1use 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
32pub struct FlakeSettings {
58 pub(crate) inner: NonNull<sys::nix_flake_settings>,
59 _context: Arc<Context>,
60}
61
62impl FlakeSettings {
63 pub fn new(context: &Arc<Context>) -> Result<Self> {
69 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 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 unsafe {
90 sys::nix_flake_settings_free(self.inner.as_ptr());
91 }
92 }
93}
94
95unsafe impl Send for FlakeSettings {}
101
102pub struct FetchersSettings {
108 inner: NonNull<sys::nix_fetchers_settings>,
109 _context: Arc<Context>,
110}
111
112impl FetchersSettings {
113 pub fn new(context: &Arc<Context>) -> Result<Self> {
119 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 unsafe {
137 sys::nix_fetchers_settings_free(self.inner.as_ptr());
138 }
139 }
140}
141
142unsafe impl Send for FetchersSettings {}
148
149pub struct FlakeReferenceParseFlags {
155 inner: NonNull<sys::nix_flake_reference_parse_flags>,
156 _context: Arc<Context>,
157}
158
159impl FlakeReferenceParseFlags {
160 pub fn new(
166 context: &Arc<Context>,
167 flake_settings: &FlakeSettings,
168 ) -> Result<Self> {
169 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 pub fn set_base_directory(self, dir: &str) -> Result<Self> {
189 let bytes = dir.as_bytes();
190 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 unsafe {
216 sys::nix_flake_reference_parse_flags_free(self.inner.as_ptr());
217 }
218 }
219}
220
221unsafe impl Send for FlakeReferenceParseFlags {}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum LockMode {
236 Check,
238 Virtual,
240 WriteAsNeeded,
242}
243
244pub struct LockFlags {
249 inner: NonNull<sys::nix_flake_lock_flags>,
250 _context: Arc<Context>,
251 _settings: Arc<FlakeSettings>,
252}
253
254impl LockFlags {
255 pub fn new(
261 context: &Arc<Context>,
262 flake_settings: &Arc<FlakeSettings>,
263 ) -> Result<Self> {
264 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 pub fn set_mode(self, mode: LockMode) -> Result<Self> {
282 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 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 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 unsafe {
346 sys::nix_flake_lock_flags_free(self.inner.as_ptr());
347 }
348 }
349}
350
351unsafe impl Send for LockFlags {}
357
358unsafe 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
373pub struct FlakeReference {
378 inner: NonNull<sys::nix_flake_reference>,
379 _context: Arc<Context>,
380}
381
382impl FlakeReference {
383 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 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 unsafe {
439 sys::nix_flake_reference_free(self.inner.as_ptr());
440 }
441 }
442}
443
444unsafe impl Send for FlakeReference {}
452
453pub struct LockedFlake {
459 inner: NonNull<sys::nix_locked_flake>,
460 _context: Arc<Context>,
461}
462
463pub struct ImportedLockedFlake {
470 inner: NonNull<sys::nix_locked_flake>,
471 _context: Arc<Context>,
472}
473
474impl LockedFlake {
475 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 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 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 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 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 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 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 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 unsafe {
642 sys::nix_locked_flake_free(self.inner.as_ptr());
643 }
644 }
645}
646
647unsafe impl Send for LockedFlake {}
653
654unsafe 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}