Merge pull request #12 from nixops4/fs-closure
feat: Store::get_fs_closure
This commit is contained in:
commit
8c64b5e380
10 changed files with 700 additions and 27 deletions
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"Bash(nix flake check:*)",
|
|
||||||
"Bash(git grep:*)",
|
|
||||||
"Bash(git cherry-pick:*)",
|
|
||||||
"Bash(sed:*)"
|
|
||||||
],
|
|
||||||
"deny": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -170,11 +170,11 @@
|
||||||
"nixpkgs-regression": "nixpkgs-regression"
|
"nixpkgs-regression": "nixpkgs-regression"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1756762578,
|
"lastModified": 1761069056,
|
||||||
"narHash": "sha256-JpSWFjOgPWeWb5bb+HMMGR+Q0dOH7nl2t4WgyBVaWx8=",
|
"narHash": "sha256-R6uBB3fef75wVM1OjiM0uYLLf2P5eTCWHPCQAbCaGzA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nix",
|
"repo": "nix",
|
||||||
"rev": "ab095c029c7deef98b99c5249c09fe9a8a095800",
|
"rev": "7e8db2eb59d8798047e8cc025a3eb18613a8918c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
2
rust/Cargo.lock
generated
2
rust/Cargo.lock
generated
|
|
@ -264,10 +264,12 @@ name = "nix-bindings-store"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"ctor",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"nix-bindings-bindgen-raw",
|
"nix-bindings-bindgen-raw",
|
||||||
"nix-bindings-util",
|
"nix-bindings-util",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,10 @@
|
||||||
|
|
||||||
echo "Configuring relocated store at $NIX_REMOTE..."
|
echo "Configuring relocated store at $NIX_REMOTE..."
|
||||||
|
|
||||||
|
# Create nix.conf with experimental features enabled
|
||||||
|
mkdir -p "$NIX_CONF_DIR"
|
||||||
|
echo "experimental-features = ca-derivations flakes" > "$NIX_CONF_DIR/nix.conf"
|
||||||
|
|
||||||
# Init ahead of time, because concurrent initialization is flaky
|
# Init ahead of time, because concurrent initialization is flaky
|
||||||
${
|
${
|
||||||
# Not using nativeBuildInputs because this should (hopefully) be
|
# Not using nativeBuildInputs because this should (hopefully) be
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,7 @@ mod tests {
|
||||||
// Only set experimental-features once to minimize the window where
|
// Only set experimental-features once to minimize the window where
|
||||||
// concurrent Nix operations might read the setting while it's being modified
|
// concurrent Nix operations might read the setting while it's being modified
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
|
nix_bindings_expr::eval_state::init().unwrap();
|
||||||
nix_bindings_util::settings::set("experimental-features", "flakes").unwrap();
|
nix_bindings_util::settings::set("experimental-features", "flakes").unwrap();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -299,6 +300,7 @@ mod tests {
|
||||||
init();
|
init();
|
||||||
let gc_registration = gc_register_my_thread();
|
let gc_registration = gc_register_my_thread();
|
||||||
let store = Store::open(None, []).unwrap();
|
let store = Store::open(None, []).unwrap();
|
||||||
|
let fetchers_settings = FetchersSettings::new().unwrap();
|
||||||
let flake_settings = FlakeSettings::new().unwrap();
|
let flake_settings = FlakeSettings::new().unwrap();
|
||||||
let mut eval_state = EvalStateBuilder::new(store)
|
let mut eval_state = EvalStateBuilder::new(store)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -326,7 +328,7 @@ mod tests {
|
||||||
let flake_lock_flags = FlakeLockFlags::new(&flake_settings).unwrap();
|
let flake_lock_flags = FlakeLockFlags::new(&flake_settings).unwrap();
|
||||||
|
|
||||||
let (flake_ref, fragment) = FlakeReference::parse_with_fragment(
|
let (flake_ref, fragment) = FlakeReference::parse_with_fragment(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&FlakeReferenceParseFlags::new(&flake_settings).unwrap(),
|
&FlakeReferenceParseFlags::new(&flake_settings).unwrap(),
|
||||||
&format!("path:{}#subthing", tmp_dir.path().display()),
|
&format!("path:{}#subthing", tmp_dir.path().display()),
|
||||||
|
|
@ -336,7 +338,7 @@ mod tests {
|
||||||
assert_eq!(fragment, "subthing");
|
assert_eq!(fragment, "subthing");
|
||||||
|
|
||||||
let locked_flake = LockedFlake::lock(
|
let locked_flake = LockedFlake::lock(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&eval_state,
|
&eval_state,
|
||||||
&flake_lock_flags,
|
&flake_lock_flags,
|
||||||
|
|
@ -353,6 +355,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(hello, "potato");
|
assert_eq!(hello, "potato");
|
||||||
|
|
||||||
|
drop(fetchers_settings);
|
||||||
drop(tmp_dir);
|
drop(tmp_dir);
|
||||||
drop(gc_registration);
|
drop(gc_registration);
|
||||||
}
|
}
|
||||||
|
|
@ -362,6 +365,7 @@ mod tests {
|
||||||
init();
|
init();
|
||||||
let gc_registration = gc_register_my_thread();
|
let gc_registration = gc_register_my_thread();
|
||||||
let store = Store::open(None, []).unwrap();
|
let store = Store::open(None, []).unwrap();
|
||||||
|
let fetchers_settings = FetchersSettings::new().unwrap();
|
||||||
let flake_settings = FlakeSettings::new().unwrap();
|
let flake_settings = FlakeSettings::new().unwrap();
|
||||||
let mut eval_state = EvalStateBuilder::new(store)
|
let mut eval_state = EvalStateBuilder::new(store)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -382,6 +386,8 @@ mod tests {
|
||||||
|
|
||||||
let flake_dir_a_str = flake_dir_a.to_str().unwrap();
|
let flake_dir_a_str = flake_dir_a.to_str().unwrap();
|
||||||
let flake_dir_c_str = flake_dir_c.to_str().unwrap();
|
let flake_dir_c_str = flake_dir_c.to_str().unwrap();
|
||||||
|
assert!(!flake_dir_a_str.is_empty());
|
||||||
|
assert!(!flake_dir_c_str.is_empty());
|
||||||
|
|
||||||
// a
|
// a
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
|
|
@ -434,7 +440,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (flake_ref_a, fragment) = FlakeReference::parse_with_fragment(
|
let (flake_ref_a, fragment) = FlakeReference::parse_with_fragment(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&flake_reference_parse_flags,
|
&flake_reference_parse_flags,
|
||||||
&format!("path:{}", &flake_dir_a_str),
|
&format!("path:{}", &flake_dir_a_str),
|
||||||
|
|
@ -448,7 +454,7 @@ mod tests {
|
||||||
flake_lock_flags.set_mode_check().unwrap();
|
flake_lock_flags.set_mode_check().unwrap();
|
||||||
|
|
||||||
let locked_flake = LockedFlake::lock(
|
let locked_flake = LockedFlake::lock(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&eval_state,
|
&eval_state,
|
||||||
&flake_lock_flags,
|
&flake_lock_flags,
|
||||||
|
|
@ -465,7 +471,7 @@ mod tests {
|
||||||
flake_lock_flags.set_mode_virtual().unwrap();
|
flake_lock_flags.set_mode_virtual().unwrap();
|
||||||
|
|
||||||
let locked_flake = LockedFlake::lock(
|
let locked_flake = LockedFlake::lock(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&eval_state,
|
&eval_state,
|
||||||
&flake_lock_flags,
|
&flake_lock_flags,
|
||||||
|
|
@ -487,7 +493,7 @@ mod tests {
|
||||||
flake_lock_flags.set_mode_check().unwrap();
|
flake_lock_flags.set_mode_check().unwrap();
|
||||||
|
|
||||||
let locked_flake = LockedFlake::lock(
|
let locked_flake = LockedFlake::lock(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&eval_state,
|
&eval_state,
|
||||||
&flake_lock_flags,
|
&flake_lock_flags,
|
||||||
|
|
@ -507,7 +513,7 @@ mod tests {
|
||||||
flake_lock_flags.set_mode_write_as_needed().unwrap();
|
flake_lock_flags.set_mode_write_as_needed().unwrap();
|
||||||
|
|
||||||
let locked_flake = LockedFlake::lock(
|
let locked_flake = LockedFlake::lock(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&eval_state,
|
&eval_state,
|
||||||
&flake_lock_flags,
|
&flake_lock_flags,
|
||||||
|
|
@ -527,7 +533,7 @@ mod tests {
|
||||||
flake_lock_flags.set_mode_check().unwrap();
|
flake_lock_flags.set_mode_check().unwrap();
|
||||||
|
|
||||||
let locked_flake = LockedFlake::lock(
|
let locked_flake = LockedFlake::lock(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&eval_state,
|
&eval_state,
|
||||||
&flake_lock_flags,
|
&flake_lock_flags,
|
||||||
|
|
@ -548,7 +554,7 @@ mod tests {
|
||||||
flake_lock_flags.set_mode_write_as_needed().unwrap();
|
flake_lock_flags.set_mode_write_as_needed().unwrap();
|
||||||
|
|
||||||
let (flake_ref_c, fragment) = FlakeReference::parse_with_fragment(
|
let (flake_ref_c, fragment) = FlakeReference::parse_with_fragment(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&flake_reference_parse_flags,
|
&flake_reference_parse_flags,
|
||||||
&format!("path:{}", &flake_dir_c_str),
|
&format!("path:{}", &flake_dir_c_str),
|
||||||
|
|
@ -561,7 +567,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let locked_flake = LockedFlake::lock(
|
let locked_flake = LockedFlake::lock(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&eval_state,
|
&eval_state,
|
||||||
&flake_lock_flags,
|
&flake_lock_flags,
|
||||||
|
|
@ -584,7 +590,7 @@ mod tests {
|
||||||
flake_lock_flags.set_mode_check().unwrap();
|
flake_lock_flags.set_mode_check().unwrap();
|
||||||
|
|
||||||
let locked_flake = LockedFlake::lock(
|
let locked_flake = LockedFlake::lock(
|
||||||
&FetchersSettings::new().unwrap(),
|
&fetchers_settings,
|
||||||
&flake_settings,
|
&flake_settings,
|
||||||
&eval_state,
|
&eval_state,
|
||||||
&flake_lock_flags,
|
&flake_lock_flags,
|
||||||
|
|
@ -599,6 +605,7 @@ mod tests {
|
||||||
let hello = eval_state.require_string(&hello).unwrap();
|
let hello = eval_state.require_string(&hello).unwrap();
|
||||||
assert_eq!(hello, "BOB");
|
assert_eq!(hello, "BOB");
|
||||||
|
|
||||||
|
drop(fetchers_settings);
|
||||||
drop(tmp_dir);
|
drop(tmp_dir);
|
||||||
drop(gc_registration);
|
drop(gc_registration);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,9 @@ nix-bindings-util = { path = "../nix-bindings-util" }
|
||||||
nix-bindings-bindgen-raw = { path = "../nix-bindings-bindgen-raw" }
|
nix-bindings-bindgen-raw = { path = "../nix-bindings-bindgen-raw" }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ctor = "0.2"
|
||||||
|
tempfile = "3.10"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
pkg-config = "0.3"
|
pkg-config = "0.3"
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ fn main() {
|
||||||
// Unfortunately, Rust doesn't give us a "greater than" operator in conditional
|
// Unfortunately, Rust doesn't give us a "greater than" operator in conditional
|
||||||
// compilation, so we pre-evaluate the version comparisons here, making use
|
// compilation, so we pre-evaluate the version comparisons here, making use
|
||||||
// of the multi-valued nature of Rust cfgs.
|
// of the multi-valued nature of Rust cfgs.
|
||||||
let relevant_versions = vec!["2.26"];
|
let relevant_versions = vec!["2.26", "2.33"];
|
||||||
let versions = relevant_versions
|
let versions = relevant_versions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| format!("\"{}\"", v))
|
.map(|v| format!("\"{}\"", v))
|
||||||
|
|
|
||||||
25
rust/nix-bindings-store/src/derivation.rs
Normal file
25
rust/nix-bindings-store/src/derivation.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#![cfg(nix_at_least = "2.33")]
|
||||||
|
|
||||||
|
use nix_bindings_bindgen_raw as raw;
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
/// A Nix derivation
|
||||||
|
///
|
||||||
|
/// **Requires Nix 2.33 or later.**
|
||||||
|
pub struct Derivation {
|
||||||
|
pub(crate) inner: NonNull<raw::derivation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Derivation {
|
||||||
|
pub(crate) fn new_raw(inner: NonNull<raw::derivation>) -> Self {
|
||||||
|
Derivation { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Derivation {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
raw::derivation_free(self.inner.as_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
|
pub mod derivation;
|
||||||
pub mod path;
|
pub mod path;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,15 @@ use nix_bindings_util::context::Context;
|
||||||
use nix_bindings_util::string_return::{callback_get_result_string, callback_get_result_string_data};
|
use nix_bindings_util::string_return::{callback_get_result_string, callback_get_result_string_data};
|
||||||
use nix_bindings_util::{check_call, result_string_init};
|
use nix_bindings_util::{check_call, result_string_init};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::ffi::{c_char, CString};
|
use std::ffi::{c_char, CString};
|
||||||
use std::ptr::null_mut;
|
use std::ptr::null_mut;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
use std::sync::{Arc, Mutex, Weak};
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
|
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
use crate::derivation::Derivation;
|
||||||
use crate::path::StorePath;
|
use crate::path::StorePath;
|
||||||
|
|
||||||
/* TODO make Nix itself thread safe */
|
/* TODO make Nix itself thread safe */
|
||||||
|
|
@ -65,6 +69,28 @@ lazy_static! {
|
||||||
static ref STORE_CACHE: Arc<Mutex<StoreCacheMap>> = Arc::new(Mutex::new(HashMap::new()));
|
static ref STORE_CACHE: Arc<Mutex<StoreCacheMap>> = Arc::new(Mutex::new(HashMap::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
unsafe extern "C" fn callback_get_result_store_path_set(
|
||||||
|
_context: *mut raw::c_context,
|
||||||
|
user_data: *mut std::os::raw::c_void,
|
||||||
|
store_path: *const raw::StorePath,
|
||||||
|
) {
|
||||||
|
let ret = user_data as *mut Vec<StorePath>;
|
||||||
|
let ret: &mut Vec<StorePath> = &mut *ret;
|
||||||
|
|
||||||
|
let store_path = raw::store_path_clone(store_path);
|
||||||
|
|
||||||
|
let store_path =
|
||||||
|
NonNull::new(store_path).expect("nix_store_parse_path returned a null pointer");
|
||||||
|
let store_path = StorePath::new_raw(store_path);
|
||||||
|
ret.push(store_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
fn callback_get_result_store_path_set_data(vec: &mut Vec<StorePath>) -> *mut std::os::raw::c_void {
|
||||||
|
vec as *mut Vec<StorePath> as *mut std::os::raw::c_void
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Store {
|
pub struct Store {
|
||||||
inner: Arc<StoreRef>,
|
inner: Arc<StoreRef>,
|
||||||
/* An error context to reuse. This way we don't have to allocate them for each store operation. */
|
/* An error context to reuse. This way we don't have to allocate them for each store operation. */
|
||||||
|
|
@ -232,6 +258,158 @@ impl Store {
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a derivation from JSON.
|
||||||
|
///
|
||||||
|
/// **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.
|
||||||
|
/// The derivation is not added to the store; use [`Store::add_derivation`] for that.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `json`: A JSON string representing the derivation
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A [`Derivation`] object if parsing succeeds, or an error if the JSON is invalid
|
||||||
|
/// or malformed.
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
#[doc(alias = "nix_derivation_from_json")]
|
||||||
|
pub fn derivation_from_json(&mut self, json: &str) -> Result<Derivation> {
|
||||||
|
let json_cstr = CString::new(json)?;
|
||||||
|
unsafe {
|
||||||
|
let drv = check_call!(raw::derivation_from_json(
|
||||||
|
&mut self.context,
|
||||||
|
self.inner.ptr(),
|
||||||
|
json_cstr.as_ptr()
|
||||||
|
))?;
|
||||||
|
let inner = NonNull::new(drv)
|
||||||
|
.ok_or_else(|| Error::msg("derivation_from_json returned null"))?;
|
||||||
|
Ok(Derivation::new_raw(inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a derivation to the store.
|
||||||
|
///
|
||||||
|
/// **Requires Nix 2.33 or later.**
|
||||||
|
///
|
||||||
|
/// This computes the store path for the derivation and registers it in the store.
|
||||||
|
/// The derivation itself is written to the store as a `.drv` file.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `drv`: The derivation to add
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The store path of the derivation (ending in `.drv`).
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
#[doc(alias = "nix_add_derivation")]
|
||||||
|
pub fn add_derivation(&mut self, drv: &Derivation) -> Result<StorePath> {
|
||||||
|
unsafe {
|
||||||
|
let path = check_call!(raw::add_derivation(
|
||||||
|
&mut self.context,
|
||||||
|
self.inner.ptr(),
|
||||||
|
drv.inner.as_ptr()
|
||||||
|
))?;
|
||||||
|
let path = NonNull::new(path)
|
||||||
|
.ok_or_else(|| Error::msg("add_derivation returned null"))?;
|
||||||
|
Ok(StorePath::new_raw(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a derivation and return its outputs.
|
||||||
|
///
|
||||||
|
/// **Requires Nix 2.33 or later.**
|
||||||
|
///
|
||||||
|
/// This builds the derivation at the given store path and returns a map of output
|
||||||
|
/// names to their realized store paths. The derivation must already exist in the store
|
||||||
|
/// (see [`Store::add_derivation`]).
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `path`: The store path of the derivation to build (typically ending in `.drv`)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A [`BTreeMap`] mapping output names (e.g., "out", "dev", "doc") to their store paths.
|
||||||
|
/// The map is ordered alphabetically by output name for deterministic iteration.
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
#[doc(alias = "nix_store_realise")]
|
||||||
|
pub fn realise(&mut self, path: &StorePath) -> Result<BTreeMap<String, StorePath>> {
|
||||||
|
let mut outputs = BTreeMap::new();
|
||||||
|
let userdata = &mut outputs as *mut BTreeMap<String, StorePath> as *mut std::os::raw::c_void;
|
||||||
|
|
||||||
|
unsafe extern "C" fn callback(
|
||||||
|
userdata: *mut std::os::raw::c_void,
|
||||||
|
outname: *const c_char,
|
||||||
|
out_path: *const raw::StorePath,
|
||||||
|
) {
|
||||||
|
let outputs = userdata as *mut BTreeMap<String, StorePath>;
|
||||||
|
let outputs = &mut *outputs;
|
||||||
|
|
||||||
|
let name = std::ffi::CStr::from_ptr(outname)
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
|
||||||
|
let path = raw::store_path_clone(out_path);
|
||||||
|
let path = NonNull::new(path).expect("store_path_clone returned null");
|
||||||
|
let path = StorePath::new_raw(path);
|
||||||
|
|
||||||
|
outputs.insert(name, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
check_call!(raw::store_realise(
|
||||||
|
&mut self.context,
|
||||||
|
self.inner.ptr(),
|
||||||
|
path.as_ptr(),
|
||||||
|
userdata,
|
||||||
|
Some(callback)
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(outputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the closure of a specific store path.
|
||||||
|
///
|
||||||
|
/// **Requires Nix 2.33 or later.**
|
||||||
|
///
|
||||||
|
/// Computes the filesystem closure (dependency graph) of a store path, with options
|
||||||
|
/// to control the direction and which related paths to include.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `store_path`: The path to compute the closure from
|
||||||
|
/// - `flip_direction`: If false, compute the forward closure (paths referenced by this path).
|
||||||
|
/// If true, compute the backward closure (paths that reference this path).
|
||||||
|
/// - `include_outputs`: When `flip_direction` is false: for any derivation in the closure, include its outputs.
|
||||||
|
/// When `flip_direction` is true: for any output in the closure, include derivations that produce it.
|
||||||
|
/// - `include_derivers`: When `flip_direction` is false: for any output in the closure, include the derivation that produced it.
|
||||||
|
/// When `flip_direction` is true: for any derivation in the closure, include its outputs.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A vector of store paths in the closure, in no particular order.
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
#[doc(alias = "nix_store_get_fs_closure")]
|
||||||
|
pub fn get_fs_closure(
|
||||||
|
&mut self,
|
||||||
|
store_path: &StorePath,
|
||||||
|
flip_direction: bool,
|
||||||
|
include_outputs: bool,
|
||||||
|
include_derivers: bool,
|
||||||
|
) -> Result<Vec<StorePath>> {
|
||||||
|
let mut r = Vec::new();
|
||||||
|
unsafe {
|
||||||
|
check_call!(raw::store_get_fs_closure(
|
||||||
|
&mut self.context,
|
||||||
|
self.inner.ptr(),
|
||||||
|
store_path.as_ptr(),
|
||||||
|
flip_direction,
|
||||||
|
include_outputs,
|
||||||
|
include_derivers,
|
||||||
|
callback_get_result_store_path_set_data(&mut r),
|
||||||
|
Some(callback_get_result_store_path_set)
|
||||||
|
))
|
||||||
|
}?;
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn weak_ref(&self) -> StoreWeak {
|
pub fn weak_ref(&self) -> StoreWeak {
|
||||||
StoreWeak {
|
StoreWeak {
|
||||||
inner: Arc::downgrade(&self.inner),
|
inner: Arc::downgrade(&self.inner),
|
||||||
|
|
@ -251,9 +429,32 @@ impl Clone for Store {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use ctor::ctor;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[ctor]
|
||||||
|
fn test_setup() {
|
||||||
|
// Initialize settings for tests
|
||||||
|
let _ = INIT.as_ref();
|
||||||
|
|
||||||
|
// Enable ca-derivations for all tests
|
||||||
|
nix_bindings_util::settings::set("experimental-features", "ca-derivations").ok();
|
||||||
|
|
||||||
|
// Disable build hooks to prevent test recursion
|
||||||
|
nix_bindings_util::settings::set("build-hook", "").ok();
|
||||||
|
|
||||||
|
// Set custom build dir for sandbox
|
||||||
|
if cfg!(target_os = "linux") {
|
||||||
|
nix_bindings_util::settings::set("sandbox-build-dir", "/custom-build-dir-for-test").ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::env::set_var("_NIX_TEST_NO_SANDBOX", "1");
|
||||||
|
|
||||||
|
// Tests run offline
|
||||||
|
nix_bindings_util::settings::set("substituters", "").ok();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn none_works() {
|
fn none_works() {
|
||||||
let res = Store::open(None, HashMap::new());
|
let res = Store::open(None, HashMap::new());
|
||||||
|
|
@ -338,4 +539,445 @@ mod tests {
|
||||||
assert!(weak.upgrade().is_none());
|
assert!(weak.upgrade().is_none());
|
||||||
assert!(weak.inner.upgrade().is_none());
|
assert!(weak.inner.upgrade().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_temp_store() -> (Store, tempfile::TempDir) {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
let store_dir = temp_dir.path().join("store");
|
||||||
|
let state_dir = temp_dir.path().join("state");
|
||||||
|
let log_dir = temp_dir.path().join("log");
|
||||||
|
|
||||||
|
let store_dir_str = store_dir.to_str().unwrap();
|
||||||
|
let state_dir_str = state_dir.to_str().unwrap();
|
||||||
|
let log_dir_str = log_dir.to_str().unwrap();
|
||||||
|
|
||||||
|
let params = vec![
|
||||||
|
("store", store_dir_str),
|
||||||
|
("state", state_dir_str),
|
||||||
|
("log", log_dir_str),
|
||||||
|
];
|
||||||
|
|
||||||
|
let store = Store::open(Some("local"), params).unwrap();
|
||||||
|
(store, temp_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_system() -> Result<String> {
|
||||||
|
nix_bindings_util::settings::get("system")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
fn create_test_derivation_json() -> String {
|
||||||
|
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"],
|
||||||
|
"builder": "/bin/sh",
|
||||||
|
"env": {{
|
||||||
|
"builder": "/bin/sh",
|
||||||
|
"name": "myname",
|
||||||
|
"out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
|
||||||
|
"system": "{}"
|
||||||
|
}},
|
||||||
|
"inputDrvs": {{}},
|
||||||
|
"inputSrcs": [],
|
||||||
|
"name": "myname",
|
||||||
|
"outputs": {{
|
||||||
|
"out": {{
|
||||||
|
"hashAlgo": "sha256",
|
||||||
|
"method": "nar"
|
||||||
|
}}
|
||||||
|
}},
|
||||||
|
"system": "{}",
|
||||||
|
"version": 3
|
||||||
|
}}"#,
|
||||||
|
system, system
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
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();
|
||||||
|
// If we got here, parsing succeeded
|
||||||
|
drop(drv);
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
fn derivation_from_invalid_json() {
|
||||||
|
let (mut store, temp_dir) = create_temp_store();
|
||||||
|
let result = store.derivation_from_json("not valid json");
|
||||||
|
assert!(result.is_err());
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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_path = store.add_derivation(&drv).unwrap();
|
||||||
|
|
||||||
|
// Verify we got a .drv path
|
||||||
|
let name = drv_path.name().unwrap();
|
||||||
|
assert!(name.ends_with(".drv"));
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
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_path = store.add_derivation(&drv).unwrap();
|
||||||
|
|
||||||
|
// Build the derivation
|
||||||
|
let outputs = store.realise(&drv_path).unwrap();
|
||||||
|
|
||||||
|
// Verify we got the expected output
|
||||||
|
assert!(outputs.contains_key("out"));
|
||||||
|
let out_path = &outputs["out"];
|
||||||
|
let out_name = out_path.name().unwrap();
|
||||||
|
assert_eq!(out_name, "myname");
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
fn create_multi_output_derivation_json() -> String {
|
||||||
|
let system = current_system().unwrap_or_else(|_| {
|
||||||
|
format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS)
|
||||||
|
});
|
||||||
|
|
||||||
|
format!(
|
||||||
|
r#"{{
|
||||||
|
"version": 3,
|
||||||
|
"name": "multi-output-test",
|
||||||
|
"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",
|
||||||
|
"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"
|
||||||
|
}},
|
||||||
|
"inputDrvs": {{}},
|
||||||
|
"inputSrcs": [],
|
||||||
|
"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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
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_path = store.add_derivation(&drv).unwrap();
|
||||||
|
|
||||||
|
// Build the derivation
|
||||||
|
let outputs = store.realise(&drv_path).unwrap();
|
||||||
|
|
||||||
|
// Verify outputs are complete (BTreeMap guarantees ordering)
|
||||||
|
let output_names: Vec<&String> = outputs.keys().collect();
|
||||||
|
let expected_order = vec!["outa", "outb", "outc", "outd", "oute", "outf", "outg", "outh", "outi", "outj"];
|
||||||
|
assert_eq!(output_names, expected_order);
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
fn realise_invalid_system() {
|
||||||
|
let (mut store, temp_dir) = create_temp_store();
|
||||||
|
|
||||||
|
// Create a derivation with an invalid system
|
||||||
|
let system = "bogus65-bogusos";
|
||||||
|
let drv_json = format!(
|
||||||
|
r#"{{
|
||||||
|
"args": ["-c", "echo $name foo > $out"],
|
||||||
|
"builder": "/bin/sh",
|
||||||
|
"env": {{
|
||||||
|
"builder": "/bin/sh",
|
||||||
|
"name": "myname",
|
||||||
|
"out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
|
||||||
|
"system": "{}"
|
||||||
|
}},
|
||||||
|
"inputDrvs": {{}},
|
||||||
|
"inputSrcs": [],
|
||||||
|
"name": "myname",
|
||||||
|
"outputs": {{
|
||||||
|
"out": {{
|
||||||
|
"hashAlgo": "sha256",
|
||||||
|
"method": "nar"
|
||||||
|
}}
|
||||||
|
}},
|
||||||
|
"system": "{}",
|
||||||
|
"version": 3
|
||||||
|
}}"#,
|
||||||
|
system, system
|
||||||
|
);
|
||||||
|
|
||||||
|
let drv = store.derivation_from_json(&drv_json).unwrap();
|
||||||
|
let drv_path = store.add_derivation(&drv).unwrap();
|
||||||
|
|
||||||
|
// Try to build - should fail
|
||||||
|
let result = store.realise(&drv_path);
|
||||||
|
let err = match result {
|
||||||
|
Ok(_) => panic!("Build should fail with invalid system"),
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
err.contains("required system or feature not available"),
|
||||||
|
"Error should mention system not available, got: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
fn realise_builder_fails() {
|
||||||
|
let (mut store, temp_dir) = create_temp_store();
|
||||||
|
|
||||||
|
let system = current_system().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"],
|
||||||
|
"builder": "/bin/sh",
|
||||||
|
"env": {{
|
||||||
|
"builder": "/bin/sh",
|
||||||
|
"name": "failing",
|
||||||
|
"out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
|
||||||
|
"system": "{}"
|
||||||
|
}},
|
||||||
|
"inputDrvs": {{}},
|
||||||
|
"inputSrcs": [],
|
||||||
|
"name": "failing",
|
||||||
|
"outputs": {{
|
||||||
|
"out": {{
|
||||||
|
"hashAlgo": "sha256",
|
||||||
|
"method": "nar"
|
||||||
|
}}
|
||||||
|
}},
|
||||||
|
"system": "{}",
|
||||||
|
"version": 3
|
||||||
|
}}"#,
|
||||||
|
system, system
|
||||||
|
);
|
||||||
|
|
||||||
|
let drv = store.derivation_from_json(&drv_json).unwrap();
|
||||||
|
let drv_path = store.add_derivation(&drv).unwrap();
|
||||||
|
|
||||||
|
// Try to build - should fail
|
||||||
|
let result = store.realise(&drv_path);
|
||||||
|
let err = match result {
|
||||||
|
Ok(_) => panic!("Build should fail when builder exits with error"),
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
err.contains("builder failed with exit code 1"),
|
||||||
|
"Error should mention builder failed with exit code, got: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
fn realise_builder_no_output() {
|
||||||
|
let (mut store, temp_dir) = create_temp_store();
|
||||||
|
|
||||||
|
let system = current_system().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"],
|
||||||
|
"builder": "/bin/sh",
|
||||||
|
"env": {{
|
||||||
|
"builder": "/bin/sh",
|
||||||
|
"name": "no-output",
|
||||||
|
"out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
|
||||||
|
"system": "{}"
|
||||||
|
}},
|
||||||
|
"inputDrvs": {{}},
|
||||||
|
"inputSrcs": [],
|
||||||
|
"name": "no-output",
|
||||||
|
"outputs": {{
|
||||||
|
"out": {{
|
||||||
|
"hashAlgo": "sha256",
|
||||||
|
"method": "nar"
|
||||||
|
}}
|
||||||
|
}},
|
||||||
|
"system": "{}",
|
||||||
|
"version": 3
|
||||||
|
}}"#,
|
||||||
|
system, system
|
||||||
|
);
|
||||||
|
|
||||||
|
let drv = store.derivation_from_json(&drv_json).unwrap();
|
||||||
|
let drv_path = store.add_derivation(&drv).unwrap();
|
||||||
|
|
||||||
|
// Try to build - should fail
|
||||||
|
let result = store.realise(&drv_path);
|
||||||
|
let err = match result {
|
||||||
|
Ok(_) => panic!("Build should fail when builder produces no output"),
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
err.contains("failed to produce output path"),
|
||||||
|
"Error should mention failed to produce output, got: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
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_path = store.add_derivation(&drv).unwrap();
|
||||||
|
|
||||||
|
// Build the derivation to get the output path
|
||||||
|
let outputs = store.realise(&drv_path).unwrap();
|
||||||
|
let out_path = &outputs["out"];
|
||||||
|
let out_path_name = out_path.name().unwrap();
|
||||||
|
|
||||||
|
// Get closure with include_outputs=true
|
||||||
|
let closure = store.get_fs_closure(&drv_path, false, true, false).unwrap();
|
||||||
|
|
||||||
|
// The closure should contain at least the derivation and its output
|
||||||
|
assert!(closure.len() >= 2, "Closure should contain at least drv and output");
|
||||||
|
|
||||||
|
// Verify the output path is in the closure
|
||||||
|
let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
|
||||||
|
assert!(out_in_closure, "Output path should be in closure when include_outputs=true");
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
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_path = store.add_derivation(&drv).unwrap();
|
||||||
|
|
||||||
|
// Build the derivation to get the output path
|
||||||
|
let outputs = store.realise(&drv_path).unwrap();
|
||||||
|
let out_path = &outputs["out"];
|
||||||
|
let out_path_name = out_path.name().unwrap();
|
||||||
|
|
||||||
|
// Get closure with include_outputs=false
|
||||||
|
let closure = store.get_fs_closure(&drv_path, false, false, false).unwrap();
|
||||||
|
|
||||||
|
// Verify the output path is NOT in the closure
|
||||||
|
let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
|
||||||
|
assert!(!out_in_closure, "Output path should not be in closure when include_outputs=false");
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
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_path = store.add_derivation(&drv).unwrap();
|
||||||
|
|
||||||
|
// Build the derivation to get the output path
|
||||||
|
let outputs = store.realise(&drv_path).unwrap();
|
||||||
|
let out_path = &outputs["out"];
|
||||||
|
let out_path_name = out_path.name().unwrap();
|
||||||
|
|
||||||
|
// Get closure with flip_direction=true (reverse dependencies)
|
||||||
|
let closure = store.get_fs_closure(&drv_path, true, true, false).unwrap();
|
||||||
|
|
||||||
|
// Verify the output path is NOT in the closure when direction is flipped
|
||||||
|
let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
|
||||||
|
assert!(!out_in_closure, "Output path should not be in closure when flip_direction=true");
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(nix_at_least = "2.33")]
|
||||||
|
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_path = store.add_derivation(&drv).unwrap();
|
||||||
|
let drv_path_name = drv_path.name().unwrap();
|
||||||
|
|
||||||
|
// Build the derivation to get the output path
|
||||||
|
let outputs = store.realise(&drv_path).unwrap();
|
||||||
|
let out_path = &outputs["out"];
|
||||||
|
|
||||||
|
// Get closure of the output path with include_derivers=true
|
||||||
|
let closure = store.get_fs_closure(out_path, false, false, true).unwrap();
|
||||||
|
|
||||||
|
// Verify the derivation path is in the closure
|
||||||
|
let drv_in_closure = closure.iter().any(|p| p.name().unwrap() == drv_path_name);
|
||||||
|
assert!(drv_in_closure, "Derivation should be in closure when include_derivers=true");
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
drop(temp_dir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue