diff --git a/Cargo.lock b/Cargo.lock index 4577bc4..58e41a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,6 +156,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + [[package]] name = "lazy_static" version = "1.5.0" @@ -313,6 +319,7 @@ dependencies = [ "nix-bindings-util", "nix-bindings-util-sys", "pkg-config", + "serde_json", "tempfile", "zerocopy", ] @@ -461,6 +468,48 @@ dependencies = [ "windows-sys 0.61.1", ] +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "shlex" version = "1.3.0" @@ -640,3 +689,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/nix-bindings-store/Cargo.toml b/nix-bindings-store/Cargo.toml index c37d62d..2e83ab4 100644 --- a/nix-bindings-store/Cargo.toml +++ b/nix-bindings-store/Cargo.toml @@ -23,6 +23,7 @@ zerocopy = "0.8" ctor = "0.2" hex-literal = "0.4" tempfile = "3.10" +serde_json = "1.0" [build-dependencies] pkg-config = "0.3" diff --git a/nix-bindings-store/src/derivation.rs b/nix-bindings-store/src/derivation.rs index a1ee1c6..ac4c265 100644 --- a/nix-bindings-store/src/derivation.rs +++ b/nix-bindings-store/src/derivation.rs @@ -1,6 +1,13 @@ #![cfg(nix_at_least = "2.33.0pre")] use nix_bindings_store_sys as raw; +#[cfg(nix_at_least = "2.33")] +use nix_bindings_util::{ + check_call, + context::Context, + result_string_init, + string_return::{callback_get_result_string, callback_get_result_string_data}, +}; use std::ptr::NonNull; /// A Nix derivation @@ -15,6 +22,28 @@ impl Derivation { Derivation { inner } } + /// Convert the derivation to JSON (which is encoded to a string). + /// + /// **Requires Nix 2.33 or later.** + /// + /// The JSON format follows the [Nix derivation JSON schema](https://nix.dev/manual/nix/latest/protocols/json/derivation.html). + /// Note that this format is experimental as of writing. + #[cfg(nix_at_least = "2.33")] + pub fn to_json_string(&self) -> anyhow::Result { + let mut ctx = Context::new(); + + unsafe { + let mut r = result_string_init!(); + check_call!(raw::derivation_to_json( + &mut ctx, + self.inner.as_ptr(), + Some(callback_get_result_string), + callback_get_result_string_data(&mut r) + ))?; + r + } + } + /// This is a low level function that you shouldn't have to call unless you are developing the Nix bindings. /// /// Construct a new `Derivation` by first cloning the C derivation. diff --git a/nix-bindings-store/src/store.rs b/nix-bindings-store/src/store.rs index 11ea0e1..7f231f0 100644 --- a/nix-bindings-store/src/store.rs +++ b/nix-bindings-store/src/store.rs @@ -572,37 +572,34 @@ mod tests { } #[cfg(nix_at_least = "2.33")] - fn create_test_derivation_json() -> String { + fn create_test_derivation_json() -> serde_json::Value { let system = current_system().unwrap_or_else(|_| { // Fallback to Rust's platform detection format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS) }); - format!( - r#"{{ - "args": ["-c", "echo $name foo > $out"], + serde_json::json!({ + "args": ["-c", "echo $name foo > $out"], + "builder": "/bin/sh", + "env": { "builder": "/bin/sh", - "env": {{ - "builder": "/bin/sh", - "name": "myname", - "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9", - "system": "{}" - }}, - "inputs": {{ - "drvs": {{}}, - "srcs": [] - }}, "name": "myname", - "outputs": {{ - "out": {{ + "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9", + "system": system + }, + "inputs": { + "drvs": {}, + "srcs": [] + }, + "name": "myname", + "outputs": { + "out": { "hashAlgo": "sha256", "method": "nar" - }} - }}, - "system": "{}", - "version": 4 - }}"#, - system, system - ) + } + }, + "system": system, + "version": 4 + }) } #[test] @@ -610,7 +607,7 @@ mod tests { fn derivation_from_json() { let (mut store, temp_dir) = create_temp_store(); let drv_json = create_test_derivation_json(); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); // If we got here, parsing succeeded drop(drv); drop(store); @@ -627,12 +624,35 @@ mod tests { drop(temp_dir); } + #[test] + #[cfg(nix_at_least = "2.33")] + fn derivation_to_json_round_trip() { + let (mut store, _temp_dir) = create_temp_store(); + let original_value = create_test_derivation_json(); + + // Parse JSON to Derivation + let drv = store + .derivation_from_json(&original_value.to_string()) + .unwrap(); + + // Convert back to JSON + let round_trip_json = drv.to_json_string().unwrap(); + let round_trip_value: serde_json::Value = serde_json::from_str(&round_trip_json).unwrap(); + + // Verify the round-trip JSON matches the original + assert_eq!( + original_value, round_trip_value, + "Round-trip JSON should match original.\nOriginal: {}\nRound-trip: {}", + original_value, round_trip_value + ); + } + #[test] #[cfg(nix_at_least = "2.33")] fn add_derivation() { let (mut store, temp_dir) = create_temp_store(); let drv_json = create_test_derivation_json(); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); let drv_path = store.add_derivation(&drv).unwrap(); // Verify we got a .drv path @@ -648,7 +668,7 @@ mod tests { fn realise() { let (mut store, temp_dir) = create_temp_store(); let drv_json = create_test_derivation_json(); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); let drv_path = store.add_derivation(&drv).unwrap(); // Build the derivation @@ -665,51 +685,48 @@ mod tests { } #[cfg(nix_at_least = "2.33")] - fn create_multi_output_derivation_json() -> String { + fn create_multi_output_derivation_json() -> serde_json::Value { let system = current_system() .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS)); - format!( - r#"{{ - "version": 4, - "name": "multi-output-test", - "system": "{}", + serde_json::json!({ + "version": 4, + "name": "multi-output-test", + "system": system, + "builder": "/bin/sh", + "args": ["-c", "echo a > $outa; echo b > $outb; echo c > $outc; echo d > $outd; echo e > $oute; echo f > $outf; echo g > $outg; echo h > $outh; echo i > $outi; echo j > $outj"], + "env": { "builder": "/bin/sh", - "args": ["-c", "echo a > $outa; echo b > $outb; echo c > $outc; echo d > $outd; echo e > $oute; echo f > $outf; echo g > $outg; echo h > $outh; echo i > $outi; echo j > $outj"], - "env": {{ - "builder": "/bin/sh", - "name": "multi-output-test", - "system": "{}", - "outf": "/1vkfzqpwk313b51x0xjyh5s7w1lx141mr8da3dr9wqz5aqjyr2fh", - "outd": "/1ypxifgmbzp5sd0pzsp2f19aq68x5215260z3lcrmy5fch567lpm", - "outi": "/1wmasjnqi12j1mkjbxazdd0qd0ky6dh1qry12fk8qyp5kdamhbdx", - "oute": "/1f9r2k1s168js509qlw8a9di1qd14g5lqdj5fcz8z7wbqg11qp1f", - "outh": "/1rkx1hmszslk5nq9g04iyvh1h7bg8p92zw0hi4155hkjm8bpdn95", - "outc": "/1rj4nsf9pjjqq9jsq58a2qkwa7wgvgr09kgmk7mdyli6h1plas4w", - "outb": "/1p7i1dxifh86xq97m5kgb44d7566gj7rfjbw7fk9iij6ca4akx61", - "outg": "/14f8qi0r804vd6a6v40ckylkk1i6yl6fm243qp6asywy0km535lc", - "outj": "/0gkw1366qklqfqb2lw1pikgdqh3cmi3nw6f1z04an44ia863nxaz", - "outa": "/039akv9zfpihrkrv4pl54f3x231x362bll9afblsgfqgvx96h198" - }}, - "inputs": {{ - "drvs": {{}}, - "srcs": [] - }}, - "outputs": {{ - "outd": {{ "hashAlgo": "sha256", "method": "nar" }}, - "outf": {{ "hashAlgo": "sha256", "method": "nar" }}, - "outg": {{ "hashAlgo": "sha256", "method": "nar" }}, - "outb": {{ "hashAlgo": "sha256", "method": "nar" }}, - "outc": {{ "hashAlgo": "sha256", "method": "nar" }}, - "outi": {{ "hashAlgo": "sha256", "method": "nar" }}, - "outj": {{ "hashAlgo": "sha256", "method": "nar" }}, - "outh": {{ "hashAlgo": "sha256", "method": "nar" }}, - "outa": {{ "hashAlgo": "sha256", "method": "nar" }}, - "oute": {{ "hashAlgo": "sha256", "method": "nar" }} - }} - }}"#, - system, system - ) + "name": "multi-output-test", + "system": system, + "outf": "/1vkfzqpwk313b51x0xjyh5s7w1lx141mr8da3dr9wqz5aqjyr2fh", + "outd": "/1ypxifgmbzp5sd0pzsp2f19aq68x5215260z3lcrmy5fch567lpm", + "outi": "/1wmasjnqi12j1mkjbxazdd0qd0ky6dh1qry12fk8qyp5kdamhbdx", + "oute": "/1f9r2k1s168js509qlw8a9di1qd14g5lqdj5fcz8z7wbqg11qp1f", + "outh": "/1rkx1hmszslk5nq9g04iyvh1h7bg8p92zw0hi4155hkjm8bpdn95", + "outc": "/1rj4nsf9pjjqq9jsq58a2qkwa7wgvgr09kgmk7mdyli6h1plas4w", + "outb": "/1p7i1dxifh86xq97m5kgb44d7566gj7rfjbw7fk9iij6ca4akx61", + "outg": "/14f8qi0r804vd6a6v40ckylkk1i6yl6fm243qp6asywy0km535lc", + "outj": "/0gkw1366qklqfqb2lw1pikgdqh3cmi3nw6f1z04an44ia863nxaz", + "outa": "/039akv9zfpihrkrv4pl54f3x231x362bll9afblsgfqgvx96h198" + }, + "inputs": { + "drvs": {}, + "srcs": [] + }, + "outputs": { + "outd": { "hashAlgo": "sha256", "method": "nar" }, + "outf": { "hashAlgo": "sha256", "method": "nar" }, + "outg": { "hashAlgo": "sha256", "method": "nar" }, + "outb": { "hashAlgo": "sha256", "method": "nar" }, + "outc": { "hashAlgo": "sha256", "method": "nar" }, + "outi": { "hashAlgo": "sha256", "method": "nar" }, + "outj": { "hashAlgo": "sha256", "method": "nar" }, + "outh": { "hashAlgo": "sha256", "method": "nar" }, + "outa": { "hashAlgo": "sha256", "method": "nar" }, + "oute": { "hashAlgo": "sha256", "method": "nar" } + } + }) } #[test] @@ -717,7 +734,7 @@ mod tests { fn realise_multi_output_ordering() { let (mut store, temp_dir) = create_temp_store(); let drv_json = create_multi_output_derivation_json(); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); let drv_path = store.add_derivation(&drv).unwrap(); // Build the derivation @@ -741,34 +758,31 @@ mod tests { // Create a derivation with an invalid system let system = "bogus65-bogusos"; - let drv_json = format!( - r#"{{ - "args": ["-c", "echo $name foo > $out"], + let drv_json = serde_json::json!({ + "args": ["-c", "echo $name foo > $out"], + "builder": "/bin/sh", + "env": { "builder": "/bin/sh", - "env": {{ - "builder": "/bin/sh", - "name": "myname", - "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9", - "system": "{}" - }}, - "inputs": {{ - "drvs": {{}}, - "srcs": [] - }}, "name": "myname", - "outputs": {{ - "out": {{ + "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9", + "system": system + }, + "inputs": { + "drvs": {}, + "srcs": [] + }, + "name": "myname", + "outputs": { + "out": { "hashAlgo": "sha256", "method": "nar" - }} - }}, - "system": "{}", - "version": 4 - }}"#, - system, system - ); + } + }, + "system": system, + "version": 4 + }); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); let drv_path = store.add_derivation(&drv).unwrap(); // Try to build - should fail @@ -796,34 +810,31 @@ mod tests { .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS)); // Create a derivation where the builder exits with error - let drv_json = format!( - r#"{{ - "args": ["-c", "exit 1"], + let drv_json = serde_json::json!({ + "args": ["-c", "exit 1"], + "builder": "/bin/sh", + "env": { "builder": "/bin/sh", - "env": {{ - "builder": "/bin/sh", - "name": "failing", - "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9", - "system": "{}" - }}, - "inputs": {{ - "drvs": {{}}, - "srcs": [] - }}, "name": "failing", - "outputs": {{ - "out": {{ + "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9", + "system": system + }, + "inputs": { + "drvs": {}, + "srcs": [] + }, + "name": "failing", + "outputs": { + "out": { "hashAlgo": "sha256", "method": "nar" - }} - }}, - "system": "{}", - "version": 4 - }}"#, - system, system - ); + } + }, + "system": system, + "version": 4 + }); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); let drv_path = store.add_derivation(&drv).unwrap(); // Try to build - should fail @@ -851,34 +862,31 @@ mod tests { .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS)); // Create a derivation where the builder succeeds but produces no output - let drv_json = format!( - r#"{{ - "args": ["-c", "true"], + let drv_json = serde_json::json!({ + "args": ["-c", "true"], + "builder": "/bin/sh", + "env": { "builder": "/bin/sh", - "env": {{ - "builder": "/bin/sh", - "name": "no-output", - "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9", - "system": "{}" - }}, - "inputs": {{ - "drvs": {{}}, - "srcs": [] - }}, "name": "no-output", - "outputs": {{ - "out": {{ + "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9", + "system": system + }, + "inputs": { + "drvs": {}, + "srcs": [] + }, + "name": "no-output", + "outputs": { + "out": { "hashAlgo": "sha256", "method": "nar" - }} - }}, - "system": "{}", - "version": 4 - }}"#, - system, system - ); + } + }, + "system": system, + "version": 4 + }); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); let drv_path = store.add_derivation(&drv).unwrap(); // Try to build - should fail @@ -902,7 +910,7 @@ mod tests { fn get_fs_closure_with_outputs() { let (mut store, temp_dir) = create_temp_store(); let drv_json = create_test_derivation_json(); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); let drv_path = store.add_derivation(&drv).unwrap(); // Build the derivation to get the output path @@ -935,7 +943,7 @@ mod tests { fn get_fs_closure_without_outputs() { let (mut store, temp_dir) = create_temp_store(); let drv_json = create_test_derivation_json(); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); let drv_path = store.add_derivation(&drv).unwrap(); // Build the derivation to get the output path @@ -964,7 +972,7 @@ mod tests { fn get_fs_closure_flip_direction() { let (mut store, temp_dir) = create_temp_store(); let drv_json = create_test_derivation_json(); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); let drv_path = store.add_derivation(&drv).unwrap(); // Build the derivation to get the output path @@ -991,7 +999,7 @@ mod tests { fn get_fs_closure_include_derivers() { let (mut store, temp_dir) = create_temp_store(); let drv_json = create_test_derivation_json(); - let drv = store.derivation_from_json(&drv_json).unwrap(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); let drv_path = store.add_derivation(&drv).unwrap(); let drv_path_name = drv_path.name().unwrap();