Merge pull request #47 from nixops4/derivation-to-json

`Derivation::to_json_string` and test
This commit is contained in:
Robert Hensing 2026-01-25 01:47:02 +01:00 committed by GitHub
commit 78262dbe2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 234 additions and 141 deletions

55
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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<String> {
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.

View file

@ -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();