1use anyhow::{bail, Error, Result};
2use nix_bindings_store_sys as raw;
3use nix_bindings_util::context::Context;
4use nix_bindings_util::string_return::{
5 callback_get_result_string, callback_get_result_string_data,
6};
7use nix_bindings_util::{check_call, result_string_init};
8use nix_bindings_util_sys as raw_util;
9#[cfg(nix_at_least = "2.33.0pre")]
10use std::collections::BTreeMap;
11use std::collections::HashMap;
12use std::ffi::{c_char, CString};
13use std::ptr::null_mut;
14use std::ptr::NonNull;
15use std::sync::{Arc, LazyLock, Mutex, Weak};
16
17#[cfg(nix_at_least = "2.33.0pre")]
18use crate::derivation::Derivation;
19use crate::path::StorePath;
20
21static INIT: LazyLock<Result<()>> = LazyLock::new(|| unsafe {
23 check_call!(raw::libstore_init(&mut Context::new()))?;
24 Ok(())
25});
26
27struct StoreRef {
28 inner: NonNull<raw::Store>,
29}
30impl StoreRef {
31 pub unsafe fn ptr(&self) -> *mut raw::Store {
35 self.inner.as_ptr()
36 }
37}
38impl Drop for StoreRef {
39 fn drop(&mut self) {
40 unsafe {
41 raw::store_free(self.inner.as_ptr());
42 }
43 }
44}
45unsafe impl Send for StoreRef {}
46unsafe impl Sync for StoreRef {}
48
49pub struct StoreWeak {
51 inner: Weak<StoreRef>,
52}
53impl StoreWeak {
54 pub fn upgrade(&self) -> Option<Store> {
58 self.inner.upgrade().map(|inner| Store {
59 inner,
60 context: Context::new(),
61 })
62 }
63}
64
65type StoreCacheMap = HashMap<(Option<String>, Vec<(String, String)>), StoreWeak>;
67
68static STORE_CACHE: LazyLock<Arc<Mutex<StoreCacheMap>>> =
69 LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));
70
71#[cfg(nix_at_least = "2.33.0pre")]
72unsafe extern "C" fn callback_get_result_store_path_set(
73 _context: *mut raw_util::c_context,
74 user_data: *mut std::os::raw::c_void,
75 store_path: *const raw::StorePath,
76) {
77 let ret = user_data as *mut Vec<StorePath>;
78 let ret: &mut Vec<StorePath> = &mut *ret;
79
80 let store_path = raw::store_path_clone(store_path);
81
82 let store_path =
83 NonNull::new(store_path).expect("nix_store_parse_path returned a null pointer");
84 let store_path = StorePath::new_raw(store_path);
85 ret.push(store_path);
86}
87
88#[cfg(nix_at_least = "2.33.0pre")]
89fn callback_get_result_store_path_set_data(vec: &mut Vec<StorePath>) -> *mut std::os::raw::c_void {
90 vec as *mut Vec<StorePath> as *mut std::os::raw::c_void
91}
92
93pub struct Store {
94 inner: Arc<StoreRef>,
95 context: Context,
97}
98impl Store {
99 #[doc(alias = "nix_store_open")]
103 pub fn open<'a, 'b>(
104 url: Option<&str>,
105 params: impl IntoIterator<Item = (&'a str, &'b str)>,
106 ) -> Result<Self> {
107 let params = params
108 .into_iter()
109 .map(|(k, v)| (k.to_owned(), v.to_owned()))
110 .collect::<Vec<(String, String)>>();
111 let params2 = params.clone();
112 let mut store_cache = STORE_CACHE
113 .lock()
114 .map_err(|_| Error::msg("Failed to lock store cache. This should never happen."))?;
115 match store_cache.entry((url.map(Into::into), params)) {
116 std::collections::hash_map::Entry::Occupied(mut e) => {
117 if let Some(store) = e.get().upgrade() {
118 Ok(store)
119 } else {
120 let store = Self::open_uncached(
121 url,
122 params2.iter().map(|(k, v)| (k.as_str(), v.as_str())),
123 )?;
124 e.insert(store.weak_ref());
125 Ok(store)
126 }
127 }
128 std::collections::hash_map::Entry::Vacant(e) => {
129 let store = Self::open_uncached(
130 url,
131 params2.iter().map(|(k, v)| (k.as_str(), v.as_str())),
132 )?;
133 e.insert(store.weak_ref());
134 Ok(store)
135 }
136 }
137 }
138 fn open_uncached<'a, 'b>(
139 url: Option<&str>,
140 params: impl IntoIterator<Item = (&'a str, &'b str)>,
141 ) -> Result<Self> {
142 let x = INIT.as_ref();
143 match x {
144 Ok(_) => {}
145 Err(e) => {
146 bail!("nix_libstore_init error: {}", e);
148 }
149 }
150
151 let mut context: Context = Context::new();
152
153 let uri_cstring = match url {
154 Some(url) => Some(CString::new(url)?),
155 None => None,
156 };
157 let uri_ptr = uri_cstring
158 .as_ref()
159 .map(|s| s.as_ptr())
160 .unwrap_or(null_mut());
161
162 let params: Vec<(CString, CString)> = params
165 .into_iter()
166 .map(|(k, v)| Ok((CString::new(k)?, CString::new(v)?))) .collect::<Result<_>>()?;
168 let mut params: Vec<_> = params
170 .iter()
171 .map(|(k, v)| [k.as_ptr(), v.as_ptr()])
172 .collect();
173 let mut params: Vec<*mut *const c_char> = params
175 .iter_mut()
176 .map(|t| t.as_mut_ptr())
177 .chain(std::iter::once(null_mut())) .collect();
179
180 let store =
181 unsafe { check_call!(raw::store_open(&mut context, uri_ptr, params.as_mut_ptr())) }?;
182 if store.is_null() {
183 panic!("nix_c_store_open returned a null pointer without an error");
184 }
185 let store = Store {
186 inner: Arc::new(StoreRef {
187 inner: NonNull::new(store).unwrap(),
188 }),
189 context,
190 };
191 Ok(store)
192 }
193
194 pub unsafe fn raw_ptr(&self) -> *mut raw::Store {
198 self.inner.ptr()
199 }
200
201 #[doc(alias = "nix_store_get_uri")]
202 pub fn get_uri(&mut self) -> Result<String> {
203 let mut r = result_string_init!();
204 unsafe {
205 check_call!(raw::store_get_uri(
206 &mut self.context,
207 self.inner.ptr(),
208 Some(callback_get_result_string),
209 callback_get_result_string_data(&mut r)
210 ))
211 }?;
212 r
213 }
214
215 #[cfg(nix_at_least = "2.26")]
216 #[doc(alias = "nix_store_get_storedir")]
217 pub fn get_storedir(&mut self) -> Result<String> {
218 let mut r = result_string_init!();
219 unsafe {
220 check_call!(raw::store_get_storedir(
221 &mut self.context,
222 self.inner.ptr(),
223 Some(callback_get_result_string),
224 callback_get_result_string_data(&mut r)
225 ))
226 }?;
227 r
228 }
229
230 #[doc(alias = "nix_store_parse_path")]
231 pub fn parse_store_path(&mut self, path: &str) -> Result<StorePath> {
232 let path = CString::new(path)?;
233 unsafe {
234 let store_path = check_call!(raw::store_parse_path(
235 &mut self.context,
236 self.inner.ptr(),
237 path.as_ptr()
238 ))?;
239 let store_path =
240 NonNull::new(store_path).expect("nix_store_parse_path returned a null pointer");
241 Ok(StorePath::new_raw(store_path))
242 }
243 }
244
245 #[doc(alias = "nix_store_real_path")]
246 pub fn real_path(&mut self, path: &StorePath) -> Result<String> {
247 let mut r = result_string_init!();
248 unsafe {
249 check_call!(raw::store_real_path(
250 &mut self.context,
251 self.inner.ptr(),
252 path.as_ptr(),
253 Some(callback_get_result_string),
254 callback_get_result_string_data(&mut r)
255 ))
256 }?;
257 r
258 }
259
260 #[cfg(nix_at_least = "2.33.0pre")]
275 #[doc(alias = "nix_derivation_from_json")]
276 pub fn derivation_from_json(&mut self, json: &str) -> Result<Derivation> {
277 let json_cstr = CString::new(json)?;
278 unsafe {
279 let drv = check_call!(raw::derivation_from_json(
280 &mut self.context,
281 self.inner.ptr(),
282 json_cstr.as_ptr()
283 ))?;
284 let inner = NonNull::new(drv)
285 .ok_or_else(|| Error::msg("derivation_from_json returned null"))?;
286 Ok(Derivation::new_raw(inner))
287 }
288 }
289
290 #[cfg(nix_at_least = "2.33.0pre")]
303 #[doc(alias = "nix_add_derivation")]
304 pub fn add_derivation(&mut self, drv: &Derivation) -> Result<StorePath> {
305 unsafe {
306 let path = check_call!(raw::add_derivation(
307 &mut self.context,
308 self.inner.ptr(),
309 drv.inner.as_ptr()
310 ))?;
311 let path =
312 NonNull::new(path).ok_or_else(|| Error::msg("add_derivation returned null"))?;
313 Ok(StorePath::new_raw(path))
314 }
315 }
316
317 #[cfg(nix_at_least = "2.33.0pre")]
332 #[doc(alias = "nix_store_realise")]
333 pub fn realise(&mut self, path: &StorePath) -> Result<BTreeMap<String, StorePath>> {
334 let mut outputs = BTreeMap::new();
335 let userdata =
336 &mut outputs as *mut BTreeMap<String, StorePath> as *mut std::os::raw::c_void;
337
338 unsafe extern "C" fn callback(
339 userdata: *mut std::os::raw::c_void,
340 outname: *const c_char,
341 out_path: *const raw::StorePath,
342 ) {
343 let outputs = userdata as *mut BTreeMap<String, StorePath>;
344 let outputs = &mut *outputs;
345
346 let name = std::ffi::CStr::from_ptr(outname)
347 .to_string_lossy()
348 .into_owned();
349
350 let path = raw::store_path_clone(out_path);
351 let path = NonNull::new(path).expect("store_path_clone returned null");
352 let path = StorePath::new_raw(path);
353
354 outputs.insert(name, path);
355 }
356
357 unsafe {
358 check_call!(raw::store_realise(
359 &mut self.context,
360 self.inner.ptr(),
361 path.as_ptr(),
362 userdata,
363 Some(callback)
364 ))?;
365 }
366
367 Ok(outputs)
368 }
369
370 #[cfg(nix_at_least = "2.33.0pre")]
389 #[doc(alias = "nix_store_get_fs_closure")]
390 pub fn get_fs_closure(
391 &mut self,
392 store_path: &StorePath,
393 flip_direction: bool,
394 include_outputs: bool,
395 include_derivers: bool,
396 ) -> Result<Vec<StorePath>> {
397 let mut r = Vec::new();
398 unsafe {
399 check_call!(raw::store_get_fs_closure(
400 &mut self.context,
401 self.inner.ptr(),
402 store_path.as_ptr(),
403 flip_direction,
404 include_outputs,
405 include_derivers,
406 callback_get_result_store_path_set_data(&mut r),
407 Some(callback_get_result_store_path_set)
408 ))
409 }?;
410 Ok(r)
411 }
412
413 pub fn weak_ref(&self) -> StoreWeak {
414 StoreWeak {
415 inner: Arc::downgrade(&self.inner),
416 }
417 }
418}
419
420impl Clone for Store {
421 fn clone(&self) -> Self {
422 Store {
423 inner: self.inner.clone(),
424 context: Context::new(),
425 }
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use ctor::ctor;
432 use std::collections::HashMap;
433
434 use super::*;
435
436 #[ctor]
437 fn test_setup() {
438 let _ = INIT.as_ref();
440
441 nix_bindings_util::settings::set("experimental-features", "ca-derivations").ok();
443
444 nix_bindings_util::settings::set("build-hook", "").ok();
446
447 if cfg!(target_os = "linux") {
449 nix_bindings_util::settings::set("sandbox-build-dir", "/custom-build-dir-for-test")
450 .ok();
451 }
452
453 std::env::set_var("_NIX_TEST_NO_SANDBOX", "1");
454
455 nix_bindings_util::settings::set("substituters", "").ok();
457 }
458
459 #[test]
460 fn none_works() {
461 let res = Store::open(None, HashMap::new());
462 res.unwrap();
463 }
464
465 #[test]
466 fn auto_works() {
467 let res = Store::open(Some("auto"), HashMap::new());
470 res.unwrap();
471 }
472
473 #[test]
474 fn invalid_uri_fails() {
475 let res = Store::open(Some("invalid://uri"), HashMap::new());
476 assert!(res.is_err());
477 }
478
479 #[test]
480 fn get_uri() {
481 let mut store = Store::open(None, HashMap::new()).unwrap();
482 let uri = store.get_uri().unwrap();
483 assert!(!uri.is_empty());
484 assert!(uri.is_ascii());
486 println!("uri: {}", uri);
488 }
489
490 #[test]
491 #[ignore] fn get_uri_nixos_cache() {
493 let mut store = Store::open(Some("https://cache.nixos.org/"), HashMap::new()).unwrap();
494 let uri = store.get_uri().unwrap();
495 assert_eq!(uri, "https://cache.nixos.org");
496 }
497
498 #[test]
499 #[cfg(nix_at_least = "2.26" )]
500 fn parse_store_path_ok() {
501 let mut store = crate::store::Store::open(Some("dummy://"), []).unwrap();
502 let store_dir = store.get_storedir().unwrap();
503 let store_path_string =
504 format!("{store_dir}/rdd4pnr4x9rqc9wgbibhngv217w2xvxl-bash-interactive-5.2p26");
505 let store_path = store.parse_store_path(store_path_string.as_str()).unwrap();
506 let real_store_path = store.real_path(&store_path).unwrap();
507 assert_eq!(store_path.name().unwrap(), "bash-interactive-5.2p26");
508 assert_eq!(real_store_path, store_path_string);
509 }
510
511 #[test]
512 fn parse_store_path_fail() {
513 let mut store = crate::store::Store::open(Some("dummy://"), []).unwrap();
514 let store_path_string = "bash-interactive-5.2p26".to_string();
515 let r = store.parse_store_path(store_path_string.as_str());
516 match r {
517 Err(e) => {
518 assert!(e.to_string().contains("bash-interactive-5.2p26"));
519 }
520 _ => panic!("Expected error"),
521 }
522 }
523
524 #[test]
525 fn weak_ref() {
526 let mut store = Store::open(None, HashMap::new()).unwrap();
527 let uri = store.get_uri().unwrap();
528 let weak = store.weak_ref();
529 let mut store2 = weak.upgrade().unwrap();
530 assert_eq!(store2.get_uri().unwrap(), uri);
531 }
532 #[test]
533 fn weak_ref_gone() {
534 let weak = {
535 let store = Store::open_uncached(None, HashMap::new()).unwrap();
538 store.weak_ref()
539 };
540 assert!(weak.upgrade().is_none());
541 assert!(weak.inner.upgrade().is_none());
542 }
543
544 #[cfg(nix_at_least = "2.33.0pre")]
545 fn create_temp_store() -> (Store, tempfile::TempDir) {
546 let temp_dir = tempfile::tempdir().unwrap();
547
548 let store_dir = temp_dir.path().join("store");
549 let state_dir = temp_dir.path().join("state");
550 let log_dir = temp_dir.path().join("log");
551
552 let store_dir_str = store_dir.to_str().unwrap();
553 let state_dir_str = state_dir.to_str().unwrap();
554 let log_dir_str = log_dir.to_str().unwrap();
555
556 let params = vec![
557 ("store", store_dir_str),
558 ("state", state_dir_str),
559 ("log", log_dir_str),
560 ];
561
562 let store = Store::open(Some("local"), params).unwrap();
563 (store, temp_dir)
564 }
565
566 fn current_system() -> Result<String> {
567 nix_bindings_util::settings::get("system")
568 }
569
570 #[cfg(nix_at_least = "2.33")]
571 fn create_test_derivation_json() -> serde_json::Value {
572 let system = current_system().unwrap_or_else(|_| {
573 format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS)
575 });
576 serde_json::json!({
577 "args": ["-c", "echo $name foo > $out"],
578 "builder": "/bin/sh",
579 "env": {
580 "builder": "/bin/sh",
581 "name": "myname",
582 "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
583 "system": system
584 },
585 "inputs": {
586 "drvs": {},
587 "srcs": []
588 },
589 "name": "myname",
590 "outputs": {
591 "out": {
592 "hashAlgo": "sha256",
593 "method": "nar"
594 }
595 },
596 "system": system,
597 "version": 4
598 })
599 }
600
601 #[test]
602 #[cfg(nix_at_least = "2.33")]
603 fn derivation_from_json() {
604 let (mut store, temp_dir) = create_temp_store();
605 let drv_json = create_test_derivation_json();
606 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
607 drop(drv);
609 drop(store);
610 drop(temp_dir);
611 }
612
613 #[test]
614 #[cfg(nix_at_least = "2.33.0pre")]
615 fn derivation_from_invalid_json() {
616 let (mut store, temp_dir) = create_temp_store();
617 let result = store.derivation_from_json("not valid json");
618 assert!(result.is_err());
619 drop(store);
620 drop(temp_dir);
621 }
622
623 #[test]
624 #[cfg(nix_at_least = "2.33")]
625 fn derivation_to_json_round_trip() {
626 let (mut store, _temp_dir) = create_temp_store();
627 let original_value = create_test_derivation_json();
628
629 let drv = store
631 .derivation_from_json(&original_value.to_string())
632 .unwrap();
633
634 let round_trip_json = drv.to_json_string().unwrap();
636 let round_trip_value: serde_json::Value = serde_json::from_str(&round_trip_json).unwrap();
637
638 assert_eq!(
640 original_value, round_trip_value,
641 "Round-trip JSON should match original.\nOriginal: {}\nRound-trip: {}",
642 original_value, round_trip_value
643 );
644 }
645
646 #[test]
647 #[cfg(nix_at_least = "2.33")]
648 fn add_derivation() {
649 let (mut store, temp_dir) = create_temp_store();
650 let drv_json = create_test_derivation_json();
651 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
652 let drv_path = store.add_derivation(&drv).unwrap();
653
654 let name = drv_path.name().unwrap();
656 assert!(name.ends_with(".drv"));
657
658 drop(store);
659 drop(temp_dir);
660 }
661
662 #[test]
663 #[cfg(nix_at_least = "2.33")]
664 fn realise() {
665 let (mut store, temp_dir) = create_temp_store();
666 let drv_json = create_test_derivation_json();
667 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
668 let drv_path = store.add_derivation(&drv).unwrap();
669
670 let outputs = store.realise(&drv_path).unwrap();
672
673 assert!(outputs.contains_key("out"));
675 let out_path = &outputs["out"];
676 let out_name = out_path.name().unwrap();
677 assert_eq!(out_name, "myname");
678
679 drop(store);
680 drop(temp_dir);
681 }
682
683 #[cfg(nix_at_least = "2.33")]
684 fn create_multi_output_derivation_json() -> serde_json::Value {
685 let system = current_system()
686 .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS));
687
688 serde_json::json!({
689 "version": 4,
690 "name": "multi-output-test",
691 "system": system,
692 "builder": "/bin/sh",
693 "args": ["-c", "echo a > $outa; echo b > $outb; echo c > $outc; echo d > $outd; echo e > $oute; echo f > $outf; echo g > $outg; echo h > $outh; echo i > $outi; echo j > $outj"],
694 "env": {
695 "builder": "/bin/sh",
696 "name": "multi-output-test",
697 "system": system,
698 "outf": "/1vkfzqpwk313b51x0xjyh5s7w1lx141mr8da3dr9wqz5aqjyr2fh",
699 "outd": "/1ypxifgmbzp5sd0pzsp2f19aq68x5215260z3lcrmy5fch567lpm",
700 "outi": "/1wmasjnqi12j1mkjbxazdd0qd0ky6dh1qry12fk8qyp5kdamhbdx",
701 "oute": "/1f9r2k1s168js509qlw8a9di1qd14g5lqdj5fcz8z7wbqg11qp1f",
702 "outh": "/1rkx1hmszslk5nq9g04iyvh1h7bg8p92zw0hi4155hkjm8bpdn95",
703 "outc": "/1rj4nsf9pjjqq9jsq58a2qkwa7wgvgr09kgmk7mdyli6h1plas4w",
704 "outb": "/1p7i1dxifh86xq97m5kgb44d7566gj7rfjbw7fk9iij6ca4akx61",
705 "outg": "/14f8qi0r804vd6a6v40ckylkk1i6yl6fm243qp6asywy0km535lc",
706 "outj": "/0gkw1366qklqfqb2lw1pikgdqh3cmi3nw6f1z04an44ia863nxaz",
707 "outa": "/039akv9zfpihrkrv4pl54f3x231x362bll9afblsgfqgvx96h198"
708 },
709 "inputs": {
710 "drvs": {},
711 "srcs": []
712 },
713 "outputs": {
714 "outd": { "hashAlgo": "sha256", "method": "nar" },
715 "outf": { "hashAlgo": "sha256", "method": "nar" },
716 "outg": { "hashAlgo": "sha256", "method": "nar" },
717 "outb": { "hashAlgo": "sha256", "method": "nar" },
718 "outc": { "hashAlgo": "sha256", "method": "nar" },
719 "outi": { "hashAlgo": "sha256", "method": "nar" },
720 "outj": { "hashAlgo": "sha256", "method": "nar" },
721 "outh": { "hashAlgo": "sha256", "method": "nar" },
722 "outa": { "hashAlgo": "sha256", "method": "nar" },
723 "oute": { "hashAlgo": "sha256", "method": "nar" }
724 }
725 })
726 }
727
728 #[test]
729 #[cfg(nix_at_least = "2.33")]
730 fn realise_multi_output_ordering() {
731 let (mut store, temp_dir) = create_temp_store();
732 let drv_json = create_multi_output_derivation_json();
733 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
734 let drv_path = store.add_derivation(&drv).unwrap();
735
736 let outputs = store.realise(&drv_path).unwrap();
738
739 let output_names: Vec<&String> = outputs.keys().collect();
741 let expected_order = vec![
742 "outa", "outb", "outc", "outd", "oute", "outf", "outg", "outh", "outi", "outj",
743 ];
744 assert_eq!(output_names, expected_order);
745
746 drop(store);
747 drop(temp_dir);
748 }
749
750 #[test]
751 #[cfg(nix_at_least = "2.33")]
752 fn realise_invalid_system() {
753 let (mut store, temp_dir) = create_temp_store();
754
755 let system = "bogus65-bogusos";
757 let drv_json = serde_json::json!({
758 "args": ["-c", "echo $name foo > $out"],
759 "builder": "/bin/sh",
760 "env": {
761 "builder": "/bin/sh",
762 "name": "myname",
763 "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
764 "system": system
765 },
766 "inputs": {
767 "drvs": {},
768 "srcs": []
769 },
770 "name": "myname",
771 "outputs": {
772 "out": {
773 "hashAlgo": "sha256",
774 "method": "nar"
775 }
776 },
777 "system": system,
778 "version": 4
779 });
780
781 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
782 let drv_path = store.add_derivation(&drv).unwrap();
783
784 let result = store.realise(&drv_path);
786 let err = match result {
787 Ok(_) => panic!("Build should fail with invalid system"),
788 Err(e) => e.to_string(),
789 };
790 assert!(
791 err.contains("required system or feature not available")
792 || err.contains("platform mismatch"),
793 "Error should mention system not available, got: {}",
794 err
795 );
796
797 drop(store);
798 drop(temp_dir);
799 }
800
801 #[test]
802 #[cfg(nix_at_least = "2.33")]
803 fn realise_builder_fails() {
804 let (mut store, temp_dir) = create_temp_store();
805
806 let system = current_system()
807 .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS));
808
809 let drv_json = serde_json::json!({
811 "args": ["-c", "exit 1"],
812 "builder": "/bin/sh",
813 "env": {
814 "builder": "/bin/sh",
815 "name": "failing",
816 "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
817 "system": system
818 },
819 "inputs": {
820 "drvs": {},
821 "srcs": []
822 },
823 "name": "failing",
824 "outputs": {
825 "out": {
826 "hashAlgo": "sha256",
827 "method": "nar"
828 }
829 },
830 "system": system,
831 "version": 4
832 });
833
834 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
835 let drv_path = store.add_derivation(&drv).unwrap();
836
837 let result = store.realise(&drv_path);
839 let err = match result {
840 Ok(_) => panic!("Build should fail when builder exits with error"),
841 Err(e) => e.to_string(),
842 };
843 assert!(
844 err.contains("builder failed with exit code 1"),
845 "Error should mention builder failed with exit code, got: {}",
846 err
847 );
848
849 drop(store);
850 drop(temp_dir);
851 }
852
853 #[test]
854 #[cfg(nix_at_least = "2.33")]
855 fn realise_builder_no_output() {
856 let (mut store, temp_dir) = create_temp_store();
857
858 let system = current_system()
859 .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS));
860
861 let drv_json = serde_json::json!({
863 "args": ["-c", "true"],
864 "builder": "/bin/sh",
865 "env": {
866 "builder": "/bin/sh",
867 "name": "no-output",
868 "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
869 "system": system
870 },
871 "inputs": {
872 "drvs": {},
873 "srcs": []
874 },
875 "name": "no-output",
876 "outputs": {
877 "out": {
878 "hashAlgo": "sha256",
879 "method": "nar"
880 }
881 },
882 "system": system,
883 "version": 4
884 });
885
886 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
887 let drv_path = store.add_derivation(&drv).unwrap();
888
889 let result = store.realise(&drv_path);
891 let err = match result {
892 Ok(_) => panic!("Build should fail when builder produces no output"),
893 Err(e) => e.to_string(),
894 };
895 assert!(
896 err.contains("failed to produce output path"),
897 "Error should mention failed to produce output, got: {}",
898 err
899 );
900
901 drop(store);
902 drop(temp_dir);
903 }
904
905 #[test]
906 #[cfg(nix_at_least = "2.33")]
907 fn get_fs_closure_with_outputs() {
908 let (mut store, temp_dir) = create_temp_store();
909 let drv_json = create_test_derivation_json();
910 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
911 let drv_path = store.add_derivation(&drv).unwrap();
912
913 let outputs = store.realise(&drv_path).unwrap();
915 let out_path = &outputs["out"];
916 let out_path_name = out_path.name().unwrap();
917
918 let closure = store.get_fs_closure(&drv_path, false, true, false).unwrap();
920
921 assert!(
923 closure.len() >= 2,
924 "Closure should contain at least drv and output"
925 );
926
927 let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
929 assert!(
930 out_in_closure,
931 "Output path should be in closure when include_outputs=true"
932 );
933
934 drop(store);
935 drop(temp_dir);
936 }
937
938 #[test]
939 #[cfg(nix_at_least = "2.33")]
940 fn get_fs_closure_without_outputs() {
941 let (mut store, temp_dir) = create_temp_store();
942 let drv_json = create_test_derivation_json();
943 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
944 let drv_path = store.add_derivation(&drv).unwrap();
945
946 let outputs = store.realise(&drv_path).unwrap();
948 let out_path = &outputs["out"];
949 let out_path_name = out_path.name().unwrap();
950
951 let closure = store
953 .get_fs_closure(&drv_path, false, false, false)
954 .unwrap();
955
956 let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
958 assert!(
959 !out_in_closure,
960 "Output path should not be in closure when include_outputs=false"
961 );
962
963 drop(store);
964 drop(temp_dir);
965 }
966
967 #[test]
968 #[cfg(nix_at_least = "2.33")]
969 fn get_fs_closure_flip_direction() {
970 let (mut store, temp_dir) = create_temp_store();
971 let drv_json = create_test_derivation_json();
972 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
973 let drv_path = store.add_derivation(&drv).unwrap();
974
975 let outputs = store.realise(&drv_path).unwrap();
977 let out_path = &outputs["out"];
978 let out_path_name = out_path.name().unwrap();
979
980 let closure = store.get_fs_closure(&drv_path, true, true, false).unwrap();
982
983 let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
985 assert!(
986 !out_in_closure,
987 "Output path should not be in closure when flip_direction=true"
988 );
989
990 drop(store);
991 drop(temp_dir);
992 }
993
994 #[test]
995 #[cfg(nix_at_least = "2.33")]
996 fn get_fs_closure_include_derivers() {
997 let (mut store, temp_dir) = create_temp_store();
998 let drv_json = create_test_derivation_json();
999 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
1000 let drv_path = store.add_derivation(&drv).unwrap();
1001 let drv_path_name = drv_path.name().unwrap();
1002
1003 let outputs = store.realise(&drv_path).unwrap();
1005 let out_path = &outputs["out"];
1006
1007 let closure = store.get_fs_closure(out_path, false, false, true).unwrap();
1009
1010 let drv_in_closure = closure.iter().any(|p| p.name().unwrap() == drv_path_name);
1012 assert!(
1013 drv_in_closure,
1014 "Derivation should be in closure when include_derivers=true"
1015 );
1016
1017 drop(store);
1018 drop(temp_dir);
1019 }
1020}