diff --git a/flake.lock b/flake.lock index 049d278..11269fb 100644 --- a/flake.lock +++ b/flake.lock @@ -173,11 +173,11 @@ "nixpkgs-regression": "nixpkgs-regression" }, "locked": { - "lastModified": 1705358697, - "narHash": "sha256-1SjVVJKIXfSoymtIBCx8mPkhw6LpyihrtzMXdFFOH2M=", + "lastModified": 1709227987, + "narHash": "sha256-ndFevohurD6MQCCBnOdHLDmsJ3vfpF5+cKizXvq5vmw=", "owner": "tweag", "repo": "nix", - "rev": "eb456d68bd22e1296c2eca30fa06281de0da289e", + "rev": "0fd441d0bf6331a1152cdc091724b4648d187f90", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 3b14cd2..d36075c 100644 --- a/flake.nix +++ b/flake.nix @@ -26,11 +26,8 @@ perSystem = { config, self', inputs', pkgs, ... }: { - packages.nix = inputs'.nix.packages.nix.overrideAttrs { - # checkPhase does not seem to terminate. - # TODO: remove override - doCheck = false; - }; + + packages.nix = inputs'.nix.packages.nix; pre-commit.settings.hooks.nixpkgs-fmt.enable = true; # Temporarily disable rustfmt due to configuration issues diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9ab8dd3..0b9d59f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -72,6 +72,16 @@ dependencies = [ "libloading", ] +[[package]] +name = "ctor" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "either" version = "1.10.0" @@ -172,6 +182,18 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "nix-expr" +version = "0.1.0" +dependencies = [ + "anyhow", + "ctor", + "lazy_static", + "nix-c-raw", + "nix-store", + "nix-util", +] + [[package]] name = "nix-store" version = "0.1.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e3b6b73..011675b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "nix-c-raw", + "nix-expr", "nix-util", "nix-store", ] diff --git a/rust/nix-c-raw/build.rs b/rust/nix-c-raw/build.rs index ae37b02..1ac9dec 100644 --- a/rust/nix-c-raw/build.rs +++ b/rust/nix-c-raw/build.rs @@ -37,6 +37,14 @@ fn c_headers() -> Vec { args.push(format!("-I{}", path.to_str().unwrap())); } + for path in pkg_config::probe_library("bdw-gc") + .unwrap() + .include_paths + .iter() + { + args.push(format!("-I{}", path.to_str().unwrap())); + } + if let Ok(cflags) = std::env::var("RUST_NIX_C_RAW_EXTRA_CFLAGS") { for flag in cflags.split_whitespace() { args.push(flag.to_string()); diff --git a/rust/nix-c-raw/include/nix-c-raw.h b/rust/nix-c-raw/include/nix-c-raw.h index 129181e..14fb03c 100644 --- a/rust/nix-c-raw/include/nix-c-raw.h +++ b/rust/nix-c-raw/include/nix-c-raw.h @@ -1,4 +1,6 @@ #include #include +#define GC_THREADS +#include #include #include diff --git a/rust/nix-expr/Cargo.toml b/rust/nix-expr/Cargo.toml new file mode 100644 index 0000000..66a1f25 --- /dev/null +++ b/rust/nix-expr/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "nix-expr" +version = "0.1.0" +edition = "2021" + +[lib] + +[dependencies] +anyhow = "1.0.79" +nix-store = { path = "../nix-store" } +nix-util = { path = "../nix-util" } +nix-c-raw = { path = "../nix-c-raw" } +lazy_static = "1.4.0" +ctor = "0.2.7" \ No newline at end of file diff --git a/rust/nix-expr/src/eval_state.rs b/rust/nix-expr/src/eval_state.rs new file mode 100644 index 0000000..e1ec8a0 --- /dev/null +++ b/rust/nix-expr/src/eval_state.rs @@ -0,0 +1,265 @@ +use crate::value::{Value, ValueType}; +use anyhow::Context as _; +use anyhow::{bail, Result}; +use lazy_static::lazy_static; +use nix_c_raw as raw; +use nix_store::store::Store; +use nix_util::context::Context; +use std::ffi::CString; +use std::ptr::null_mut; +use std::ptr::NonNull; + +lazy_static! { + static ref INIT: Result<()> = { + unsafe { + raw::GC_allow_register_threads(); + } + let context: Context = Context::new(); + unsafe { + raw::nix_libexpr_init(context.ptr()); + } + context.check_err()?; + Ok(()) + }; +} +pub fn init() -> Result<()> { + let x = INIT.as_ref(); + match x { + Ok(_) => Ok(()), + Err(e) => { + // Couldn't just clone the error, so we have to print it here. + Err(anyhow::format_err!("nix_libstore_init error: {}", e)) + } + } +} + +pub struct EvalState { + eval_state: NonNull, + store: Store, + context: Context, +} +impl EvalState { + pub fn new(store: Store) -> Result { + let context = Context::new(); + + init()?; + + let eval_state = unsafe { + raw::nix_state_create( + context.ptr(), + /* searchPath */ null_mut(), + store.raw_ptr(), + ) + }; + if eval_state.is_null() { + bail!("nix_state_create returned a null pointer"); + } + Ok(EvalState { + eval_state: NonNull::new(eval_state).unwrap(), + store, + context, + }) + } + pub fn raw_ptr(&self) -> *mut raw::EvalState { + self.eval_state.as_ptr() + } + pub fn store(&self) -> &Store { + &self.store + } + pub fn eval_from_string(&self, expr: String, path: String) -> Result { + let expr_ptr = + CString::new(expr).with_context(|| "eval_from_string: expr contains null byte")?; + let path_ptr = + CString::new(path).with_context(|| "eval_from_string: path contains null byte")?; + let value = self.new_value_uninitialized(); + unsafe { + let ctx_ptr = self.context.ptr(); + raw::nix_expr_eval_from_string( + ctx_ptr, + self.raw_ptr(), + expr_ptr.as_ptr(), + path_ptr.as_ptr(), + value.raw_ptr(), + ); + }; + self.context.check_err()?; + Ok(value) + } + /** Try turn any Value into a Value that isn't a Thunk. */ + pub fn force(&self, v: &Value) -> Result<()> { + unsafe { + raw::nix_value_force(self.context.ptr(), self.raw_ptr(), v.raw_ptr()); + } + self.context.check_err() + } + pub fn value_is_thunk(&self, value: &Value) -> bool { + let r = unsafe { + raw::nix_get_type(self.context.ptr(), value.raw_ptr()) == raw::ValueType_NIX_TYPE_THUNK + }; + self.context.check_err().unwrap(); + r + } + pub fn value_type(&self, value: &Value) -> Result { + if self.value_is_thunk(value) { + self.force(value)?; + } + let r = unsafe { raw::nix_get_type(self.context.ptr(), value.raw_ptr()) }; + Ok(ValueType::from_raw(r)) + } + + fn new_value_uninitialized(&self) -> Value { + let value = unsafe { raw::nix_alloc_value(self.context.ptr(), self.raw_ptr()) }; + Value::new(value) + } +} + +pub fn gc_now() { + unsafe { + raw::nix_gc_now(); + } +} + +/** Run a function while making sure that the current thread is registered with the GC. */ +pub fn gc_registering_current_thread(f: F) -> Result +where + F: FnOnce() -> R, +{ + init()?; + if unsafe { raw::GC_thread_is_registered() } != 0 { + return Ok(f()); + } else { + gc_register_my_thread().unwrap(); + let r = f(); + unsafe { + raw::GC_unregister_my_thread(); + } + return Ok(r); + } +} + +pub fn gc_register_my_thread() -> Result<()> { + unsafe { + let already_done = raw::GC_thread_is_registered(); + if already_done != 0 { + return Ok(()); + } + let mut sb: raw::GC_stack_base = raw::GC_stack_base { + mem_base: 0 as *mut _, + }; + let r = raw::GC_get_stack_base(&mut sb); + if r as u32 != raw::GC_SUCCESS { + Err(anyhow::format_err!("GC_get_stack_base failed: {}", r))?; + } + raw::GC_register_my_thread(&sb); + Ok(()) + } +} + +impl Drop for EvalState { + fn drop(&mut self) { + unsafe { + raw::nix_state_free(self.raw_ptr()); + } + } +} + +#[cfg(test)] +mod tests { + use ctor::ctor; + + use super::*; + + #[ctor] + fn setup() { + init().unwrap(); + } + + #[test] + fn eval_state_new_and_drop() { + gc_registering_current_thread(|| { + // very basic test: make sure initialization doesn't crash + let store = Store::open("auto").unwrap(); + let _e = EvalState::new(store).unwrap(); + }) + .unwrap(); + } + + #[test] + fn eval_state_eval_from_string() { + gc_registering_current_thread(|| { + let store = Store::open("auto").unwrap(); + let es = EvalState::new(store).unwrap(); + let v = es + .eval_from_string("1".to_string(), "".to_string()) + .unwrap(); + let v2 = v.clone(); + es.force(&v).unwrap(); + let t = es.value_type(&v).unwrap(); + assert!(t == ValueType::Int); + let t2 = es.value_type(&v2).unwrap(); + assert!(t2 == ValueType::Int); + gc_now(); + }) + .unwrap(); + } + + #[test] + fn eval_state_value_bool() { + gc_registering_current_thread(|| { + let store = Store::open("auto").unwrap(); + let es = EvalState::new(store).unwrap(); + let v = es + .eval_from_string("true".to_string(), "".to_string()) + .unwrap(); + es.force(&v).unwrap(); + let t = es.value_type(&v).unwrap(); + assert!(t == ValueType::Bool); + }) + .unwrap(); + } + + #[test] + fn eval_state_value_string() { + gc_registering_current_thread(|| { + let store = Store::open("auto").unwrap(); + let es = EvalState::new(store).unwrap(); + let v = es + .eval_from_string("\"hello\"".to_string(), "".to_string()) + .unwrap(); + es.force(&v).unwrap(); + let t = es.value_type(&v).unwrap(); + assert!(t == ValueType::String); + }) + .unwrap(); + } + + #[test] + fn eval_state_value_attrset() { + gc_registering_current_thread(|| { + let store = Store::open("auto").unwrap(); + let es = EvalState::new(store).unwrap(); + let v = es + .eval_from_string("{ }".to_string(), "".to_string()) + .unwrap(); + es.force(&v).unwrap(); + let t = es.value_type(&v).unwrap(); + assert!(t == ValueType::AttrSet); + }) + .unwrap(); + } + + #[test] + fn eval_state_value_list() { + gc_registering_current_thread(|| { + let store = Store::open("auto").unwrap(); + let es = EvalState::new(store).unwrap(); + let v = es + .eval_from_string("[ ]".to_string(), "".to_string()) + .unwrap(); + es.force(&v).unwrap(); + let t = es.value_type(&v).unwrap(); + assert!(t == ValueType::List); + }) + .unwrap(); + } +} diff --git a/rust/nix-expr/src/lib.rs b/rust/nix-expr/src/lib.rs new file mode 100644 index 0000000..ead2024 --- /dev/null +++ b/rust/nix-expr/src/lib.rs @@ -0,0 +1,2 @@ +pub mod eval_state; +pub mod value; diff --git a/rust/nix-expr/src/value.rs b/rust/nix-expr/src/value.rs new file mode 100644 index 0000000..cbe1164 --- /dev/null +++ b/rust/nix-expr/src/value.rs @@ -0,0 +1,75 @@ +use nix_c_raw as raw; +use nix_util::context::Context; +use std::ptr::NonNull; + +// TODO: test: cloning a thunk does not duplicate the evaluation. + +/** The type of a value (or thunk) */ +#[derive(Eq, PartialEq)] +pub enum ValueType { + AttrSet, + Bool, + External, + Float, + Function, + Int, + List, + Null, + Path, + String, + Thunk, + Unknown, +} + +impl ValueType { + pub(crate) fn from_raw(raw: raw::ValueType) -> ValueType { + match raw { + raw::ValueType_NIX_TYPE_ATTRS => ValueType::AttrSet, + raw::ValueType_NIX_TYPE_BOOL => ValueType::Bool, + raw::ValueType_NIX_TYPE_EXTERNAL => ValueType::External, + raw::ValueType_NIX_TYPE_FLOAT => ValueType::Float, + raw::ValueType_NIX_TYPE_FUNCTION => ValueType::Function, + raw::ValueType_NIX_TYPE_INT => ValueType::Int, + raw::ValueType_NIX_TYPE_LIST => ValueType::List, + raw::ValueType_NIX_TYPE_NULL => ValueType::Null, + raw::ValueType_NIX_TYPE_PATH => ValueType::Path, + raw::ValueType_NIX_TYPE_STRING => ValueType::String, + raw::ValueType_NIX_TYPE_THUNK => ValueType::Thunk, + _ => ValueType::Unknown, + } + } +} + +/* A pointer to a value or thunk, to be used with EvalState methods. */ +pub struct Value { + inner: NonNull, +} +impl Value { + pub(crate) fn new(inner: *mut raw::Value) -> Self { + Value { + inner: NonNull::new(inner).unwrap(), + } + } + pub(crate) fn raw_ptr(&self) -> *mut raw::Value { + self.inner.as_ptr() + } +} +impl Drop for Value { + fn drop(&mut self) { + let context = Context::new(); + unsafe { + raw::nix_gc_decref(context.ptr(), self.inner.as_ptr()); + } + // ignore error from context, because drop should not panic + } +} +impl Clone for Value { + fn clone(&self) -> Self { + let context = Context::new(); + unsafe { raw::nix_gc_incref(context.ptr(), self.inner.as_ptr()) }; + context.check_err().unwrap(); + Value { inner: self.inner } + } +} + +// Tested in eval_state.rs diff --git a/rust/nix-store/src/store.rs b/rust/nix-store/src/store.rs index 8650dcc..c1d274c 100644 --- a/rust/nix-store/src/store.rs +++ b/rust/nix-store/src/store.rs @@ -29,7 +29,7 @@ impl StoreRef { impl Drop for StoreRef { fn drop(&mut self) { unsafe { - raw::nix_store_unref(self.inner.as_ptr()); + raw::nix_store_free(self.inner.as_ptr()); } } } @@ -73,6 +73,10 @@ impl Store { Ok(store) } + pub fn raw_ptr(&self) -> *mut raw::Store { + self.inner.ptr() + } + pub fn get_uri(&self) -> Result { const N: usize = 1024; let mut buffer: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() };