#![cfg(feature = "expr")]
use std::{ffi::CString, path::Path, ptr::NonNull, sync::Arc};
use crate::{
Context,
Error,
Result,
Store,
StorePath,
Value,
context::is_pure_eval,
error::check_err,
sys,
};
pub struct EvalStateBuilder {
inner: NonNull<sys::nix_eval_state_builder>,
store: Arc<Store>,
context: Arc<Context>,
skip_load: bool,
}
impl EvalStateBuilder {
pub fn new(store: &Arc<Store>) -> Result<Self> {
let builder_ptr = unsafe {
sys::nix_eval_state_builder_new(store._context.as_ptr(), store.as_ptr())
};
let inner = NonNull::new(builder_ptr).ok_or(Error::NullPointer)?;
Ok(EvalStateBuilder {
inner,
store: Arc::clone(store),
context: Arc::clone(&store._context),
skip_load: false,
})
}
pub fn set_lookup_path(self, paths: &[impl AsRef<str>]) -> Result<Self> {
let c_strings: Vec<CString> = paths
.iter()
.map(|s| CString::new(s.as_ref()))
.collect::<std::result::Result<_, _>>()?;
let mut ptrs: Vec<*const std::os::raw::c_char> =
c_strings.iter().map(|cs| cs.as_ptr()).collect();
ptrs.push(std::ptr::null());
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_eval_state_builder_set_lookup_path(
self.context.as_ptr(),
self.inner.as_ptr(),
ptrs.as_mut_ptr(),
),
)?;
}
Ok(self)
}
#[cfg(feature = "flake")]
pub fn with_flake_settings(
self,
settings: &crate::flake::FlakeSettings,
) -> Result<Self> {
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_flake_settings_add_to_eval_state_builder(
self.context.as_ptr(),
settings.as_ptr(),
self.inner.as_ptr(),
),
)?;
}
Ok(self)
}
#[must_use]
pub fn no_load_config(mut self) -> Self {
self.skip_load = true;
self
}
pub fn build(self) -> Result<EvalState> {
if !self.skip_load {
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_eval_state_builder_load(
self.context.as_ptr(),
self.inner.as_ptr(),
),
)?;
}
}
let state_ptr = unsafe {
sys::nix_eval_state_build(self.context.as_ptr(), self.inner.as_ptr())
};
let inner = NonNull::new(state_ptr).ok_or(Error::NullPointer)?;
Ok(EvalState {
inner,
store: self.store.clone(),
context: self.context.clone(),
})
}
}
impl Drop for EvalStateBuilder {
fn drop(&mut self) {
unsafe {
sys::nix_eval_state_builder_free(self.inner.as_ptr());
}
}
}
pub struct EvalState {
pub(crate) inner: NonNull<sys::EvalState>,
#[expect(dead_code, reason = "keeps the Arc<Store> alive Drop side-effects")]
store: Arc<Store>,
pub(crate) context: Arc<Context>,
}
impl EvalState {
pub fn eval_from_string(&self, expr: &str, path: &str) -> Result<Value<'_>> {
let expr_c = CString::new(expr)?;
let path_c = CString::new(path)?;
let value_ptr = unsafe {
sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr())
};
if value_ptr.is_null() {
return Err(Error::NullPointer);
}
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_expr_eval_from_string(
self.context.as_ptr(),
self.inner.as_ptr(),
expr_c.as_ptr(),
path_c.as_ptr(),
value_ptr,
),
)?;
}
let inner = NonNull::new(value_ptr).ok_or(Error::NullPointer)?;
Ok(Value { inner, state: self })
}
pub fn eval_from_file(&self, path: impl AsRef<Path>) -> Result<Value<'_>> {
let path = path.as_ref();
let expr = std::fs::read_to_string(path).map_err(|e| {
Error::Unknown(format!("Failed to read file {}: {e}", path.display()))
})?;
let base_path = path.parent().unwrap_or_else(|| Path::new("."));
let base_str = base_path.to_string_lossy();
self.eval_from_string(&expr, &base_str)
}
pub fn alloc_value(&self) -> Result<Value<'_>> {
let value_ptr = unsafe {
sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr())
};
let inner = NonNull::new(value_ptr).ok_or(Error::NullPointer)?;
Ok(Value { inner, state: self })
}
pub fn make_int(&self, i: i64) -> Result<Value<'_>> {
let v = self.alloc_value()?;
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_init_int(self.context.as_ptr(), v.inner.as_ptr(), i),
)?;
}
Ok(v)
}
pub fn make_float(&self, f: f64) -> Result<Value<'_>> {
let v = self.alloc_value()?;
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_init_float(self.context.as_ptr(), v.inner.as_ptr(), f),
)?;
}
Ok(v)
}
pub fn make_bool(&self, b: bool) -> Result<Value<'_>> {
let v = self.alloc_value()?;
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_init_bool(self.context.as_ptr(), v.inner.as_ptr(), b),
)?;
}
Ok(v)
}
pub fn make_null(&self) -> Result<Value<'_>> {
let v = self.alloc_value()?;
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_init_null(self.context.as_ptr(), v.inner.as_ptr()),
)?;
}
Ok(v)
}
pub fn make_string(&self, s: &str) -> Result<Value<'_>> {
let v = self.alloc_value()?;
let s_c = CString::new(s)?;
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_init_string(
self.context.as_ptr(),
v.inner.as_ptr(),
s_c.as_ptr(),
),
)?;
}
Ok(v)
}
pub fn make_path(&self, path: impl AsRef<Path>) -> Result<Value<'_>> {
let v = self.alloc_value()?;
let path_str = path
.as_ref()
.to_str()
.ok_or_else(|| Error::Unknown("Path is not valid UTF-8".to_string()))?;
let path_c = CString::new(path_str)?;
#[cfg(feature = "shim")]
if path.as_ref().is_absolute()
&& path_str.starts_with("/nix/store/")
&& is_pure_eval()
{
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_eval_state_allow_path(
self.context.as_ptr(),
self.inner.as_ptr(),
path_c.as_ptr(),
),
)?;
}
}
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_init_path_string(
self.context.as_ptr(),
self.inner.as_ptr(),
v.inner.as_ptr(),
path_c.as_ptr(),
),
)?;
}
Ok(v)
}
pub fn make_list(&self, items: &[&Value<'_>]) -> Result<Value<'_>> {
let builder = unsafe {
sys::nix_make_list_builder(
self.context.as_ptr(),
self.inner.as_ptr(),
items.len(),
)
};
if builder.is_null() {
return Err(Error::NullPointer);
}
struct ListBuilderGuard(*mut sys::ListBuilder);
impl Drop for ListBuilderGuard {
fn drop(&mut self) {
unsafe { sys::nix_list_builder_free(self.0) };
}
}
let _guard = ListBuilderGuard(builder);
for (i, item) in items.iter().enumerate() {
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_list_builder_insert(
self.context.as_ptr(),
builder,
i as std::os::raw::c_uint,
item.inner.as_ptr(),
),
)?;
}
}
let result = self.alloc_value()?;
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_make_list(
self.context.as_ptr(),
builder,
result.inner.as_ptr(),
),
)?;
}
Ok(result)
}
pub fn make_attrs<'s>(
&'s self,
pairs: &[(&str, &Value<'_>)],
) -> Result<Value<'s>> {
let builder = unsafe {
sys::nix_make_bindings_builder(
self.context.as_ptr(),
self.inner.as_ptr(),
pairs.len(),
)
};
if builder.is_null() {
return Err(Error::NullPointer);
}
struct BindingsBuilderGuard(*mut sys::BindingsBuilder);
impl Drop for BindingsBuilderGuard {
fn drop(&mut self) {
unsafe { sys::nix_bindings_builder_free(self.0) };
}
}
let _guard = BindingsBuilderGuard(builder);
for (key, value) in pairs {
let key_c = CString::new(*key)?;
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_bindings_builder_insert(
self.context.as_ptr(),
builder,
key_c.as_ptr(),
value.inner.as_ptr(),
),
)?;
}
}
let result = self.alloc_value()?;
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_make_attrs(
self.context.as_ptr(),
result.inner.as_ptr(),
builder,
),
)?;
}
Ok(result)
}
#[cfg(feature = "shim")]
pub fn get_derivation(&self, value: &Value<'_>) -> Result<Option<StorePath>> {
let path_ptr = unsafe {
sys::nix_get_derivation(
self.context.as_ptr(),
self.inner.as_ptr(),
value.inner.as_ptr(),
false,
)
};
if path_ptr.is_null() {
unsafe {
let ctx = self.context.as_ptr();
let code = sys::nix_err_code(ctx);
check_err(ctx, code)?;
}
return Ok(None);
}
let inner = NonNull::new(path_ptr).ok_or(Error::NullPointer)?;
Ok(Some(StorePath {
inner,
_context: Arc::clone(&self.context),
}))
}
#[cfg(feature = "shim")]
pub fn auto_call_function<'s>(
&'s self,
auto_args: Option<&Value<'_>>,
fn_val: &Value<'_>,
) -> Result<Value<'s>> {
let result = self.alloc_value()?;
let auto_args_ptr =
auto_args.map_or(std::ptr::null_mut(), |v| v.inner.as_ptr());
unsafe {
check_err(
self.context.as_ptr(),
sys::nix_value_auto_call_function(
self.context.as_ptr(),
self.inner.as_ptr(),
auto_args_ptr,
fn_val.inner.as_ptr(),
result.inner.as_ptr(),
),
)?;
}
Ok(result)
}
pub(crate) unsafe fn as_ptr(&self) -> *mut sys::EvalState {
self.inner.as_ptr()
}
}
impl Drop for EvalState {
fn drop(&mut self) {
unsafe {
sys::nix_state_free(self.inner.as_ptr());
}
}
}
unsafe impl Send for EvalState {}