1use std::{ffi::CString, os::raw::c_char, ptr::NonNull};
2
3use anyhow::{Context as _, Result};
4use nix_bindings_expr::eval_state::EvalState;
5use nix_bindings_fetchers::FetchersSettings;
6use nix_bindings_flake_sys as raw;
7use nix_bindings_util::{
8 context::{self, Context},
9 result_string_init,
10 string_return::{callback_get_result_string, callback_get_result_string_data},
11};
12
13pub struct FlakeSettings {
15 pub(crate) ptr: *mut raw::flake_settings,
16}
17impl Drop for FlakeSettings {
18 fn drop(&mut self) {
19 unsafe {
20 raw::flake_settings_free(self.ptr);
21 }
22 }
23}
24impl FlakeSettings {
25 pub fn new() -> Result<Self> {
26 let mut ctx = Context::new();
27 let s = unsafe { context::check_call!(raw::flake_settings_new(&mut ctx)) }?;
28 Ok(FlakeSettings { ptr: s })
29 }
30 fn add_to_eval_state_builder(
31 &self,
32 builder: &mut nix_bindings_expr::eval_state::EvalStateBuilder,
33 ) -> Result<()> {
34 let mut ctx = Context::new();
35 unsafe {
36 context::check_call!(raw::flake_settings_add_to_eval_state_builder(
37 &mut ctx,
38 self.ptr,
39 builder.raw_ptr()
40 ))
41 }?;
42 Ok(())
43 }
44}
45
46pub trait EvalStateBuilderExt {
47 fn flakes(
49 self,
50 settings: &FlakeSettings,
51 ) -> Result<nix_bindings_expr::eval_state::EvalStateBuilder>;
52}
53impl EvalStateBuilderExt for nix_bindings_expr::eval_state::EvalStateBuilder {
54 fn flakes(
56 mut self,
57 settings: &FlakeSettings,
58 ) -> Result<nix_bindings_expr::eval_state::EvalStateBuilder> {
59 settings.add_to_eval_state_builder(&mut self)?;
60 Ok(self)
61 }
62}
63
64pub struct FlakeReferenceParseFlags {
66 pub(crate) ptr: NonNull<raw::flake_reference_parse_flags>,
67}
68impl Drop for FlakeReferenceParseFlags {
69 fn drop(&mut self) {
70 unsafe {
71 raw::flake_reference_parse_flags_free(self.ptr.as_ptr());
72 }
73 }
74}
75impl FlakeReferenceParseFlags {
76 pub fn new(settings: &FlakeSettings) -> Result<Self> {
77 let mut ctx = Context::new();
78 let ptr = unsafe {
79 context::check_call!(raw::flake_reference_parse_flags_new(&mut ctx, settings.ptr))
80 }?;
81 let ptr = NonNull::new(ptr)
82 .context("flake_reference_parse_flags_new unexpectedly returned null")?;
83 Ok(FlakeReferenceParseFlags { ptr })
84 }
85 pub fn set_base_directory(&mut self, base_directory: &str) -> Result<()> {
88 let mut ctx = Context::new();
89 unsafe {
90 context::check_call!(raw::flake_reference_parse_flags_set_base_directory(
91 &mut ctx,
92 self.ptr.as_ptr(),
93 base_directory.as_ptr() as *const c_char,
94 base_directory.len()
95 ))
96 }?;
97 Ok(())
98 }
99}
100
101pub struct FlakeReference {
102 pub(crate) ptr: NonNull<raw::flake_reference>,
103}
104impl Drop for FlakeReference {
105 fn drop(&mut self) {
106 unsafe {
107 raw::flake_reference_free(self.ptr.as_ptr());
108 }
109 }
110}
111impl FlakeReference {
112 pub fn parse_with_fragment(
117 fetch_settings: &FetchersSettings,
118 flake_settings: &FlakeSettings,
119 flags: &FlakeReferenceParseFlags,
120 reference: &str,
121 ) -> Result<(FlakeReference, String)> {
122 let mut ctx = Context::new();
123 let mut r = result_string_init!();
124 let mut ptr: *mut raw::flake_reference = std::ptr::null_mut();
125 unsafe {
126 context::check_call!(raw::flake_reference_and_fragment_from_string(
127 &mut ctx,
128 fetch_settings.raw_ptr(),
129 flake_settings.ptr,
130 flags.ptr.as_ptr(),
131 reference.as_ptr() as *const c_char,
132 reference.len(),
133 &mut ptr,
135 Some(callback_get_result_string),
136 callback_get_result_string_data(&mut r)
137 ))
138 }?;
139 let ptr = NonNull::new(ptr)
140 .context("flake_reference_and_fragment_from_string unexpectedly returned null")?;
141 Ok((FlakeReference { ptr }, r?))
142 }
143}
144
145pub struct FlakeLockFlags {
147 pub(crate) ptr: *mut raw::flake_lock_flags,
148}
149impl Drop for FlakeLockFlags {
150 fn drop(&mut self) {
151 unsafe {
152 raw::flake_lock_flags_free(self.ptr);
153 }
154 }
155}
156impl FlakeLockFlags {
157 pub fn new(settings: &FlakeSettings) -> Result<Self> {
158 let mut ctx = Context::new();
159 let s = unsafe { context::check_call!(raw::flake_lock_flags_new(&mut ctx, settings.ptr)) }?;
160 Ok(FlakeLockFlags { ptr: s })
161 }
162 pub fn set_mode_write_as_needed(&mut self) -> Result<()> {
164 let mut ctx = Context::new();
165 unsafe {
166 context::check_call!(raw::flake_lock_flags_set_mode_write_as_needed(
167 &mut ctx, self.ptr
168 ))
169 }?;
170 Ok(())
171 }
172 pub fn set_mode_check(&mut self) -> Result<()> {
174 let mut ctx = Context::new();
175 unsafe { context::check_call!(raw::flake_lock_flags_set_mode_check(&mut ctx, self.ptr)) }?;
176 Ok(())
177 }
178 pub fn set_mode_virtual(&mut self) -> Result<()> {
180 let mut ctx = Context::new();
181 unsafe {
182 context::check_call!(raw::flake_lock_flags_set_mode_virtual(&mut ctx, self.ptr))
183 }?;
184 Ok(())
185 }
186 pub fn add_input_override(
188 &mut self,
189 override_path: &str,
190 override_ref: &FlakeReference,
191 ) -> Result<()> {
192 let mut ctx = Context::new();
193 unsafe {
194 context::check_call!(raw::flake_lock_flags_add_input_override(
195 &mut ctx,
196 self.ptr,
197 CString::new(override_path)
198 .context("Failed to create CString for override_path")?
199 .as_ptr(),
200 override_ref.ptr.as_ptr()
201 ))
202 }?;
203 Ok(())
204 }
205}
206
207pub struct LockedFlake {
208 pub(crate) ptr: NonNull<raw::locked_flake>,
209}
210impl Drop for LockedFlake {
211 fn drop(&mut self) {
212 unsafe {
213 raw::locked_flake_free(self.ptr.as_ptr());
214 }
215 }
216}
217impl LockedFlake {
218 pub fn lock(
219 fetch_settings: &FetchersSettings,
220 flake_settings: &FlakeSettings,
221 eval_state: &EvalState,
222 flags: &FlakeLockFlags,
223 flake_ref: &FlakeReference,
224 ) -> Result<LockedFlake> {
225 let mut ctx = Context::new();
226 let ptr = unsafe {
227 context::check_call!(raw::flake_lock(
228 &mut ctx,
229 fetch_settings.raw_ptr(),
230 flake_settings.ptr,
231 eval_state.raw_ptr(),
232 flags.ptr,
233 flake_ref.ptr.as_ptr()
234 ))
235 }?;
236 let ptr = NonNull::new(ptr).context("flake_lock unexpectedly returned null")?;
237 Ok(LockedFlake { ptr })
238 }
239
240 pub fn outputs(
242 &self,
243 flake_settings: &FlakeSettings,
244 eval_state: &mut EvalState,
245 ) -> Result<nix_bindings_expr::value::Value> {
246 let mut ctx = Context::new();
247 unsafe {
248 let r = context::check_call!(raw::locked_flake_get_output_attrs(
249 &mut ctx,
250 flake_settings.ptr,
251 eval_state.raw_ptr(),
252 self.ptr.as_ptr()
253 ))?;
254 Ok(nix_bindings_expr::value::__private::raw_value_new(r))
255 }
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use nix_bindings_expr::eval_state::{gc_register_my_thread, EvalStateBuilder};
262 use nix_bindings_store::store::Store;
263
264 use super::*;
265 use std::sync::Once;
266
267 static INIT: Once = Once::new();
268
269 fn init() {
270 INIT.call_once(|| {
273 nix_bindings_expr::eval_state::init().unwrap();
274 nix_bindings_util::settings::set("experimental-features", "flakes").unwrap();
275 });
276 }
277
278 #[test]
279 fn flake_settings_getflake_exists() {
280 init();
281 let gc_registration = gc_register_my_thread();
282 let store = Store::open(None, []).unwrap();
283 let mut eval_state = EvalStateBuilder::new(store)
284 .unwrap()
285 .flakes(&FlakeSettings::new().unwrap())
286 .unwrap()
287 .build()
288 .unwrap();
289
290 let v = eval_state
291 .eval_from_string("builtins?getFlake", "<test>")
292 .unwrap();
293
294 let b = eval_state.require_bool(&v).unwrap();
295
296 assert!(b);
297
298 drop(gc_registration);
299 }
300
301 #[test]
302 fn flake_lock_load_flake() {
303 init();
304 let gc_registration = gc_register_my_thread();
305 let store = Store::open(None, []).unwrap();
306 let fetchers_settings = FetchersSettings::new().unwrap();
307 let flake_settings = FlakeSettings::new().unwrap();
308 let mut eval_state = EvalStateBuilder::new(store)
309 .unwrap()
310 .flakes(&flake_settings)
311 .unwrap()
312 .build()
313 .unwrap();
314
315 let tmp_dir = tempfile::tempdir().unwrap();
316
317 let flake_nix = tmp_dir.path().join("flake.nix");
319 std::fs::write(
320 &flake_nix,
321 r#"
322{
323 outputs = { ... }: {
324 hello = "potato";
325 };
326}
327 "#,
328 )
329 .unwrap();
330
331 let flake_lock_flags = FlakeLockFlags::new(&flake_settings).unwrap();
332
333 let (flake_ref, fragment) = FlakeReference::parse_with_fragment(
334 &fetchers_settings,
335 &flake_settings,
336 &FlakeReferenceParseFlags::new(&flake_settings).unwrap(),
337 &format!("path:{}#subthing", tmp_dir.path().display()),
338 )
339 .unwrap();
340
341 assert_eq!(fragment, "subthing");
342
343 let locked_flake = LockedFlake::lock(
344 &fetchers_settings,
345 &flake_settings,
346 &eval_state,
347 &flake_lock_flags,
348 &flake_ref,
349 )
350 .unwrap();
351
352 let outputs = locked_flake
353 .outputs(&flake_settings, &mut eval_state)
354 .unwrap();
355
356 let hello = eval_state.require_attrs_select(&outputs, "hello").unwrap();
357 let hello = eval_state.require_string(&hello).unwrap();
358
359 assert_eq!(hello, "potato");
360
361 drop(fetchers_settings);
362 drop(tmp_dir);
363 drop(gc_registration);
364 }
365
366 #[test]
367 fn flake_lock_load_flake_with_flags() {
368 init();
369 let gc_registration = gc_register_my_thread();
370 let store = Store::open(None, []).unwrap();
371 let fetchers_settings = FetchersSettings::new().unwrap();
372 let flake_settings = FlakeSettings::new().unwrap();
373 let mut eval_state = EvalStateBuilder::new(store)
374 .unwrap()
375 .flakes(&flake_settings)
376 .unwrap()
377 .build()
378 .unwrap();
379
380 let tmp_dir = tempfile::tempdir().unwrap();
381
382 let flake_dir_a = tmp_dir.path().join("a");
383 let flake_dir_b = tmp_dir.path().join("b");
384 let flake_dir_c = tmp_dir.path().join("c");
385
386 std::fs::create_dir_all(&flake_dir_a).unwrap();
387 std::fs::create_dir_all(&flake_dir_b).unwrap();
388 std::fs::create_dir_all(&flake_dir_c).unwrap();
389
390 let flake_dir_a_str = flake_dir_a.to_str().unwrap();
391 let flake_dir_c_str = flake_dir_c.to_str().unwrap();
392 assert!(!flake_dir_a_str.is_empty());
393 assert!(!flake_dir_c_str.is_empty());
394
395 std::fs::write(
397 tmp_dir.path().join("a/flake.nix"),
398 r#"
399 {
400 inputs.b.url = "@flake_dir_b@";
401 outputs = { b, ... }: {
402 hello = b.hello;
403 };
404 }
405 "#
406 .replace("@flake_dir_b@", flake_dir_b.to_str().unwrap()),
407 )
408 .unwrap();
409
410 std::fs::write(
412 tmp_dir.path().join("b/flake.nix"),
413 r#"
414 {
415 outputs = { ... }: {
416 hello = "BOB";
417 };
418 }
419 "#,
420 )
421 .unwrap();
422
423 std::fs::write(
425 tmp_dir.path().join("c/flake.nix"),
426 r#"
427 {
428 outputs = { ... }: {
429 hello = "Claire";
430 };
431 }
432 "#,
433 )
434 .unwrap();
435
436 let mut flake_lock_flags = FlakeLockFlags::new(&flake_settings).unwrap();
437
438 let mut flake_reference_parse_flags =
439 FlakeReferenceParseFlags::new(&flake_settings).unwrap();
440
441 flake_reference_parse_flags
442 .set_base_directory(tmp_dir.path().to_str().unwrap())
443 .unwrap();
444
445 let (flake_ref_a, fragment) = FlakeReference::parse_with_fragment(
446 &fetchers_settings,
447 &flake_settings,
448 &flake_reference_parse_flags,
449 &format!("path:{}", &flake_dir_a_str),
450 )
451 .unwrap();
452
453 assert_eq!(fragment, "");
454
455 flake_lock_flags.set_mode_check().unwrap();
458
459 let locked_flake = LockedFlake::lock(
460 &fetchers_settings,
461 &flake_settings,
462 &eval_state,
463 &flake_lock_flags,
464 &flake_ref_a,
465 );
466 assert!(locked_flake.is_err());
468 let saved_err = match locked_flake {
469 Ok(_) => panic!("Expected error, but got Ok"),
470 Err(e) => e,
471 };
472
473 flake_lock_flags.set_mode_virtual().unwrap();
475
476 let locked_flake = LockedFlake::lock(
477 &fetchers_settings,
478 &flake_settings,
479 &eval_state,
480 &flake_lock_flags,
481 &flake_ref_a,
482 )
483 .unwrap();
484
485 let outputs = locked_flake
486 .outputs(&flake_settings, &mut eval_state)
487 .unwrap();
488
489 let hello = eval_state.require_attrs_select(&outputs, "hello").unwrap();
490 let hello = eval_state.require_string(&hello).unwrap();
491
492 assert_eq!(hello, "BOB");
493
494 flake_lock_flags.set_mode_check().unwrap();
497
498 let locked_flake = LockedFlake::lock(
499 &fetchers_settings,
500 &flake_settings,
501 &eval_state,
502 &flake_lock_flags,
503 &flake_ref_a,
504 );
505 assert!(locked_flake.is_err());
507 match locked_flake {
508 Ok(_) => panic!("Expected error, but got Ok"),
509 Err(e) => {
510 assert_eq!(e.to_string(), saved_err.to_string());
511 }
512 };
513
514 flake_lock_flags.set_mode_write_as_needed().unwrap();
517
518 let locked_flake = LockedFlake::lock(
519 &fetchers_settings,
520 &flake_settings,
521 &eval_state,
522 &flake_lock_flags,
523 &flake_ref_a,
524 )
525 .unwrap();
526
527 let outputs = locked_flake
528 .outputs(&flake_settings, &mut eval_state)
529 .unwrap();
530 let hello = eval_state.require_attrs_select(&outputs, "hello").unwrap();
531 let hello = eval_state.require_string(&hello).unwrap();
532 assert_eq!(hello, "BOB");
533
534 flake_lock_flags.set_mode_check().unwrap();
537
538 let locked_flake = LockedFlake::lock(
539 &fetchers_settings,
540 &flake_settings,
541 &eval_state,
542 &flake_lock_flags,
543 &flake_ref_a,
544 )
545 .unwrap();
546
547 let outputs = locked_flake
548 .outputs(&flake_settings, &mut eval_state)
549 .unwrap();
550 let hello = eval_state.require_attrs_select(&outputs, "hello").unwrap();
551 let hello = eval_state.require_string(&hello).unwrap();
552 assert_eq!(hello, "BOB");
553
554 flake_lock_flags.set_mode_write_as_needed().unwrap();
558
559 let (flake_ref_c, fragment) = FlakeReference::parse_with_fragment(
560 &fetchers_settings,
561 &flake_settings,
562 &flake_reference_parse_flags,
563 &format!("path:{}", &flake_dir_c_str),
564 )
565 .unwrap();
566 assert_eq!(fragment, "");
567
568 flake_lock_flags
569 .add_input_override("b", &flake_ref_c)
570 .unwrap();
571
572 let locked_flake = LockedFlake::lock(
573 &fetchers_settings,
574 &flake_settings,
575 &eval_state,
576 &flake_lock_flags,
577 &flake_ref_a,
578 )
579 .unwrap();
580
581 let outputs = locked_flake
582 .outputs(&flake_settings, &mut eval_state)
583 .unwrap();
584 let hello = eval_state.require_attrs_select(&outputs, "hello").unwrap();
585 let hello = eval_state.require_string(&hello).unwrap();
586 assert_eq!(hello, "Claire");
587
588 let mut flake_lock_flags = FlakeLockFlags::new(&flake_settings).unwrap();
590
591 flake_lock_flags.set_mode_check().unwrap();
594
595 let locked_flake = LockedFlake::lock(
596 &fetchers_settings,
597 &flake_settings,
598 &eval_state,
599 &flake_lock_flags,
600 &flake_ref_a,
601 )
602 .unwrap();
603
604 let outputs = locked_flake
605 .outputs(&flake_settings, &mut eval_state)
606 .unwrap();
607 let hello = eval_state.require_attrs_select(&outputs, "hello").unwrap();
608 let hello = eval_state.require_string(&hello).unwrap();
609 assert_eq!(hello, "BOB");
610
611 drop(fetchers_settings);
612 drop(tmp_dir);
613 drop(gc_registration);
614 }
615}