Skip to main content

nix_bindings/
eval.rs

1//! [`EvalState`] and [`EvalStateBuilder`]: the Nix evaluator handle and
2//! its configuration.
3
4#![cfg(feature = "expr")]
5
6use std::{ffi::CString, path::Path, ptr::NonNull, sync::Arc};
7
8use crate::{
9  Context,
10  Error,
11  Result,
12  Store,
13  Value,
14  context::is_pure_eval,
15  error::check_err,
16  sys,
17};
18
19/// Builder for Nix evaluation state.
20///
21/// This allows configuring the evaluation environment before creating
22/// the evaluation state.
23pub struct EvalStateBuilder {
24  inner:     NonNull<sys::nix_eval_state_builder>,
25  store:     Arc<Store>,
26  context:   Arc<Context>,
27  skip_load: bool,
28}
29
30impl EvalStateBuilder {
31  /// Create a new evaluation state builder.
32  ///
33  /// # Arguments
34  ///
35  /// * `store` - The Nix store to use for evaluation
36  ///
37  /// # Errors
38  ///
39  /// Returns an error if the builder cannot be created.
40  pub fn new(store: &Arc<Store>) -> Result<Self> {
41    // SAFETY: store context and store are valid
42    let builder_ptr = unsafe {
43      sys::nix_eval_state_builder_new(store._context.as_ptr(), store.as_ptr())
44    };
45
46    let inner = NonNull::new(builder_ptr).ok_or(Error::NullPointer)?;
47
48    Ok(EvalStateBuilder {
49      inner,
50      store: Arc::clone(store),
51      context: Arc::clone(&store._context),
52      skip_load: false,
53    })
54  }
55
56  /// Set the lookup path (`NIX_PATH`) for `<...>` expressions.
57  ///
58  /// Each entry should be in the form `"name=path"` or just `"path"`,
59  /// matching the format of `NIX_PATH` entries.
60  ///
61  /// # Errors
62  ///
63  /// Returns an error if the lookup path cannot be set.
64  pub fn set_lookup_path(self, paths: &[impl AsRef<str>]) -> Result<Self> {
65    let c_strings: Vec<CString> = paths
66      .iter()
67      .map(|s| CString::new(s.as_ref()))
68      .collect::<std::result::Result<_, _>>()?;
69
70    let mut ptrs: Vec<*const std::os::raw::c_char> =
71      c_strings.iter().map(|cs| cs.as_ptr()).collect();
72    ptrs.push(std::ptr::null());
73
74    // SAFETY: context and builder are valid, ptrs is null-terminated
75    unsafe {
76      check_err(
77        self.context.as_ptr(),
78        sys::nix_eval_state_builder_set_lookup_path(
79          self.context.as_ptr(),
80          self.inner.as_ptr(),
81          ptrs.as_mut_ptr(),
82        ),
83      )?;
84    }
85
86    Ok(self)
87  }
88
89  /// Apply flake settings to the evaluation state builder.
90  ///
91  /// This enables `builtins.getFlake` and related flake functionality
92  /// in the resulting [`EvalState`].
93  ///
94  /// # Errors
95  ///
96  /// Returns an error if the flake settings cannot be applied.
97  #[cfg(feature = "flake")]
98  pub fn with_flake_settings(
99    self,
100    settings: &crate::flake::FlakeSettings,
101  ) -> Result<Self> {
102    // SAFETY: context, settings, and builder are valid
103    unsafe {
104      check_err(
105        self.context.as_ptr(),
106        sys::nix_flake_settings_add_to_eval_state_builder(
107          self.context.as_ptr(),
108          settings.as_ptr(),
109          self.inner.as_ptr(),
110        ),
111      )?;
112    }
113
114    Ok(self)
115  }
116
117  /// Skip loading Nix configuration from the environment.
118  ///
119  /// By default [`build`](Self::build) calls `nix_eval_state_builder_load` to
120  /// read configuration from environment variables and config files. Call
121  /// this method to skip that step, which is useful in tests or sandboxed
122  /// environments.
123  #[must_use]
124  pub fn no_load_config(mut self) -> Self {
125    self.skip_load = true;
126    self
127  }
128
129  /// Build the evaluation state.
130  ///
131  /// # Errors
132  ///
133  /// Returns an error if the evaluation state cannot be built.
134  pub fn build(self) -> Result<EvalState> {
135    if !self.skip_load {
136      // SAFETY: context and builder are valid
137      unsafe {
138        check_err(
139          self.context.as_ptr(),
140          sys::nix_eval_state_builder_load(
141            self.context.as_ptr(),
142            self.inner.as_ptr(),
143          ),
144        )?;
145      }
146    }
147
148    // SAFETY: context and builder are valid
149    let state_ptr = unsafe {
150      sys::nix_eval_state_build(self.context.as_ptr(), self.inner.as_ptr())
151    };
152
153    let inner = NonNull::new(state_ptr).ok_or(Error::NullPointer)?;
154
155    Ok(EvalState {
156      inner,
157      store: self.store.clone(),
158      context: self.context.clone(),
159    })
160  }
161}
162
163impl Drop for EvalStateBuilder {
164  fn drop(&mut self) {
165    // SAFETY: We own the builder and it's valid until drop
166    unsafe {
167      sys::nix_eval_state_builder_free(self.inner.as_ptr());
168    }
169  }
170}
171
172/// Nix evaluation state for evaluating expressions.
173///
174/// This provides the main interface for evaluating Nix expressions
175/// and creating values.
176pub struct EvalState {
177  pub(crate) inner:   NonNull<sys::EvalState>,
178  #[expect(dead_code, reason = "keeps the Arc<Store> alive Drop side-effects")]
179  store:              Arc<Store>,
180  pub(crate) context: Arc<Context>,
181}
182
183impl EvalState {
184  /// Evaluate a Nix expression from a string.
185  ///
186  /// # Arguments
187  ///
188  /// * `expr` - The Nix expression to evaluate
189  /// * `path` - The path to use for error reporting (e.g., `"<eval>"`)
190  ///
191  /// # Errors
192  ///
193  /// Returns an error if evaluation fails.
194  pub fn eval_from_string(&self, expr: &str, path: &str) -> Result<Value<'_>> {
195    let expr_c = CString::new(expr)?;
196    let path_c = CString::new(path)?;
197
198    // SAFETY: context and state are valid
199    let value_ptr = unsafe {
200      sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr())
201    };
202    if value_ptr.is_null() {
203      return Err(Error::NullPointer);
204    }
205
206    // SAFETY: all pointers are valid
207    unsafe {
208      check_err(
209        self.context.as_ptr(),
210        sys::nix_expr_eval_from_string(
211          self.context.as_ptr(),
212          self.inner.as_ptr(),
213          expr_c.as_ptr(),
214          path_c.as_ptr(),
215          value_ptr,
216        ),
217      )?;
218    }
219
220    let inner = NonNull::new(value_ptr).ok_or(Error::NullPointer)?;
221
222    Ok(Value { inner, state: self })
223  }
224
225  /// Evaluate a Nix expression from a file.
226  ///
227  /// Reads the file at `path` as UTF-8, then evaluates its contents using the
228  /// parent directory as the base path for relative imports. The base path is
229  /// passed to Nix as a UTF-8 string; non-UTF-8 components are replaced
230  /// lossily for the error-reporting label only.
231  ///
232  /// # Errors
233  ///
234  /// Returns an error if the file cannot be read as UTF-8 or if evaluation
235  /// fails.
236  pub fn eval_from_file(&self, path: impl AsRef<Path>) -> Result<Value<'_>> {
237    let path = path.as_ref();
238    let expr = std::fs::read_to_string(path).map_err(|e| {
239      Error::Unknown(format!("Failed to read file {}: {e}", path.display()))
240    })?;
241    let base_path = path.parent().unwrap_or_else(|| Path::new("."));
242    let base_str = base_path.to_string_lossy();
243    self.eval_from_string(&expr, &base_str)
244  }
245
246  /// Allocate a new uninitialized value.
247  ///
248  /// # Errors
249  ///
250  /// Returns an error if value allocation fails.
251  pub fn alloc_value(&self) -> Result<Value<'_>> {
252    // SAFETY: context and state are valid
253    let value_ptr = unsafe {
254      sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr())
255    };
256    let inner = NonNull::new(value_ptr).ok_or(Error::NullPointer)?;
257
258    Ok(Value { inner, state: self })
259  }
260
261  /// Create a Nix integer value.
262  ///
263  /// # Errors
264  ///
265  /// Returns an error if value allocation or initialization fails.
266  pub fn make_int(&self, i: i64) -> Result<Value<'_>> {
267    let v = self.alloc_value()?;
268    // SAFETY: context and value are valid
269    unsafe {
270      check_err(
271        self.context.as_ptr(),
272        sys::nix_init_int(self.context.as_ptr(), v.inner.as_ptr(), i),
273      )?;
274    }
275    Ok(v)
276  }
277
278  /// Create a Nix float value.
279  ///
280  /// # Errors
281  ///
282  /// Returns an error if value allocation or initialization fails.
283  pub fn make_float(&self, f: f64) -> Result<Value<'_>> {
284    let v = self.alloc_value()?;
285    // SAFETY: context and value are valid
286    unsafe {
287      check_err(
288        self.context.as_ptr(),
289        sys::nix_init_float(self.context.as_ptr(), v.inner.as_ptr(), f),
290      )?;
291    }
292    Ok(v)
293  }
294
295  /// Create a Nix boolean value.
296  ///
297  /// # Errors
298  ///
299  /// Returns an error if value allocation or initialization fails.
300  pub fn make_bool(&self, b: bool) -> Result<Value<'_>> {
301    let v = self.alloc_value()?;
302    // SAFETY: context and value are valid
303    unsafe {
304      check_err(
305        self.context.as_ptr(),
306        sys::nix_init_bool(self.context.as_ptr(), v.inner.as_ptr(), b),
307      )?;
308    }
309    Ok(v)
310  }
311
312  /// Create a Nix null value.
313  ///
314  /// # Errors
315  ///
316  /// Returns an error if value allocation or initialization fails.
317  pub fn make_null(&self) -> Result<Value<'_>> {
318    let v = self.alloc_value()?;
319    // SAFETY: context and value are valid
320    unsafe {
321      check_err(
322        self.context.as_ptr(),
323        sys::nix_init_null(self.context.as_ptr(), v.inner.as_ptr()),
324      )?;
325    }
326    Ok(v)
327  }
328
329  /// Create a Nix string value.
330  ///
331  /// # Errors
332  ///
333  /// Returns an error if value allocation, string conversion, or
334  /// initialization fails.
335  pub fn make_string(&self, s: &str) -> Result<Value<'_>> {
336    let v = self.alloc_value()?;
337    let s_c = CString::new(s)?;
338    // SAFETY: context and value are valid
339    unsafe {
340      check_err(
341        self.context.as_ptr(),
342        sys::nix_init_string(
343          self.context.as_ptr(),
344          v.inner.as_ptr(),
345          s_c.as_ptr(),
346        ),
347      )?;
348    }
349    Ok(v)
350  }
351
352  /// Create a Nix path value.
353  ///
354  /// # Pure Evaluation
355  ///
356  /// In pure-eval mode (`--pure-eval`) the Nix evaluator wraps the
357  /// filesystem in an `AllowListSourceAccessor` that rejects any
358  /// unregistered absolute path. When the `shim` feature is enabled this
359  /// method automatically registers absolute paths via the shim's
360  /// `nix_eval_state_allow_path` before constructing the value, mirroring
361  /// what Nix's own fetch builtins do. Without `shim` you must arrange
362  /// for allowPath yourself, or [`is_pure_eval`] returns true.
363  ///
364  /// # Errors
365  ///
366  /// Returns an error if value allocation, path conversion, or
367  /// initialization fails.
368  pub fn make_path(&self, path: impl AsRef<Path>) -> Result<Value<'_>> {
369    let v = self.alloc_value()?;
370    let path_str = path
371      .as_ref()
372      .to_str()
373      .ok_or_else(|| Error::Unknown("Path is not valid UTF-8".to_string()))?;
374    let path_c = CString::new(path_str)?;
375
376    // See make_path note in old lib.rs for why we restrict auto-allow
377    // to /nix/store/ paths.
378    #[cfg(feature = "shim")]
379    if path.as_ref().is_absolute()
380      && path_str.starts_with("/nix/store/")
381      && is_pure_eval()
382    {
383      // SAFETY: context, state, and path are valid.
384      unsafe {
385        check_err(
386          self.context.as_ptr(),
387          sys::nix_eval_state_allow_path(
388            self.context.as_ptr(),
389            self.inner.as_ptr(),
390            path_c.as_ptr(),
391          ),
392        )?;
393      }
394    }
395
396    // SAFETY: context, state, and value are valid
397    unsafe {
398      check_err(
399        self.context.as_ptr(),
400        sys::nix_init_path_string(
401          self.context.as_ptr(),
402          self.inner.as_ptr(),
403          v.inner.as_ptr(),
404          path_c.as_ptr(),
405        ),
406      )?;
407    }
408    Ok(v)
409  }
410
411  /// Create a Nix list value from a slice of values.
412  ///
413  /// # Errors
414  ///
415  /// Returns an error if value allocation or list construction fails.
416  pub fn make_list(&self, items: &[&Value<'_>]) -> Result<Value<'_>> {
417    // SAFETY: context and state are valid
418    let builder = unsafe {
419      sys::nix_make_list_builder(
420        self.context.as_ptr(),
421        self.inner.as_ptr(),
422        items.len(),
423      )
424    };
425    if builder.is_null() {
426      return Err(Error::NullPointer);
427    }
428
429    struct ListBuilderGuard(*mut sys::ListBuilder);
430    impl Drop for ListBuilderGuard {
431      fn drop(&mut self) {
432        unsafe { sys::nix_list_builder_free(self.0) };
433      }
434    }
435    let _guard = ListBuilderGuard(builder);
436
437    for (i, item) in items.iter().enumerate() {
438      // SAFETY: context, builder, and value are valid; index in bounds
439      unsafe {
440        check_err(
441          self.context.as_ptr(),
442          sys::nix_list_builder_insert(
443            self.context.as_ptr(),
444            builder,
445            i as std::os::raw::c_uint,
446            item.inner.as_ptr(),
447          ),
448        )?;
449      }
450    }
451
452    let result = self.alloc_value()?;
453    // SAFETY: context, builder, and result value are valid
454    unsafe {
455      check_err(
456        self.context.as_ptr(),
457        sys::nix_make_list(
458          self.context.as_ptr(),
459          builder,
460          result.inner.as_ptr(),
461        ),
462      )?;
463    }
464
465    Ok(result)
466  }
467
468  /// Create a Nix attribute set from key-value pairs.
469  ///
470  /// # Errors
471  ///
472  /// Returns an error if value allocation or attribute set construction
473  /// fails.
474  pub fn make_attrs<'s>(
475    &'s self,
476    pairs: &[(&str, &Value<'_>)],
477  ) -> Result<Value<'s>> {
478    // SAFETY: context and state are valid
479    let builder = unsafe {
480      sys::nix_make_bindings_builder(
481        self.context.as_ptr(),
482        self.inner.as_ptr(),
483        pairs.len(),
484      )
485    };
486    if builder.is_null() {
487      return Err(Error::NullPointer);
488    }
489
490    struct BindingsBuilderGuard(*mut sys::BindingsBuilder);
491    impl Drop for BindingsBuilderGuard {
492      fn drop(&mut self) {
493        unsafe { sys::nix_bindings_builder_free(self.0) };
494      }
495    }
496    let _guard = BindingsBuilderGuard(builder);
497
498    for (key, value) in pairs {
499      let key_c = CString::new(*key)?;
500      // SAFETY: context, builder, key, and value are valid
501      unsafe {
502        check_err(
503          self.context.as_ptr(),
504          sys::nix_bindings_builder_insert(
505            self.context.as_ptr(),
506            builder,
507            key_c.as_ptr(),
508            value.inner.as_ptr(),
509          ),
510        )?;
511      }
512    }
513
514    let result = self.alloc_value()?;
515    // SAFETY: context, builder, and result value are valid
516    unsafe {
517      check_err(
518        self.context.as_ptr(),
519        sys::nix_make_attrs(
520          self.context.as_ptr(),
521          result.inner.as_ptr(),
522          builder,
523        ),
524      )?;
525    }
526
527    Ok(result)
528  }
529
530  /// Get the raw state pointer.
531  ///
532  /// # Safety
533  ///
534  /// The caller must ensure the pointer is used safely.
535  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::EvalState {
536    self.inner.as_ptr()
537  }
538}
539
540impl Drop for EvalState {
541  fn drop(&mut self) {
542    // SAFETY: We own the state and it's valid until drop
543    unsafe {
544      sys::nix_state_free(self.inner.as_ptr());
545    }
546  }
547}
548
549// SAFETY: see crate-level "# Thread Safety" docs and the comment in
550// the original lib.rs Send impl.
551unsafe impl Send for EvalState {}