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 "Error should mention system not available, got: {}",
793 err
794 );
795
796 drop(store);
797 drop(temp_dir);
798 }
799
800 #[test]
801 #[cfg(nix_at_least = "2.33")]
802 fn realise_builder_fails() {
803 let (mut store, temp_dir) = create_temp_store();
804
805 let system = current_system()
806 .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS));
807
808 let drv_json = serde_json::json!({
810 "args": ["-c", "exit 1"],
811 "builder": "/bin/sh",
812 "env": {
813 "builder": "/bin/sh",
814 "name": "failing",
815 "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
816 "system": system
817 },
818 "inputs": {
819 "drvs": {},
820 "srcs": []
821 },
822 "name": "failing",
823 "outputs": {
824 "out": {
825 "hashAlgo": "sha256",
826 "method": "nar"
827 }
828 },
829 "system": system,
830 "version": 4
831 });
832
833 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
834 let drv_path = store.add_derivation(&drv).unwrap();
835
836 let result = store.realise(&drv_path);
838 let err = match result {
839 Ok(_) => panic!("Build should fail when builder exits with error"),
840 Err(e) => e.to_string(),
841 };
842 assert!(
843 err.contains("builder failed with exit code 1"),
844 "Error should mention builder failed with exit code, got: {}",
845 err
846 );
847
848 drop(store);
849 drop(temp_dir);
850 }
851
852 #[test]
853 #[cfg(nix_at_least = "2.33")]
854 fn realise_builder_no_output() {
855 let (mut store, temp_dir) = create_temp_store();
856
857 let system = current_system()
858 .unwrap_or_else(|_| format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS));
859
860 let drv_json = serde_json::json!({
862 "args": ["-c", "true"],
863 "builder": "/bin/sh",
864 "env": {
865 "builder": "/bin/sh",
866 "name": "no-output",
867 "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
868 "system": system
869 },
870 "inputs": {
871 "drvs": {},
872 "srcs": []
873 },
874 "name": "no-output",
875 "outputs": {
876 "out": {
877 "hashAlgo": "sha256",
878 "method": "nar"
879 }
880 },
881 "system": system,
882 "version": 4
883 });
884
885 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
886 let drv_path = store.add_derivation(&drv).unwrap();
887
888 let result = store.realise(&drv_path);
890 let err = match result {
891 Ok(_) => panic!("Build should fail when builder produces no output"),
892 Err(e) => e.to_string(),
893 };
894 assert!(
895 err.contains("failed to produce output path"),
896 "Error should mention failed to produce output, got: {}",
897 err
898 );
899
900 drop(store);
901 drop(temp_dir);
902 }
903
904 #[test]
905 #[cfg(nix_at_least = "2.33")]
906 fn get_fs_closure_with_outputs() {
907 let (mut store, temp_dir) = create_temp_store();
908 let drv_json = create_test_derivation_json();
909 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
910 let drv_path = store.add_derivation(&drv).unwrap();
911
912 let outputs = store.realise(&drv_path).unwrap();
914 let out_path = &outputs["out"];
915 let out_path_name = out_path.name().unwrap();
916
917 let closure = store.get_fs_closure(&drv_path, false, true, false).unwrap();
919
920 assert!(
922 closure.len() >= 2,
923 "Closure should contain at least drv and output"
924 );
925
926 let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
928 assert!(
929 out_in_closure,
930 "Output path should be in closure when include_outputs=true"
931 );
932
933 drop(store);
934 drop(temp_dir);
935 }
936
937 #[test]
938 #[cfg(nix_at_least = "2.33")]
939 fn get_fs_closure_without_outputs() {
940 let (mut store, temp_dir) = create_temp_store();
941 let drv_json = create_test_derivation_json();
942 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
943 let drv_path = store.add_derivation(&drv).unwrap();
944
945 let outputs = store.realise(&drv_path).unwrap();
947 let out_path = &outputs["out"];
948 let out_path_name = out_path.name().unwrap();
949
950 let closure = store
952 .get_fs_closure(&drv_path, false, false, false)
953 .unwrap();
954
955 let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
957 assert!(
958 !out_in_closure,
959 "Output path should not be in closure when include_outputs=false"
960 );
961
962 drop(store);
963 drop(temp_dir);
964 }
965
966 #[test]
967 #[cfg(nix_at_least = "2.33")]
968 fn get_fs_closure_flip_direction() {
969 let (mut store, temp_dir) = create_temp_store();
970 let drv_json = create_test_derivation_json();
971 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
972 let drv_path = store.add_derivation(&drv).unwrap();
973
974 let outputs = store.realise(&drv_path).unwrap();
976 let out_path = &outputs["out"];
977 let out_path_name = out_path.name().unwrap();
978
979 let closure = store.get_fs_closure(&drv_path, true, true, false).unwrap();
981
982 let out_in_closure = closure.iter().any(|p| p.name().unwrap() == out_path_name);
984 assert!(
985 !out_in_closure,
986 "Output path should not be in closure when flip_direction=true"
987 );
988
989 drop(store);
990 drop(temp_dir);
991 }
992
993 #[test]
994 #[cfg(nix_at_least = "2.33")]
995 fn get_fs_closure_include_derivers() {
996 let (mut store, temp_dir) = create_temp_store();
997 let drv_json = create_test_derivation_json();
998 let drv = store.derivation_from_json(&drv_json.to_string()).unwrap();
999 let drv_path = store.add_derivation(&drv).unwrap();
1000 let drv_path_name = drv_path.name().unwrap();
1001
1002 let outputs = store.realise(&drv_path).unwrap();
1004 let out_path = &outputs["out"];
1005
1006 let closure = store.get_fs_closure(out_path, false, false, true).unwrap();
1008
1009 let drv_in_closure = closure.iter().any(|p| p.name().unwrap() == drv_path_name);
1011 assert!(
1012 drv_in_closure,
1013 "Derivation should be in closure when include_derivers=true"
1014 );
1015
1016 drop(store);
1017 drop(temp_dir);
1018 }
1019}