feat: EvalState.realise_string

(cherry picked from commit f2b1142018fd64dd45ec97f1eccf0c48cc4a8c6d)
This commit is contained in:
Robert Hensing 2024-04-08 16:54:36 +02:00
parent 6736f05a3f
commit 87203ef394
3 changed files with 140 additions and 1 deletions

View file

@ -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<StorePath>,
}
pub struct EvalState {
eval_state: NonNull<raw::EvalState>,
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<RealisedString> {
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, "<test>").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<String> = 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();
}
}