1use std::collections::{BTreeMap, HashSet, VecDeque};
5use std::future::Future;
6use std::path::PathBuf;
7
8use async_stream::try_stream;
9use bstr::BString;
10use bytes::Bytes;
11use futures::Stream;
12use nix_compat::derivation::Output;
13use nix_compat::store_path::hash_placeholder;
14use nix_compat::{derivation::Derivation, nixbase32, store_path::StorePath};
15use sha2::{Digest, Sha256};
16use snix_build::buildservice::{AdditionalFile, BuildConstraints, BuildRequest, EnvVar};
17use snix_castore::Node;
18use snix_store::path_info::PathInfo;
19use tracing::warn;
20
21use crate::builder::structured_attrs::handle_structured_attrs;
22use crate::known_paths::KnownPaths;
23
24pub mod export_reference_graph;
25pub mod structured_attrs;
26
27const NIX_ENVIRONMENT_VARS: [(&str, &str); 12] = [
30 ("HOME", "/homeless-shelter"),
31 ("NIX_BUILD_CORES", "0"), ("NIX_BUILD_TOP", "/build"),
33 ("NIX_LOG_FD", "2"),
34 ("NIX_STORE", "/nix/store"),
35 ("PATH", "/path-not-set"),
36 ("PWD", "/build"),
37 ("TEMP", "/build"),
38 ("TEMPDIR", "/build"),
39 ("TERM", "xterm-256color"),
40 ("TMP", "/build"),
41 ("TMPDIR", "/build"),
42];
43
44pub(crate) fn get_all_inputs<'a, F, Fut>(
48 derivation: &'a Derivation,
49 known_paths: &'a KnownPaths,
50 get_path_info: F,
51) -> impl Stream<Item = Result<(StorePath<String>, Node), std::io::Error>> + use<F, Fut>
52where
53 F: Fn(StorePath<String>) -> Fut,
54 Fut: Future<Output = std::io::Result<Option<PathInfo>>>,
55{
56 let mut visited: HashSet<StorePath<String>> = HashSet::new();
57 let mut queue: VecDeque<StorePath<String>> = derivation
58 .input_sources
59 .iter()
60 .cloned()
61 .chain(
62 derivation
63 .input_derivations
64 .iter()
65 .flat_map(|(drv_path, outs)| {
66 let drv = known_paths.get_drv_by_drvpath(drv_path).expect("drv Bug!!");
67 outs.iter().map(move |output| {
68 drv.outputs
69 .get(output)
70 .expect("No output bug!")
71 .path
72 .as_ref()
73 .expect("output has no store path")
74 .clone()
75 })
76 }),
77 )
78 .collect();
79 try_stream! {
80 while let Some(store_path) = queue.pop_front() {
81 let info = get_path_info(store_path).await?.ok_or(std::io::Error::other("path_info not present"))?;
82 for reference in info.references {
83 if visited.insert(reference.clone()) {
84 queue.push_back(reference);
85 }
86 }
87
88 yield (info.store_path, info.node);
89
90
91 }
92 }
93}
94
95pub(crate) fn derivation_into_build_request(
98 mut derivation: Derivation,
99 inputs: &BTreeMap<StorePath<String>, Node>,
100) -> std::io::Result<BuildRequest> {
101 debug_assert!(derivation.validate(true).is_ok(), "drv must validate");
102
103 let mut command_args: Vec<String> = Vec::with_capacity(derivation.arguments.len() + 1);
105 command_args.push(derivation.builder);
106 command_args.extend_from_slice(
107 derivation
108 .arguments
109 .into_iter()
110 .map(|arg| replace_placeholders(&arg, &derivation.outputs))
111 .collect::<Vec<String>>()
112 .as_slice(),
113 );
114
115 let mut environment_vars: BTreeMap<String, Vec<u8>> = BTreeMap::new();
119 let mut additional_files: BTreeMap<String, Bytes> = BTreeMap::new();
120
121 environment_vars.extend(
123 NIX_ENVIRONMENT_VARS
124 .iter()
125 .map(|(k, v)| (k.to_string(), v.to_owned().into())),
126 );
127
128 if let Some(json_str) = derivation.environment.remove(structured_attrs::JSON_KEY) {
129 handle_structured_attrs(
130 &json_str,
131 derivation.outputs.iter().map(|(out_name, output)| {
132 (
133 out_name.as_str(),
134 output
135 .path
136 .as_ref()
137 .expect("Snix bug: output has no path")
138 .as_ref(),
139 )
140 }),
141 &mut environment_vars,
142 &mut additional_files,
143 )?;
144 } else {
145 environment_vars.extend(derivation.environment.into_iter().map(|(k, v)| {
148 (
149 k.clone(),
150 replace_placeholders_b(&v, &derivation.outputs).into(),
151 )
152 }));
153
154 handle_pass_as_file(&mut environment_vars, &mut additional_files)?;
156 }
157
158 let mut constraints = HashSet::from([
160 BuildConstraints::System(derivation.system.to_owned()),
161 BuildConstraints::ProvideBinSh,
162 ]);
163
164 if derivation.outputs.len() == 1
165 && derivation
166 .outputs
167 .get("out")
168 .expect("Snix bug: Derivation has no out output")
169 .is_fixed()
170 {
171 constraints.insert(BuildConstraints::NetworkAccess);
172 }
173
174 Ok(BuildRequest {
175 refscan_needles: derivation
178 .outputs
179 .values()
180 .filter_map(|output| output.path.as_ref())
181 .map(|path| nixbase32::encode(path.digest()))
182 .chain(inputs.keys().map(|path| nixbase32::encode(path.digest())))
183 .collect(),
184 command_args,
185
186 outputs: derivation
187 .outputs
188 .values()
189 .map(|e| PathBuf::from(e.path_str()[1..].to_owned()))
190 .collect(),
191
192 environment_vars: environment_vars
194 .into_iter()
195 .map(|(key, value)| EnvVar {
196 key,
197 value: Bytes::from(value),
198 })
199 .collect(),
200 inputs: inputs
201 .iter()
202 .map(|(path, node)| {
203 (
204 path.to_string()
205 .as_str()
206 .try_into()
207 .expect("Snix bug: unable to convert store path basename to PathComponent"),
208 node.clone(),
209 )
210 })
211 .collect(),
212 inputs_dir: nix_compat::store_path::STORE_DIR[1..].into(),
213 constraints,
214 working_dir: "build".into(),
215 scratch_paths: vec![
216 "build".into(),
217 "nix/store".into(),
223 ],
224 additional_files: additional_files
225 .into_iter()
226 .map(|(path, contents)| AdditionalFile {
227 path: PathBuf::from(path),
228 contents,
229 })
230 .collect(),
231 })
232}
233
234fn handle_pass_as_file(
239 environment_vars: &mut BTreeMap<String, Vec<u8>>,
240 additional_files: &mut BTreeMap<String, Bytes>,
241) -> std::io::Result<()> {
242 let pass_as_file = environment_vars.get("passAsFile").map(|v| {
243 String::from_utf8(v.to_vec())
247 });
248
249 if let Some(pass_as_file) = pass_as_file {
250 let pass_as_file = pass_as_file.map_err(|_| {
251 std::io::Error::new(
252 std::io::ErrorKind::InvalidInput,
253 "passAsFile elements are no valid utf8 strings",
254 )
255 })?;
256
257 for x in pass_as_file.split(' ') {
258 match environment_vars.remove_entry(x) {
259 Some((k, contents)) => {
260 let (new_k, path) = calculate_pass_as_file_env(&k);
261
262 additional_files.insert(path[1..].to_string(), Bytes::from(contents));
263 environment_vars.insert(new_k, path.into());
264 }
265 None => {
266 return Err(std::io::Error::new(
267 std::io::ErrorKind::InvalidData,
268 "passAsFile refers to non-existent env key",
269 ));
270 }
271 }
272 }
273 }
274
275 Ok(())
276}
277
278fn calculate_pass_as_file_env(k: &str) -> (String, String) {
283 (
284 format!("{k}Path"),
285 format!("/build/.attr-{}", nixbase32::encode(&Sha256::digest(k))),
286 )
287}
288
289fn replace_placeholders(s: &str, outputs: &BTreeMap<String, Output>) -> String {
291 let mut s = s.to_owned();
292 for (out_name, output) in outputs {
293 let placeholder = hash_placeholder(out_name.as_str());
294 if let Some(path) = output.path.as_ref() {
295 s = s.replace(&placeholder, &path.to_absolute_path());
296 } else {
297 warn!(
298 output.name = out_name,
299 "output should have a path during placeholder replacement"
300 );
301 }
302 }
303 s
304}
305
306fn replace_placeholders_b(s: &BString, outputs: &BTreeMap<String, Output>) -> BString {
308 use bstr::ByteSlice;
309 let mut s = s.clone();
310 for (out_name, output) in outputs {
311 let placeholder = hash_placeholder(out_name.as_str());
312 if let Some(path) = output.path.as_ref() {
313 s = s
314 .replace(placeholder.as_bytes(), path.to_absolute_path().as_bytes())
315 .into();
316 } else {
317 warn!(
318 output.name = out_name,
319 "output should have a path during placeholder replacement"
320 );
321 }
322 }
323 s
324}
325
326#[cfg(test)]
327mod test {
328 use bytes::Bytes;
329 use nix_compat::store_path::hash_placeholder;
330 use nix_compat::{derivation::Derivation, store_path::StorePath};
331 use snix_castore::fixtures::DUMMY_DIGEST;
332 use snix_castore::{Node, PathComponent};
333 use std::collections::{BTreeMap, HashSet};
334 use std::sync::LazyLock;
335
336 use snix_build::buildservice::{AdditionalFile, BuildConstraints, BuildRequest, EnvVar};
337
338 use crate::builder::NIX_ENVIRONMENT_VARS;
339 use crate::known_paths::KnownPaths;
340
341 use super::derivation_into_build_request;
342
343 static INPUT_NODE_FOO_NAME: LazyLock<Bytes> =
344 LazyLock::new(|| "mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar".into());
345
346 static INPUT_NODE_FOO: LazyLock<Node> = LazyLock::new(|| Node::Directory {
347 digest: *DUMMY_DIGEST,
348 size: 42,
349 });
350
351 #[test]
352 fn test_derivation_to_build_request() {
353 let aterm_bytes = include_bytes!("../tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv");
354
355 let dep_drv_bytes = include_bytes!("../tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv");
356
357 let derivation1 = Derivation::from_aterm_bytes(aterm_bytes).expect("drv1 must parse");
358 let drv_path1 =
359 StorePath::<String>::from_bytes("ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv".as_bytes())
360 .expect("drv path1 must parse");
361 let derivation2 = Derivation::from_aterm_bytes(dep_drv_bytes).expect("drv2 must parse");
362 let drv_path2 =
363 StorePath::<String>::from_bytes("ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv".as_bytes())
364 .expect("drv path2 must parse");
365
366 let mut known_paths = KnownPaths::default();
367
368 known_paths.add_derivation(drv_path2, derivation2);
369 known_paths.add_derivation(drv_path1, derivation1.clone());
370
371 let build_request = derivation_into_build_request(
372 derivation1.clone(),
373 &BTreeMap::from([(
374 StorePath::<String>::from_bytes(&INPUT_NODE_FOO_NAME.clone()).unwrap(),
375 INPUT_NODE_FOO.clone(),
376 )]),
377 )
378 .expect("must succeed");
379
380 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
381 expected_environment_vars.extend([
382 ("bar", "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),
383 ("builder", ":"),
384 ("name", "foo"),
385 ("out", "/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo"),
386 ("system", ":"),
387 ]);
388
389 assert_eq!(
390 BuildRequest {
391 command_args: vec![":".into()],
392 outputs: vec!["nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo".into()],
393 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
394 |(k, v)| EnvVar {
395 key: k.into(),
396 value: v.into(),
397 }
398 )),
399 inputs: BTreeMap::from([(
400 PathComponent::try_from(INPUT_NODE_FOO_NAME.clone()).unwrap(),
401 INPUT_NODE_FOO.clone()
402 )]),
403 inputs_dir: "nix/store".into(),
404 constraints: HashSet::from([
405 BuildConstraints::System(derivation1.system.to_owned()),
406 BuildConstraints::ProvideBinSh
407 ]),
408 additional_files: vec![],
409 working_dir: "build".into(),
410 scratch_paths: vec!["build".into(), "nix/store".into()],
411 refscan_needles: vec![
412 "fhaj6gmwns62s6ypkcldbaj2ybvkhx3p".into(),
413 "mp57d33657rf34lzvlbpfa1gjfv5gmpg".into()
414 ],
415 },
416 build_request
417 );
418 }
419
420 #[test]
421 fn test_drv_with_placeholders_to_build_request() {
422 let aterm_bytes =
423 include_bytes!("../tests/18m7y1d025lqgrzx8ypnhjbvq23z2kda-with-placeholders.drv");
424 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
425
426 let mut expected_environment_vars: BTreeMap<&str, String> =
427 BTreeMap::from_iter(NIX_ENVIRONMENT_VARS.map(|(k, v)| (k, v.to_owned())));
428
429 expected_environment_vars.extend([
430 (
431 "FOO",
432 "/nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".to_owned(),
433 ),
434 (
435 "BAR",
436 hash_placeholder("non-existent"),
438 ),
439 ("builder", "/bin/sh".to_owned()),
440 ("name", "with-placeholders".to_owned()),
441 (
442 "out",
443 "/nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".to_owned(),
444 ),
445 ("system", "x86_64-linux".to_owned()),
446 ]);
447
448 let exp_build_request = BuildRequest {
449 command_args: vec![
450 "/bin/sh".into(),
451 "-c".into(),
452 "/nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".into(),
453 hash_placeholder("non-existent"),
455 ],
456 outputs: vec!["nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".into()],
457 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
458 |(k, v)| EnvVar {
459 key: k.into(),
460 value: v.into(),
461 },
462 )),
463 inputs: BTreeMap::new(),
464 inputs_dir: "nix/store".into(),
465 constraints: HashSet::from([
466 BuildConstraints::System(derivation.system.clone()),
467 BuildConstraints::System(derivation.system.to_owned()),
468 BuildConstraints::ProvideBinSh,
469 ]),
470 additional_files: vec![],
471 working_dir: "build".into(),
472 scratch_paths: vec!["build".into(), "nix/store".into()],
473 refscan_needles: vec!["dgapb8kh5gis4w7hzfl5725sx5gam0nz".into()],
474 };
475
476 assert_eq!(
477 exp_build_request,
478 derivation_into_build_request(derivation.clone(), &BTreeMap::from([]))
479 .expect("must succeed"),
480 );
481 }
482
483 #[test]
484 fn test_fod_to_build_request() {
485 let aterm_bytes = include_bytes!("../tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv");
486
487 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
488
489 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
490 expected_environment_vars.extend([
491 ("builder", ":"),
492 ("name", "bar"),
493 ("out", "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),
494 (
495 "outputHash",
496 "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
497 ),
498 ("outputHashAlgo", "sha256"),
499 ("outputHashMode", "recursive"),
500 ("system", ":"),
501 ]);
502
503 let exp_build_request = BuildRequest {
504 command_args: vec![":".to_string()],
505 outputs: vec!["nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".into()],
506 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
507 |(k, v)| EnvVar {
508 key: k.into(),
509 value: v.into(),
510 },
511 )),
512 inputs: BTreeMap::new(),
513 inputs_dir: "nix/store".into(),
514 constraints: HashSet::from([
515 BuildConstraints::System(derivation.system.to_owned()),
516 BuildConstraints::System(derivation.system.to_owned()),
517 BuildConstraints::NetworkAccess,
518 BuildConstraints::ProvideBinSh,
519 ]),
520 additional_files: vec![],
521 working_dir: "build".into(),
522 scratch_paths: vec!["build".into(), "nix/store".into()],
523 refscan_needles: vec!["4q0pg5zpfmznxscq3avycvf9xdvx50n3".into()],
524 };
525
526 assert_eq!(
527 exp_build_request,
528 derivation_into_build_request(derivation, &BTreeMap::from([])).expect("must succeed")
529 );
530 }
531
532 #[test]
533 fn test_pass_as_file() {
534 let aterm_bytes = r#"Derive([("out","/nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo","","")],[],[],":",":",[],[("bar","baz"),("baz","bar"),("builder",":"),("name","foo"),("out","/nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo"),("passAsFile","bar baz"),("system",":")])"#.as_bytes();
536
537 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
538
539 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
540 expected_environment_vars.extend([
541 (
544 "barPath",
545 "/build/.attr-1fcgpy7vc4ammr7s17j2xq88scswkgz23dqzc04g8sx5vcp2pppw",
546 ),
547 (
548 "bazPath",
549 "/build/.attr-15l04iksj1280dvhbzdq9ai3wlf8ac2188m9qv0gn81k9nba19ds",
550 ),
551 ("builder", ":"),
552 ("name", "foo"),
553 ("out", "/nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo"),
554 ("passAsFile", "bar baz"),
556 ("system", ":"),
557 ]);
558
559 let exp_build_request = BuildRequest {
560 command_args: vec![":".to_string()],
561 outputs: vec!["nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo".into()],
562 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
563 |(k, v)| EnvVar {
564 key: k.into(),
565 value: v.into(),
566 },
567 )),
568 inputs: BTreeMap::new(),
569 inputs_dir: "nix/store".into(),
570 constraints: HashSet::from([
571 BuildConstraints::System(derivation.system.to_owned()),
572 BuildConstraints::ProvideBinSh,
573 ]),
574 additional_files: vec![
575 AdditionalFile {
577 path: "build/.attr-15l04iksj1280dvhbzdq9ai3wlf8ac2188m9qv0gn81k9nba19ds".into(),
578 contents: "bar".into(),
579 },
580 AdditionalFile {
582 path: "build/.attr-1fcgpy7vc4ammr7s17j2xq88scswkgz23dqzc04g8sx5vcp2pppw".into(),
583 contents: "baz".into(),
584 },
585 ],
586 working_dir: "build".into(),
587 scratch_paths: vec!["build".into(), "nix/store".into()],
588 refscan_needles: vec!["pp17lwra2jkx8rha15qabg2q3wij72lj".into()],
589 };
590
591 assert_eq!(
592 exp_build_request,
593 derivation_into_build_request(derivation, &BTreeMap::from([])).expect("must succeed")
594 );
595 }
596
597 #[test]
598 fn test_structured_attrs() {
599 let aterm_bytes = r#"Derive([("out","/nix/store/s92b6ykfzn3d8z0479r56x9f23bsyl92-script.sh","","")],[("/nix/store/azd4vaik6ssl5d411m5fsa757sic630r-bash-interactive-5.3p3.drv",["out"]),("/nix/store/m507z3g5zq4lv5x99rqb6dfdh5m0xixx-coreutils-9.8.drv",["out"])],[],"x86_64-linux","/nix/store/35yc81pz0q5yba14lxhn5r3jx5yg6c3l-bash-interactive-5.3p3/bin/bash",["-xc","source ${NIX_ATTRS_SH_FILE:-/dev/null}; cat ${NIX_ATTRS_JSON_FILE:-/dev/null}; out=${out:-${outputs[out]}}; cat ${NIX_ATTRS_JSON_FILE:-/dev/null} >$out; exit 0"],[("__json","{\"\":\"bar\",\"PATH\":\"/nix/store/imad8dvhp77h0pjbckp6wvmnyhp8dpgg-coreutils-9.8/bin\",\"builder\":\"/nix/store/35yc81pz0q5yba14lxhn5r3jx5yg6c3l-bash-interactive-5.3p3/bin/bash\",\"k\":{\"b\":1.0,\"bar\":true,\"c\":false,\"d\":true},\"l\":42,\"m\":false,\"n\":1.1,\"name\":\"script.sh\",\"system\":\"x86_64-linux\"}"),("out","/nix/store/s92b6ykfzn3d8z0479r56x9f23bsyl92-script.sh")])"#.as_bytes();
601
602 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
603
604 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
605 expected_environment_vars.extend([
606 ("NIX_ATTRS_JSON_FILE", "/build/.attrs.json"),
607 ("NIX_ATTRS_SH_FILE", "/build/.attrs.sh"),
608 ]);
628
629 let exp_build_request = BuildRequest {
630 command_args: vec![
631 "/nix/store/35yc81pz0q5yba14lxhn5r3jx5yg6c3l-bash-interactive-5.3p3/bin/bash".to_string(),
632 "-xc".to_string(),
633 r#"source ${NIX_ATTRS_SH_FILE:-/dev/null}; cat ${NIX_ATTRS_JSON_FILE:-/dev/null}; out=${out:-${outputs[out]}}; cat ${NIX_ATTRS_JSON_FILE:-/dev/null} >$out; exit 0"#.to_string()
634 ],
635 outputs: vec!["nix/store/s92b6ykfzn3d8z0479r56x9f23bsyl92-script.sh".into()],
636 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
637 |(k, v)| EnvVar {
638 key: k.into(),
639 value: v.into(),
640 }
641 )),
642 inputs: BTreeMap::new(),
643 inputs_dir: "nix/store".into(),
644 constraints: HashSet::from([
645 BuildConstraints::System(derivation.system.to_owned()),
646 BuildConstraints::ProvideBinSh,
647 ]),
648 additional_files: vec![
649 AdditionalFile {
650 path: "/build/.attrs.json".into(),
651 contents: Bytes::from_static(br#"{"":"bar","PATH":"/nix/store/imad8dvhp77h0pjbckp6wvmnyhp8dpgg-coreutils-9.8/bin","builder":"/nix/store/35yc81pz0q5yba14lxhn5r3jx5yg6c3l-bash-interactive-5.3p3/bin/bash","k":{"b":1.0,"bar":true,"c":false,"d":true},"l":42,"m":false,"n":1.1,"name":"script.sh","outputs":{"out":"/nix/store/s92b6ykfzn3d8z0479r56x9f23bsyl92-script.sh"},"system":"x86_64-linux"}"#)
652 },
653 AdditionalFile {
654 path: "/build/.attrs.sh".into(),
655 contents: Bytes::from_static(br#"declare PATH='/nix/store/imad8dvhp77h0pjbckp6wvmnyhp8dpgg-coreutils-9.8/bin'
656declare builder='/nix/store/35yc81pz0q5yba14lxhn5r3jx5yg6c3l-bash-interactive-5.3p3/bin/bash'
657declare -A k=(['b']=1 ['bar']=1 ['c']= ['d']=1 )
658declare l=42
659declare m=
660declare name='script.sh'
661declare -A outputs=(['out']='/nix/store/s92b6ykfzn3d8z0479r56x9f23bsyl92-script.sh' )
662declare system='x86_64-linux'
663"#)
664 }
665 ],
666 working_dir: "build".into(),
667 scratch_paths: vec!["build".into(), "nix/store".into()],
668 refscan_needles: vec!["s92b6ykfzn3d8z0479r56x9f23bsyl92".into()],
669 };
670
671 assert_eq!(
672 exp_build_request,
673 derivation_into_build_request(derivation, &BTreeMap::from([])).expect("must succeed")
674 );
675 }
676}