Skip to main content

nix_bindings/
flake.rs

1//! Nix flake support.
2//!
3//! Types:
4//!
5//! - [`FlakeSettings`]: global flake configuration; pass to
6//!   [`EvalStateBuilder::with_flake_settings`](crate::EvalStateBuilder::with_flake_settings).
7//! - [`FetchersSettings`]: fetcher configuration required by
8//!   [`FlakeReference::parse`] and [`LockedFlake::lock`].
9//! - [`FlakeReferenceParseFlags`]: optional flags controlling how a flake
10//!   reference string is parsed.
11//! - [`LockFlags`]: controls locking behaviour (check, virtual,
12//!   write-as-needed, input overrides).
13//! - [`FlakeReference`]: an unresolved reference to a flake; produced by
14//!   [`FlakeReference::parse`].
15//! - [`LockedFlake`]: a fully locked flake; produced by [`LockedFlake::lock`].
16//!   Call [`LockedFlake::output_attrs`] to obtain the flake's output attribute
17//!   set.
18
19use std::{ffi::CString, ptr::NonNull, sync::Arc};
20
21use crate::{
22  Context,
23  EvalState,
24  Result,
25  Value,
26  check_err,
27  check_ptr,
28  checked_string_from_callback,
29  sys,
30};
31
32/// Configuration for the Nix flake subsystem.
33///
34/// This enables flake evaluation features in the Nix evaluator (such as
35/// `builtins.getFlake`). Obtain a `FlakeSettings` and pass it to
36/// [`EvalStateBuilder::with_flake_settings`](crate::EvalStateBuilder::with_flake_settings)
37/// before building the [`EvalState`].
38///
39/// # Example
40///
41/// ```no_run
42/// use std::sync::Arc;
43///
44/// use nix_bindings::{Context, EvalStateBuilder, Store, flake::FlakeSettings};
45///
46/// fn main() -> Result<(), Box<dyn std::error::Error>> {
47///   let ctx = Arc::new(Context::new()?);
48///   let store = Arc::new(Store::open(&ctx, None)?);
49///   let flake_settings = FlakeSettings::new(&ctx)?;
50///   let state = EvalStateBuilder::new(&store)?
51///     .with_flake_settings(&flake_settings)?
52///     .build()?;
53///
54///   Ok(())
55/// }
56/// ```
57pub struct FlakeSettings {
58  pub(crate) inner: NonNull<sys::nix_flake_settings>,
59  _context:         Arc<Context>,
60}
61
62impl FlakeSettings {
63  /// Create a new set of flake settings with default values.
64  ///
65  /// # Errors
66  ///
67  /// Returns an error if the underlying allocation fails.
68  pub fn new(context: &Arc<Context>) -> Result<Self> {
69    // SAFETY: context is valid
70    let ptr = unsafe { sys::nix_flake_settings_new(context.as_ptr()) };
71
72    let inner = check_ptr(unsafe { context.as_ptr() }, ptr)?;
73
74    Ok(FlakeSettings {
75      inner,
76      _context: Arc::clone(context),
77    })
78  }
79
80  /// Get the raw flake settings pointer.
81  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_flake_settings {
82    self.inner.as_ptr()
83  }
84}
85
86impl Drop for FlakeSettings {
87  fn drop(&mut self) {
88    // SAFETY: We own the settings and they are valid until drop
89    unsafe {
90      sys::nix_flake_settings_free(self.inner.as_ptr());
91    }
92  }
93}
94
95// SAFETY: `FlakeSettings` owns its `nix_flake_settings*` and uses the
96// `Arc<Context>` purely for lifetime extension. The settings object
97// holds plain configuration values with no thread affinity. `Sync` is
98// NOT implemented: every method that consults the settings goes through
99// `Context`'s racy error buffer.
100unsafe impl Send for FlakeSettings {}
101
102/// Fetcher configuration.
103///
104/// This is required by [`FlakeReference::parse`] and [`LockedFlake::lock`].
105/// Create one with [`FetchersSettings::new`] and keep it alive for the
106/// duration of any flake operations that need it.
107pub struct FetchersSettings {
108  inner:    NonNull<sys::nix_fetchers_settings>,
109  _context: Arc<Context>,
110}
111
112impl FetchersSettings {
113  /// Create new fetcher settings with default values.
114  ///
115  /// # Errors
116  ///
117  /// Returns an error if the underlying allocation fails.
118  pub fn new(context: &Arc<Context>) -> Result<Self> {
119    // SAFETY: context is valid
120    let ptr = unsafe { sys::nix_fetchers_settings_new(context.as_ptr()) };
121    let inner = check_ptr(unsafe { context.as_ptr() }, ptr)?;
122    Ok(FetchersSettings {
123      inner,
124      _context: Arc::clone(context),
125    })
126  }
127
128  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_fetchers_settings {
129    self.inner.as_ptr()
130  }
131}
132
133impl Drop for FetchersSettings {
134  fn drop(&mut self) {
135    // SAFETY: We own the settings and they are valid until drop
136    unsafe {
137      sys::nix_fetchers_settings_free(self.inner.as_ptr());
138    }
139  }
140}
141
142// SAFETY: `FetchersSettings` is an opaque pointer to plain configuration
143// values, kept alive by `Arc<Context>`. The C object has no thread
144// affinity. `Sync` is NOT implemented for the same reason as
145// `FlakeSettings`: any call into it routes through `Context`'s racy
146// error buffer.
147unsafe impl Send for FetchersSettings {}
148
149/// Flags that control how a flake reference string is parsed.
150///
151/// Create one with [`FlakeReferenceParseFlags::new`] then optionally call
152/// [`set_base_directory`](Self::set_base_directory) before passing it to
153/// [`FlakeReference::parse`].
154pub struct FlakeReferenceParseFlags {
155  inner:    NonNull<sys::nix_flake_reference_parse_flags>,
156  _context: Arc<Context>,
157}
158
159impl FlakeReferenceParseFlags {
160  /// Create new parse flags with default values.
161  ///
162  /// # Errors
163  ///
164  /// Returns an error if the underlying allocation fails.
165  pub fn new(
166    context: &Arc<Context>,
167    flake_settings: &FlakeSettings,
168  ) -> Result<Self> {
169    // SAFETY: context and flake_settings are valid
170    let ptr = unsafe {
171      sys::nix_flake_reference_parse_flags_new(
172        context.as_ptr(),
173        flake_settings.as_ptr(),
174      )
175    };
176    let inner = check_ptr(unsafe { context.as_ptr() }, ptr)?;
177    Ok(FlakeReferenceParseFlags {
178      inner,
179      _context: Arc::clone(context),
180    })
181  }
182
183  /// Set the base directory used when resolving relative flake references.
184  ///
185  /// # Errors
186  ///
187  /// Returns an error if the C API call fails.
188  pub fn set_base_directory(self, dir: &str) -> Result<Self> {
189    let bytes = dir.as_bytes();
190    // SAFETY: context, flags, and dir bytes are valid
191    unsafe {
192      check_err(
193        self._context.as_ptr(),
194        sys::nix_flake_reference_parse_flags_set_base_directory(
195          self._context.as_ptr(),
196          self.inner.as_ptr(),
197          bytes.as_ptr().cast(),
198          bytes.len(),
199        ),
200      )?;
201    }
202    Ok(self)
203  }
204
205  pub(crate) unsafe fn as_ptr(
206    &self,
207  ) -> *mut sys::nix_flake_reference_parse_flags {
208    self.inner.as_ptr()
209  }
210}
211
212impl Drop for FlakeReferenceParseFlags {
213  fn drop(&mut self) {
214    // SAFETY: We own the flags and they are valid until drop
215    unsafe {
216      sys::nix_flake_reference_parse_flags_free(self.inner.as_ptr());
217    }
218  }
219}
220
221// SAFETY: `FlakeReferenceParseFlags` is a small flags struct kept alive
222// by `Arc<Context>` plus `Arc<FlakeSettings>`. Both Arcs are themselves
223// `Send` for our types (see their notes). The C object has no thread
224// affinity. `Sync` is NOT implemented: `set_base_directory` mutates
225// state through `Context`'s racy error buffer.
226unsafe impl Send for FlakeReferenceParseFlags {}
227
228/// Lock-file update strategy for [`LockFlags::set_mode`].
229///
230/// The three modes are mutually exclusive at the C-API level. Picking one
231/// writes into the underlying `nix_flake_lock_flags` slot, so chaining
232/// modes is meaningless. Exposing them as an enum makes that obvious at
233/// the call site.
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum LockMode {
236  /// Require the lock file to be up-to-date; fail if it needs updating.
237  Check,
238  /// Update the lock file in memory only; do not write it to disk.
239  Virtual,
240  /// Update and write the lock file to disk if it needs updating.
241  WriteAsNeeded,
242}
243
244/// Flags controlling the lock-file update strategy for [`LockedFlake::lock`].
245///
246/// Holds an Arc to the originating [`FlakeSettings`] so the settings cannot
247/// be dropped while these flags are alive.
248pub struct LockFlags {
249  inner:     NonNull<sys::nix_flake_lock_flags>,
250  _context:  Arc<Context>,
251  _settings: Arc<FlakeSettings>,
252}
253
254impl LockFlags {
255  /// Create new lock flags with default values.
256  ///
257  /// # Errors
258  ///
259  /// Returns an error if the underlying allocation fails.
260  pub fn new(
261    context: &Arc<Context>,
262    flake_settings: &Arc<FlakeSettings>,
263  ) -> Result<Self> {
264    // SAFETY: context and flake_settings are valid
265    let ptr = unsafe {
266      sys::nix_flake_lock_flags_new(context.as_ptr(), flake_settings.as_ptr())
267    };
268    let inner = check_ptr(unsafe { context.as_ptr() }, ptr)?;
269    Ok(LockFlags {
270      inner,
271      _context: Arc::clone(context),
272      _settings: Arc::clone(flake_settings),
273    })
274  }
275
276  /// Set the lock-file update strategy.
277  ///
278  /// # Errors
279  ///
280  /// Returns an error if the C API call fails.
281  pub fn set_mode(self, mode: LockMode) -> Result<Self> {
282    // SAFETY: context and flags are valid
283    unsafe {
284      let err = match mode {
285        LockMode::Check => {
286          sys::nix_flake_lock_flags_set_mode_check(
287            self._context.as_ptr(),
288            self.inner.as_ptr(),
289          )
290        },
291        LockMode::Virtual => {
292          sys::nix_flake_lock_flags_set_mode_virtual(
293            self._context.as_ptr(),
294            self.inner.as_ptr(),
295          )
296        },
297        LockMode::WriteAsNeeded => {
298          sys::nix_flake_lock_flags_set_mode_write_as_needed(
299            self._context.as_ptr(),
300            self.inner.as_ptr(),
301          )
302        },
303      };
304      check_err(self._context.as_ptr(), err)?;
305    }
306    Ok(self)
307  }
308
309  /// Override a specific input with an alternative flake reference.
310  ///
311  /// `input_path` identifies the input (e.g. `"nixpkgs"`).
312  ///
313  /// # Errors
314  ///
315  /// Returns an error if the C API call fails.
316  pub fn add_input_override(
317    self,
318    input_path: &str,
319    flake_ref: &FlakeReference,
320  ) -> Result<Self> {
321    let path_c = CString::new(input_path)?;
322    // SAFETY: context, flags, path_c, and flake_ref are valid
323    unsafe {
324      check_err(
325        self._context.as_ptr(),
326        sys::nix_flake_lock_flags_add_input_override(
327          self._context.as_ptr(),
328          self.inner.as_ptr(),
329          path_c.as_ptr(),
330          flake_ref.inner.as_ptr(),
331        ),
332      )?;
333    }
334    Ok(self)
335  }
336
337  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_flake_lock_flags {
338    self.inner.as_ptr()
339  }
340}
341
342impl Drop for LockFlags {
343  fn drop(&mut self) {
344    // SAFETY: We own the flags and they are valid until drop
345    unsafe {
346      sys::nix_flake_lock_flags_free(self.inner.as_ptr());
347    }
348  }
349}
350
351// SAFETY: `LockFlags` is a small mode-and-overrides struct kept alive
352// by `Arc<Context>` plus `Arc<FlakeSettings>`. Same move-only contract
353// as `FlakeReferenceParseFlags`. `Sync` is NOT implemented:
354// `set_mode` and `add_input_override` mutate through `Context`'s racy
355// error buffer.
356unsafe impl Send for LockFlags {}
357
358/// Callback that collects a string returned from the Nix C API via a pointer
359/// and length pair into an `Option<String>` stored in `user_data`.
360unsafe extern "C" fn collect_fragment_cb(
361  start: *const std::os::raw::c_char,
362  n: std::os::raw::c_uint,
363  user_data: *mut std::os::raw::c_void,
364) {
365  let result = unsafe { &mut *(user_data as *mut Option<String>) };
366  if !start.is_null() {
367    let bytes =
368      unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
369    *result = std::str::from_utf8(bytes).ok().map(|s| s.to_owned());
370  }
371}
372
373/// An unresolved flake reference.
374///
375/// Obtain one via [`FlakeReference::parse`], then pass it to
376/// [`LockedFlake::lock`] (or [`LockFlags::add_input_override`]).
377pub struct FlakeReference {
378  inner:    NonNull<sys::nix_flake_reference>,
379  _context: Arc<Context>,
380}
381
382impl FlakeReference {
383  /// Parse a flake reference string into a [`FlakeReference`].
384  ///
385  /// Returns both the parsed reference and any fragment that followed a `#`
386  /// in the input string. For references without a fragment the second
387  /// element is an empty string.
388  ///
389  /// # Errors
390  ///
391  /// Returns an error if the C API call fails or returns a null pointer.
392  pub fn parse(
393    context: &Arc<Context>,
394    fetch_settings: &FetchersSettings,
395    flake_settings: &FlakeSettings,
396    parse_flags: &FlakeReferenceParseFlags,
397    s: &str,
398  ) -> Result<(Self, String)> {
399    let bytes = s.as_bytes();
400
401    let mut out_ptr: *mut sys::nix_flake_reference = std::ptr::null_mut();
402    let mut fragment: Option<String> = None;
403
404    // SAFETY: all arguments are valid; we capture the fragment via callback
405    let err = unsafe {
406      sys::nix_flake_reference_and_fragment_from_string(
407        context.as_ptr(),
408        fetch_settings.as_ptr(),
409        flake_settings.as_ptr(),
410        parse_flags.as_ptr(),
411        bytes.as_ptr().cast(),
412        bytes.len(),
413        &mut out_ptr as *mut *mut sys::nix_flake_reference,
414        Some(collect_fragment_cb),
415        &mut fragment as *mut Option<String> as *mut std::os::raw::c_void,
416      )
417    };
418
419    check_err(unsafe { context.as_ptr() }, err)?;
420
421    let inner = check_ptr(unsafe { context.as_ptr() }, out_ptr)?;
422
423    let frag = fragment.unwrap_or_default();
424
425    Ok((
426      FlakeReference {
427        inner,
428        _context: Arc::clone(context),
429      },
430      frag,
431    ))
432  }
433}
434
435impl Drop for FlakeReference {
436  fn drop(&mut self) {
437    // SAFETY: We own the reference and it is valid until drop
438    unsafe {
439      sys::nix_flake_reference_free(self.inner.as_ptr());
440    }
441  }
442}
443
444// SAFETY: `FlakeReference` wraps a parsed but unresolved reference value
445// owned outright via `nix_flake_reference*`, kept alive by
446// `Arc<Context>`. Resolution happens later through `LockedFlake::lock`,
447// which calls into `Context`; sending the unresolved value to another
448// thread before locking is sound. `Sync` is NOT implemented because
449// `lock` and `add_input_override` mutate through `Context`'s error
450// buffer.
451unsafe impl Send for FlakeReference {}
452
453/// A fully locked flake.
454///
455/// Obtain one via [`LockedFlake::lock`], then call
456/// [`output_attrs`](LockedFlake::output_attrs) to get the attribute set of
457/// flake outputs.
458pub struct LockedFlake {
459  inner:    NonNull<sys::nix_locked_flake>,
460  _context: Arc<Context>,
461}
462
463/// A locked flake graph imported from an owned export payload.
464///
465/// This is intentionally narrower than [`LockedFlake`]. It contains enough
466/// graph state to evaluate outputs through Nix's `callFlake`, but it is not a
467/// general-purpose replacement for a flake freshly returned by
468/// [`LockedFlake::lock`].
469pub struct ImportedLockedFlake {
470  inner:    NonNull<sys::nix_locked_flake>,
471  _context: Arc<Context>,
472}
473
474impl LockedFlake {
475  /// Lock a flake, resolving and pinning all inputs.
476  ///
477  /// # Errors
478  ///
479  /// Returns an error if the C API call fails or returns a null pointer.
480  pub fn lock(
481    context: &Arc<Context>,
482    fetch_settings: &FetchersSettings,
483    flake_settings: &FlakeSettings,
484    eval_state: &EvalState,
485    lock_flags: &LockFlags,
486    flake_ref: &FlakeReference,
487  ) -> Result<Self> {
488    // SAFETY: all arguments are valid
489    let ptr = unsafe {
490      sys::nix_flake_lock(
491        context.as_ptr(),
492        fetch_settings.as_ptr(),
493        flake_settings.as_ptr(),
494        eval_state.as_ptr(),
495        lock_flags.as_ptr(),
496        flake_ref.inner.as_ptr(),
497      )
498    };
499
500    let inner = check_ptr(unsafe { context.as_ptr() }, ptr)?;
501
502    Ok(LockedFlake {
503      inner,
504      _context: Arc::clone(context),
505    })
506  }
507
508  /// Export this locked flake as an owned JSON graph.
509  ///
510  /// The exported JSON contains the lock file and the source-path overrides
511  /// Nix keeps beside it for local or overridden inputs. Send this payload to
512  /// another process and reconstruct it with
513  /// [`ImportedLockedFlake::import_json`].
514  ///
515  /// # Errors
516  ///
517  /// Returns an error if the C API call fails or returns invalid UTF-8.
518  pub fn export_json(&self) -> Result<String> {
519    unsafe {
520      checked_string_from_callback(self._context.as_ptr(), |cb, ud| {
521        sys::nix_locked_flake_export_json(
522          self._context.as_ptr(),
523          self.inner.as_ptr(),
524          cb,
525          ud,
526        )
527      })
528    }
529  }
530
531  /// Get the output attributes of this locked flake as a Nix value.
532  ///
533  /// The returned [`Value`] is tied to the lifetime of `eval_state`.
534  ///
535  /// # Errors
536  ///
537  /// Returns an error if the C API call fails.
538  pub fn output_attrs<'s>(
539    &self,
540    flake_settings: &FlakeSettings,
541    eval_state: &'s EvalState,
542  ) -> Result<Value<'s>> {
543    output_attrs_from_raw(
544      &self._context,
545      self.inner,
546      flake_settings,
547      eval_state,
548    )
549  }
550}
551
552impl ImportedLockedFlake {
553  /// Import a locked flake graph from an owned JSON graph.
554  ///
555  /// The root flake and inputs are taken from `json`; this does not resolve a
556  /// flake reference, update inputs, or write a lock file.
557  ///
558  /// # Errors
559  ///
560  /// Returns an error if the C API call fails or returns a null pointer.
561  pub fn import_json(
562    context: &Arc<Context>,
563    fetch_settings: &FetchersSettings,
564    json: &str,
565  ) -> Result<Self> {
566    let bytes = json.as_bytes();
567    let ptr = unsafe {
568      sys::nix_locked_flake_import_json(
569        context.as_ptr(),
570        fetch_settings.as_ptr(),
571        bytes.as_ptr().cast(),
572        bytes.len(),
573      )
574    };
575
576    let inner = check_ptr(unsafe { context.as_ptr() }, ptr)?;
577
578    Ok(Self {
579      inner,
580      _context: Arc::clone(context),
581    })
582  }
583
584  /// Get the output attributes of this imported graph as a Nix value.
585  ///
586  /// The returned [`Value`] is tied to the lifetime of `eval_state`.
587  ///
588  /// # Errors
589  ///
590  /// Returns an error if the C API call fails.
591  pub fn output_attrs<'s>(
592    &self,
593    flake_settings: &FlakeSettings,
594    eval_state: &'s EvalState,
595  ) -> Result<Value<'s>> {
596    output_attrs_from_raw(
597      &self._context,
598      self.inner,
599      flake_settings,
600      eval_state,
601    )
602  }
603}
604
605fn output_attrs_from_raw<'s>(
606  context: &Arc<Context>,
607  inner: NonNull<sys::nix_locked_flake>,
608  flake_settings: &FlakeSettings,
609  eval_state: &'s EvalState,
610) -> Result<Value<'s>> {
611  // SAFETY: all pointers are valid.
612  let ptr = unsafe {
613    sys::nix_locked_flake_get_output_attrs(
614      context.as_ptr(),
615      flake_settings.as_ptr(),
616      eval_state.as_ptr(),
617      inner.as_ptr(),
618    )
619  };
620
621  let inner = check_ptr(unsafe { context.as_ptr() }, ptr)?;
622
623  Ok(Value {
624    inner,
625    state: eval_state,
626  })
627}
628
629impl Drop for LockedFlake {
630  fn drop(&mut self) {
631    // SAFETY: We own the locked flake and it is valid until drop
632    unsafe {
633      sys::nix_locked_flake_free(self.inner.as_ptr());
634    }
635  }
636}
637
638impl Drop for ImportedLockedFlake {
639  fn drop(&mut self) {
640    // SAFETY: We own the locked flake and it is valid until drop
641    unsafe {
642      sys::nix_locked_flake_free(self.inner.as_ptr());
643    }
644  }
645}
646
647// SAFETY: `LockedFlake` owns its `nix_locked_flake*` and keeps the
648// context alive via `Arc<Context>`. The locked-flake value is immutable
649// once produced; calling `output_attrs` only reads from it but still
650// routes through `Context`'s error buffer, which is why `Sync` is NOT
651// implemented.
652unsafe impl Send for LockedFlake {}
653
654// SAFETY: same ownership and thread-safety contract as `LockedFlake`.
655unsafe impl Send for ImportedLockedFlake {}
656
657#[cfg(test)]
658mod tests {
659  use std::{fs, sync::Arc};
660
661  use serial_test::serial;
662
663  use super::*;
664  use crate::{Context, EvalStateBuilder, Store};
665
666  fn make_state(ctx: &Arc<Context>) -> (Arc<Store>, EvalState) {
667    let store = Arc::new(Store::open(ctx, None).expect("Failed to open store"));
668    let flake_settings =
669      FlakeSettings::new(ctx).expect("Failed to create flake settings");
670    let state = EvalStateBuilder::new(&store)
671      .expect("Failed to create builder")
672      .with_flake_settings(&flake_settings)
673      .expect("Failed to apply flake settings")
674      .build()
675      .expect("Failed to build state");
676    (store, state)
677  }
678
679  #[test]
680  #[serial]
681  fn test_flake_settings_new() {
682    let ctx = Arc::new(Context::new().expect("Failed to create context"));
683    let _settings =
684      FlakeSettings::new(&ctx).expect("Failed to create flake settings");
685  }
686
687  #[test]
688  #[serial]
689  fn test_flake_settings_with_eval_state() {
690    let ctx = Arc::new(Context::new().expect("Failed to create context"));
691    make_state(&ctx);
692  }
693
694  #[test]
695  #[serial]
696  fn test_fetchers_settings_new() {
697    let ctx = Arc::new(Context::new().expect("Failed to create context"));
698    let _s =
699      FetchersSettings::new(&ctx).expect("Failed to create fetcher settings");
700  }
701
702  #[test]
703  #[serial]
704  fn test_flake_reference_parse_flags_new() {
705    let ctx = Arc::new(Context::new().expect("Failed to create context"));
706    let settings = Arc::new(
707      FlakeSettings::new(&ctx).expect("Failed to create flake settings"),
708    );
709    let _f = FlakeReferenceParseFlags::new(&ctx, &settings)
710      .expect("Failed to create parse flags");
711  }
712
713  #[test]
714  #[serial]
715  fn test_flake_reference_parse_flags_set_base_directory() {
716    let ctx = Arc::new(Context::new().expect("Failed to create context"));
717    let settings = Arc::new(
718      FlakeSettings::new(&ctx).expect("Failed to create flake settings"),
719    );
720    let _f = FlakeReferenceParseFlags::new(&ctx, &settings)
721      .expect("Failed to create parse flags")
722      .set_base_directory("/tmp")
723      .expect("Failed to set base directory");
724  }
725
726  #[test]
727  #[serial]
728  fn test_lock_flags_new() {
729    let ctx = Arc::new(Context::new().expect("Failed to create context"));
730    let settings = Arc::new(
731      FlakeSettings::new(&ctx).expect("Failed to create flake settings"),
732    );
733    let _f =
734      LockFlags::new(&ctx, &settings).expect("Failed to create lock flags");
735  }
736
737  #[test]
738  #[serial]
739  fn test_lock_flags_set_modes() {
740    let ctx = Arc::new(Context::new().expect("Failed to create context"));
741    let settings = Arc::new(
742      FlakeSettings::new(&ctx).expect("Failed to create flake settings"),
743    );
744    let _check = LockFlags::new(&ctx, &settings)
745      .expect("create")
746      .set_mode(LockMode::Check)
747      .expect("set Check");
748    let _virtual = LockFlags::new(&ctx, &settings)
749      .expect("create")
750      .set_mode(LockMode::Virtual)
751      .expect("set Virtual");
752    let _write = LockFlags::new(&ctx, &settings)
753      .expect("create")
754      .set_mode(LockMode::WriteAsNeeded)
755      .expect("set WriteAsNeeded");
756  }
757
758  #[test]
759  #[serial]
760  fn test_locked_flake_export_import_json() {
761    let root = tempfile::tempdir().expect("create root tempdir");
762    let original = tempfile::tempdir().expect("create original input tempdir");
763    let override_input =
764      tempfile::tempdir().expect("create override input tempdir");
765
766    fs::write(
767      original.path().join("flake.nix"),
768      r#"{
769  outputs = { self }: {
770    answer = 1;
771  };
772}
773"#,
774    )
775    .expect("write original input flake");
776    fs::write(
777      override_input.path().join("flake.nix"),
778      r#"{
779  outputs = { self }: {
780    answer = 42;
781  };
782}
783"#,
784    )
785    .expect("write override input flake");
786    fs::write(
787      root.path().join("flake.nix"),
788      format!(
789        r#"{{
790  inputs.dep.url = "path:{}";
791  outputs = {{ self, dep }}: {{
792    answer = dep.answer;
793  }};
794}}
795"#,
796        original.path().display(),
797      ),
798    )
799    .expect("write root flake");
800
801    let ctx = Arc::new(Context::new().expect("Failed to create context"));
802    let settings = Arc::new(
803      FlakeSettings::new(&ctx).expect("Failed to create flake settings"),
804    );
805    let fetch_settings =
806      FetchersSettings::new(&ctx).expect("Failed to create fetcher settings");
807    let parse_flags = FlakeReferenceParseFlags::new(&ctx, &settings)
808      .expect("Failed to create parse flags");
809    let flake_ref = format!("path:{}#answer", root.path().display());
810    let (flake_ref, fragment) = FlakeReference::parse(
811      &ctx,
812      &fetch_settings,
813      &settings,
814      &parse_flags,
815      &flake_ref,
816    )
817    .expect("parse flake ref");
818    assert_eq!(fragment, "answer");
819    let override_ref = format!("path:{}", override_input.path().display());
820    let (override_ref, override_fragment) = FlakeReference::parse(
821      &ctx,
822      &fetch_settings,
823      &settings,
824      &parse_flags,
825      &override_ref,
826    )
827    .expect("parse override flake ref");
828    assert!(override_fragment.is_empty());
829
830    let (store, state) = make_state(&ctx);
831    let lock_flags = LockFlags::new(&ctx, &settings)
832      .expect("create lock flags")
833      .set_mode(LockMode::Virtual)
834      .expect("set virtual mode")
835      .add_input_override("dep", &override_ref)
836      .expect("add input override");
837    let locked = LockedFlake::lock(
838      &ctx,
839      &fetch_settings,
840      &settings,
841      &state,
842      &lock_flags,
843      &flake_ref,
844    )
845    .expect("lock flake");
846
847    let exported = locked.export_json().expect("export locked flake");
848    assert!(exported.contains("\"lockFile\""));
849    assert!(exported.contains("\"dep\""));
850    drop(locked);
851    drop(state);
852    drop(store);
853
854    fs::write(
855      override_input.path().join("flake.nix"),
856      r#"{
857  outputs = { self }: {
858    answer = 99;
859  };
860}
861"#,
862    )
863    .expect("mutate override input flake");
864
865    let (store, imported_state) = make_state(&ctx);
866    let imported =
867      ImportedLockedFlake::import_json(&ctx, &fetch_settings, &exported)
868        .expect("import locked flake");
869
870    let outputs = imported
871      .output_attrs(&settings, &imported_state)
872      .expect("get output attrs");
873    let answer = outputs
874      .get_attr(&fragment)
875      .expect("get fragment output")
876      .as_int()
877      .expect("read answer");
878    assert_eq!(answer, 42);
879    drop(store);
880  }
881}