diff --git a/rust/nix-expr/src/eval_state.rs b/rust/nix-expr/src/eval_state.rs index c82d770..51e236c 100644 --- a/rust/nix-expr/src/eval_state.rs +++ b/rust/nix-expr/src/eval_state.rs @@ -3,6 +3,7 @@ use anyhow::Context as _; use anyhow::{bail, Result}; use lazy_static::lazy_static; use nix_c_raw as raw; +use nix_store::path::StorePath; use nix_store::store::Store; use nix_util::context::Context; use nix_util::string_return::callback_get_vec_u8; @@ -34,6 +35,11 @@ pub fn init() -> Result<()> { } } +pub struct RealisedString { + pub s: String, + pub paths: Vec, +} + pub struct EvalState { eval_state: NonNull, store: Store, @@ -131,6 +137,46 @@ impl EvalState { } self.get_string(value) } + pub fn realise_string( + &self, + value: &Value, + is_import_from_derivation: bool, + ) -> Result { + let t = self.value_type(value)?; + if t != ValueType::String { + bail!("expected a string, but got a {:?}", t); + } + + let rs = unsafe { + raw::string_realise( + self.context.ptr(), + self.raw_ptr(), + value.raw_ptr(), + is_import_from_derivation, + ) + }; + self.context.check_err()?; + + let s = unsafe { + let start = raw::realised_string_get_buffer_start(rs) as *const u8; + let size = raw::realised_string_get_buffer_size(rs); + let slice = std::slice::from_raw_parts(start, size); + String::from_utf8(slice.to_vec()) + .map_err(|e| anyhow::format_err!("Nix string is not valid UTF-8: {}", e))? + }; + + let paths = unsafe { + let n = raw::realised_string_get_store_path_count(rs); + let mut paths = Vec::with_capacity(n as usize); + for i in 0..n { + let path = raw::realised_string_get_store_path(rs, i); + paths.push(StorePath::new_raw_clone(path)); + } + paths + }; + + Ok(RealisedString { s, paths }) + } fn new_value_uninitialized(&self) -> Value { let value = unsafe { raw::alloc_value(self.context.ptr(), self.raw_ptr()) }; @@ -191,12 +237,24 @@ impl Drop for EvalState { #[cfg(test)] mod tests { use ctor::ctor; + use nix_util::settings; use super::*; #[ctor] fn setup() { - init().unwrap(); + (|| -> Result<()> { + init()?; + // If it reinvokes the test suite, + // settings::set("build-hook", "")?; + + // When testing in the sandbox, the default build dir would be a parent of the storeDir, + // which causes an error. So we set a custom build dir here. + settings::set("sandbox-build-dir", "/custom-build-dir-for-test")?; + Ok(()) + })() + .unwrap(); + std::env::set_var("_NIX_TEST_NO_SANDBOX", "1"); } #[test] @@ -356,4 +414,51 @@ mod tests { }) .unwrap(); } + + #[test] + fn eval_state_realise_string() { + gc_registering_current_thread(|| { + let store = Store::open("auto").unwrap(); + let es = EvalState::new(store).unwrap(); + let expr = r#" + '' + a derivation output: ${ + derivation { name = "letsbuild"; + system = builtins.currentSystem; + builder = "/bin/sh"; + args = [ "-c" "echo foo > $out" ]; + }} + a path: ${builtins.toFile "just-a-file" "ooh file good"} + a derivation path by itself: ${ + builtins.unsafeDiscardOutputDependency + (derivation { + name = "not-actually-built-yet"; + system = builtins.currentSystem; + builder = "/bin/sh"; + args = [ "-c" "echo foo > $out" ]; + }).drvPath} + '' + "#; + let v = es.eval_from_string(expr, "").unwrap(); + es.force(&v).unwrap(); + let rs = es.realise_string(&v, false).unwrap(); + + assert!(rs.s.starts_with("a derivation output:")); + assert!(rs.s.contains("-letsbuild\n")); + assert!(!rs.s.contains("-letsbuild.drv")); + assert!(rs.s.contains("a path:")); + assert!(rs.s.contains("-just-a-file")); + assert!(!rs.s.contains("-just-a-file.drv")); + assert!(!rs.s.contains("ooh file good")); + assert!(rs.s.ends_with("-not-actually-built-yet.drv\n")); + + assert_eq!(rs.paths.len(), 3); + let mut names: Vec = rs.paths.iter().map(|p| p.name().unwrap()).collect(); + names.sort(); + assert_eq!(names[0], "just-a-file"); + assert_eq!(names[1], "letsbuild"); + assert_eq!(names[2], "not-actually-built-yet.drv"); + }) + .unwrap(); + } } diff --git a/rust/nix-store/src/lib.rs b/rust/nix-store/src/lib.rs index 55c88cb..5c57e2c 100644 --- a/rust/nix-store/src/lib.rs +++ b/rust/nix-store/src/lib.rs @@ -1 +1,2 @@ +pub mod path; pub mod store; diff --git a/rust/nix-store/src/path.rs b/rust/nix-store/src/path.rs new file mode 100644 index 0000000..1a7350c --- /dev/null +++ b/rust/nix-store/src/path.rs @@ -0,0 +1,33 @@ +use anyhow::Result; +use nix_c_raw as raw; +use nix_util::string_return::{callback_get_vec_u8, callback_get_vec_u8_data}; + +pub struct StorePath { + raw: *mut raw::StorePath, +} +impl StorePath { + pub fn new_raw_clone(raw: *const raw::StorePath) -> Self { + Self::new_raw(unsafe { raw::store_path_clone(raw as *mut raw::StorePath) }) + } + pub fn new_raw(raw: *mut raw::StorePath) -> Self { + StorePath { raw } + } + pub fn name(&self) -> Result { + unsafe { + let mut vec = Vec::new(); + raw::store_path_name( + self.raw, + Some(callback_get_vec_u8), + callback_get_vec_u8_data(&mut vec), + ); + String::from_utf8(vec).map_err(|e| e.into()) + } + } +} +impl Drop for StorePath { + fn drop(&mut self) { + unsafe { + raw::store_path_free(self.raw); + } + } +}