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 command_args: Vec<String> = Vec::from_iter(
105 std::iter::once(&derivation.builder)
106 .chain(&derivation.arguments)
107 .map(|s| replace_placeholders(s, &derivation.outputs)),
108 );
109
110 let mut environment_vars: BTreeMap<String, Vec<u8>> = BTreeMap::new();
114 let mut additional_files: BTreeMap<String, Bytes> = BTreeMap::new();
115
116 environment_vars.extend(
118 NIX_ENVIRONMENT_VARS
119 .iter()
120 .map(|(k, v)| (k.to_string(), v.to_owned().into())),
121 );
122
123 if let Some(json_str) = derivation.environment.remove(structured_attrs::JSON_KEY) {
124 let json_str = replace_placeholders_b(&json_str, &derivation.outputs);
126 handle_structured_attrs(
127 &json_str,
128 derivation.outputs.iter().map(|(out_name, output)| {
129 (
130 out_name.as_str(),
131 output
132 .path
133 .as_ref()
134 .expect("Snix bug: output has no path")
135 .as_ref(),
136 )
137 }),
138 &mut environment_vars,
139 &mut additional_files,
140 )?;
141 } else {
142 environment_vars.extend(derivation.environment.into_iter().map(|(k, v)| {
145 (
146 k.clone(),
147 replace_placeholders_b(&v, &derivation.outputs).into(),
148 )
149 }));
150
151 handle_pass_as_file(&mut environment_vars, &mut additional_files)?;
153 }
154
155 let mut constraints = HashSet::from([
157 BuildConstraints::System(derivation.system.to_owned()),
158 BuildConstraints::ProvideBinSh,
159 ]);
160
161 if derivation.outputs.len() == 1
162 && derivation
163 .outputs
164 .get("out")
165 .expect("Snix bug: Derivation has no out output")
166 .is_fixed()
167 {
168 constraints.insert(BuildConstraints::NetworkAccess);
169 }
170
171 Ok(BuildRequest {
172 refscan_needles: derivation
175 .outputs
176 .values()
177 .filter_map(|output| output.path.as_ref())
178 .map(|path| nixbase32::encode(path.digest()))
179 .chain(inputs.keys().map(|path| nixbase32::encode(path.digest())))
180 .collect(),
181 command_args,
182
183 outputs: derivation
184 .outputs
185 .values()
186 .map(|e| PathBuf::from(e.path_str()[1..].to_owned()))
187 .collect(),
188
189 environment_vars: environment_vars
191 .into_iter()
192 .map(|(key, value)| EnvVar {
193 key,
194 value: Bytes::from(value),
195 })
196 .collect(),
197 inputs: inputs
198 .iter()
199 .map(|(path, node)| {
200 (
201 path.to_string()
202 .as_str()
203 .try_into()
204 .expect("Snix bug: unable to convert store path basename to PathComponent"),
205 node.clone(),
206 )
207 })
208 .collect(),
209 inputs_dir: nix_compat::store_path::STORE_DIR[1..].into(),
210 constraints,
211 working_dir: "build".into(),
212 scratch_paths: vec![
213 "build".into(),
214 "nix/store".into(),
220 ],
221 additional_files: additional_files
222 .into_iter()
223 .map(|(path, contents)| AdditionalFile {
224 path: PathBuf::from(path),
225 contents,
226 })
227 .collect(),
228 })
229}
230
231fn handle_pass_as_file(
236 environment_vars: &mut BTreeMap<String, Vec<u8>>,
237 additional_files: &mut BTreeMap<String, Bytes>,
238) -> std::io::Result<()> {
239 let pass_as_file = environment_vars.get("passAsFile").map(|v| {
240 String::from_utf8(v.to_vec())
244 });
245
246 if let Some(pass_as_file) = pass_as_file {
247 let pass_as_file = pass_as_file.map_err(|_| {
248 std::io::Error::new(
249 std::io::ErrorKind::InvalidInput,
250 "passAsFile elements are no valid utf8 strings",
251 )
252 })?;
253
254 for x in pass_as_file.split(' ') {
255 match environment_vars.remove_entry(x) {
256 Some((k, contents)) => {
257 let (new_k, path) = calculate_pass_as_file_env(&k);
258
259 additional_files.insert(path[1..].to_string(), Bytes::from(contents));
260 environment_vars.insert(new_k, path.into());
261 }
262 None => {
263 return Err(std::io::Error::new(
264 std::io::ErrorKind::InvalidData,
265 "passAsFile refers to non-existent env key",
266 ));
267 }
268 }
269 }
270 }
271
272 Ok(())
273}
274
275fn calculate_pass_as_file_env(k: &str) -> (String, String) {
280 (
281 format!("{k}Path"),
282 format!("/build/.attr-{}", nixbase32::encode(&Sha256::digest(k))),
283 )
284}
285
286fn replace_placeholders(s: &str, outputs: &BTreeMap<String, Output>) -> String {
288 let mut s = s.to_owned();
289 for (out_name, output) in outputs {
290 let placeholder = hash_placeholder(out_name.as_str());
291 if let Some(path) = output.path.as_ref() {
292 s = s.replace(&placeholder, &path.to_absolute_path());
293 } else {
294 warn!(
295 output.name = out_name,
296 "output should have a path during placeholder replacement"
297 );
298 }
299 }
300 s
301}
302
303fn replace_placeholders_b(s: &BString, outputs: &BTreeMap<String, Output>) -> BString {
305 use bstr::ByteSlice;
306 let mut s = s.clone();
307 for (out_name, output) in outputs {
308 let placeholder = hash_placeholder(out_name.as_str());
309 if let Some(path) = output.path.as_ref() {
310 s = s
311 .replace(placeholder.as_bytes(), path.to_absolute_path().as_bytes())
312 .into();
313 } else {
314 warn!(
315 output.name = out_name,
316 "output should have a path during placeholder replacement"
317 );
318 }
319 }
320 s
321}
322
323#[cfg(test)]
324mod test {
325 use bytes::Bytes;
326 use nix_compat::store_path::hash_placeholder;
327 use nix_compat::{derivation::Derivation, store_path::StorePath};
328 use snix_castore::fixtures::DUMMY_DIGEST;
329 use snix_castore::{Node, PathComponent};
330 use std::collections::{BTreeMap, HashSet};
331 use std::sync::LazyLock;
332
333 use snix_build::buildservice::{AdditionalFile, BuildConstraints, BuildRequest, EnvVar};
334
335 use crate::builder::NIX_ENVIRONMENT_VARS;
336 use crate::known_paths::KnownPaths;
337
338 use super::derivation_into_build_request;
339
340 static INPUT_NODE_FOO_NAME: LazyLock<Bytes> =
341 LazyLock::new(|| "mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar".into());
342
343 static INPUT_NODE_FOO: LazyLock<Node> = LazyLock::new(|| Node::Directory {
344 digest: *DUMMY_DIGEST,
345 size: 42,
346 });
347
348 #[test]
349 fn test_derivation_to_build_request() {
350 let aterm_bytes = include_bytes!("../tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv");
351
352 let dep_drv_bytes = include_bytes!("../tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv");
353
354 let derivation1 = Derivation::from_aterm_bytes(aterm_bytes).expect("drv1 must parse");
355 let drv_path1 =
356 StorePath::<String>::from_bytes("ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv".as_bytes())
357 .expect("drv path1 must parse");
358 let derivation2 = Derivation::from_aterm_bytes(dep_drv_bytes).expect("drv2 must parse");
359 let drv_path2 =
360 StorePath::<String>::from_bytes("ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv".as_bytes())
361 .expect("drv path2 must parse");
362
363 let mut known_paths = KnownPaths::default();
364
365 known_paths.add_derivation(drv_path2, derivation2);
366 known_paths.add_derivation(drv_path1, derivation1.clone());
367
368 let build_request = derivation_into_build_request(
369 derivation1.clone(),
370 &BTreeMap::from([(
371 StorePath::<String>::from_bytes(&INPUT_NODE_FOO_NAME.clone()).unwrap(),
372 INPUT_NODE_FOO.clone(),
373 )]),
374 )
375 .expect("must succeed");
376
377 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
378 expected_environment_vars.extend([
379 ("bar", "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),
380 ("builder", ":"),
381 ("name", "foo"),
382 ("out", "/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo"),
383 ("system", ":"),
384 ]);
385
386 assert_eq!(
387 BuildRequest {
388 command_args: vec![":".into()],
389 outputs: vec!["nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo".into()],
390 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
391 |(k, v)| EnvVar {
392 key: k.into(),
393 value: v.into(),
394 }
395 )),
396 inputs: BTreeMap::from([(
397 PathComponent::try_from(INPUT_NODE_FOO_NAME.clone()).unwrap(),
398 INPUT_NODE_FOO.clone()
399 )]),
400 inputs_dir: "nix/store".into(),
401 constraints: HashSet::from([
402 BuildConstraints::System(derivation1.system.to_owned()),
403 BuildConstraints::ProvideBinSh
404 ]),
405 additional_files: vec![],
406 working_dir: "build".into(),
407 scratch_paths: vec!["build".into(), "nix/store".into()],
408 refscan_needles: vec![
409 "fhaj6gmwns62s6ypkcldbaj2ybvkhx3p".into(),
410 "mp57d33657rf34lzvlbpfa1gjfv5gmpg".into()
411 ],
412 },
413 build_request
414 );
415 }
416
417 #[test]
418 fn test_drv_with_placeholders_to_build_request() {
419 let aterm_bytes =
420 include_bytes!("../tests/18m7y1d025lqgrzx8ypnhjbvq23z2kda-with-placeholders.drv");
421 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
422
423 let mut expected_environment_vars: BTreeMap<&str, String> =
424 BTreeMap::from_iter(NIX_ENVIRONMENT_VARS.map(|(k, v)| (k, v.to_owned())));
425
426 expected_environment_vars.extend([
427 (
428 "FOO",
429 "/nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".to_owned(),
430 ),
431 (
432 "BAR",
433 hash_placeholder("non-existent"),
435 ),
436 ("builder", "/bin/sh".to_owned()),
437 ("name", "with-placeholders".to_owned()),
438 (
439 "out",
440 "/nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".to_owned(),
441 ),
442 ("system", "x86_64-linux".to_owned()),
443 ]);
444
445 let exp_build_request = BuildRequest {
446 command_args: vec![
447 "/bin/sh".into(),
448 "-c".into(),
449 "/nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".into(),
450 hash_placeholder("non-existent"),
452 ],
453 outputs: vec!["nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".into()],
454 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
455 |(k, v)| EnvVar {
456 key: k.into(),
457 value: v.into(),
458 },
459 )),
460 inputs: BTreeMap::new(),
461 inputs_dir: "nix/store".into(),
462 constraints: HashSet::from([
463 BuildConstraints::System(derivation.system.clone()),
464 BuildConstraints::System(derivation.system.to_owned()),
465 BuildConstraints::ProvideBinSh,
466 ]),
467 additional_files: vec![],
468 working_dir: "build".into(),
469 scratch_paths: vec!["build".into(), "nix/store".into()],
470 refscan_needles: vec!["dgapb8kh5gis4w7hzfl5725sx5gam0nz".into()],
471 };
472
473 assert_eq!(
474 exp_build_request,
475 derivation_into_build_request(derivation.clone(), &BTreeMap::from([]))
476 .expect("must succeed"),
477 );
478 }
479
480 #[test]
481 fn test_fod_to_build_request() {
482 let aterm_bytes = include_bytes!("../tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv");
483
484 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
485
486 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
487 expected_environment_vars.extend([
488 ("builder", ":"),
489 ("name", "bar"),
490 ("out", "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),
491 (
492 "outputHash",
493 "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
494 ),
495 ("outputHashAlgo", "sha256"),
496 ("outputHashMode", "recursive"),
497 ("system", ":"),
498 ]);
499
500 let exp_build_request = BuildRequest {
501 command_args: vec![":".to_string()],
502 outputs: vec!["nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".into()],
503 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
504 |(k, v)| EnvVar {
505 key: k.into(),
506 value: v.into(),
507 },
508 )),
509 inputs: BTreeMap::new(),
510 inputs_dir: "nix/store".into(),
511 constraints: HashSet::from([
512 BuildConstraints::System(derivation.system.to_owned()),
513 BuildConstraints::System(derivation.system.to_owned()),
514 BuildConstraints::NetworkAccess,
515 BuildConstraints::ProvideBinSh,
516 ]),
517 additional_files: vec![],
518 working_dir: "build".into(),
519 scratch_paths: vec!["build".into(), "nix/store".into()],
520 refscan_needles: vec!["4q0pg5zpfmznxscq3avycvf9xdvx50n3".into()],
521 };
522
523 assert_eq!(
524 exp_build_request,
525 derivation_into_build_request(derivation, &BTreeMap::from([])).expect("must succeed")
526 );
527 }
528
529 #[test]
530 fn test_pass_as_file() {
531 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();
533
534 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
535
536 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
537 expected_environment_vars.extend([
538 (
541 "barPath",
542 "/build/.attr-1fcgpy7vc4ammr7s17j2xq88scswkgz23dqzc04g8sx5vcp2pppw",
543 ),
544 (
545 "bazPath",
546 "/build/.attr-15l04iksj1280dvhbzdq9ai3wlf8ac2188m9qv0gn81k9nba19ds",
547 ),
548 ("builder", ":"),
549 ("name", "foo"),
550 ("out", "/nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo"),
551 ("passAsFile", "bar baz"),
553 ("system", ":"),
554 ]);
555
556 let exp_build_request = BuildRequest {
557 command_args: vec![":".to_string()],
558 outputs: vec!["nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo".into()],
559 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
560 |(k, v)| EnvVar {
561 key: k.into(),
562 value: v.into(),
563 },
564 )),
565 inputs: BTreeMap::new(),
566 inputs_dir: "nix/store".into(),
567 constraints: HashSet::from([
568 BuildConstraints::System(derivation.system.to_owned()),
569 BuildConstraints::ProvideBinSh,
570 ]),
571 additional_files: vec![
572 AdditionalFile {
574 path: "build/.attr-15l04iksj1280dvhbzdq9ai3wlf8ac2188m9qv0gn81k9nba19ds".into(),
575 contents: "bar".into(),
576 },
577 AdditionalFile {
579 path: "build/.attr-1fcgpy7vc4ammr7s17j2xq88scswkgz23dqzc04g8sx5vcp2pppw".into(),
580 contents: "baz".into(),
581 },
582 ],
583 working_dir: "build".into(),
584 scratch_paths: vec!["build".into(), "nix/store".into()],
585 refscan_needles: vec!["pp17lwra2jkx8rha15qabg2q3wij72lj".into()],
586 };
587
588 assert_eq!(
589 exp_build_request,
590 derivation_into_build_request(derivation, &BTreeMap::from([])).expect("must succeed")
591 );
592 }
593
594 #[test]
595 fn test_structured_attrs() {
596 let aterm_bytes = r#"Derive([("out","/nix/store/knq92bscsfi5xzvhf8icj2kbwddkk5m4-script.sh","","")],[("/nix/store/l6bi2ln3vlv6mkkw95bvh09pgy4d3xra-coreutils-9.10.drv",["out"]),("/nix/store/s9b1a2zhv6l7x8ady5vfbj5kg8rkvznx-bash-interactive-5.3p9.drv",["out"])],[],"x86_64-linux","/nix/store/sfvyavxai6qvzmv9p9x6mp4wwdz4v41m-bash-interactive-5.3p9/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/74sind1d6vf2bfwd7yklg8chsvzqxmmq-coreutils-9.10/bin\",\"builder\":\"/nix/store/sfvyavxai6qvzmv9p9x6mp4wwdz4v41m-bash-interactive-5.3p9/bin/bash\",\"hello\":\"/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9\",\"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/knq92bscsfi5xzvhf8icj2kbwddkk5m4-script.sh")])"#.as_bytes();
598
599 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
600
601 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
602 expected_environment_vars.extend([
603 ("NIX_ATTRS_JSON_FILE", "/build/.attrs.json"),
604 ("NIX_ATTRS_SH_FILE", "/build/.attrs.sh"),
605 ]);
625
626 let exp_build_request = BuildRequest {
627 command_args: vec![
628 "/nix/store/sfvyavxai6qvzmv9p9x6mp4wwdz4v41m-bash-interactive-5.3p9/bin/bash".to_string(),
629 "-xc".to_string(),
630 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()
631 ],
632 outputs: vec!["nix/store/knq92bscsfi5xzvhf8icj2kbwddkk5m4-script.sh".into()],
633 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
634 |(k, v)| EnvVar {
635 key: k.into(),
636 value: v.into(),
637 }
638 )),
639 inputs: BTreeMap::new(),
640 inputs_dir: "nix/store".into(),
641 constraints: HashSet::from([
642 BuildConstraints::System(derivation.system.to_owned()),
643 BuildConstraints::ProvideBinSh,
644 ]),
645 additional_files: vec![
646 AdditionalFile {
647 path: "build/.attrs.json".into(),
648 contents: Bytes::from_static(br#"{"":"bar","PATH":"/nix/store/74sind1d6vf2bfwd7yklg8chsvzqxmmq-coreutils-9.10/bin","builder":"/nix/store/sfvyavxai6qvzmv9p9x6mp4wwdz4v41m-bash-interactive-5.3p9/bin/bash","hello":"/nix/store/knq92bscsfi5xzvhf8icj2kbwddkk5m4-script.sh","k":{"b":1.0,"bar":true,"c":false,"d":true},"l":42,"m":false,"n":1.1,"name":"script.sh","outputs":{"out":"/nix/store/knq92bscsfi5xzvhf8icj2kbwddkk5m4-script.sh"},"system":"x86_64-linux"}"#)
649 },
650 AdditionalFile {
651 path: "build/.attrs.sh".into(),
652 contents: Bytes::from_static(br#"declare PATH='/nix/store/74sind1d6vf2bfwd7yklg8chsvzqxmmq-coreutils-9.10/bin'
653declare builder='/nix/store/sfvyavxai6qvzmv9p9x6mp4wwdz4v41m-bash-interactive-5.3p9/bin/bash'
654declare hello='/nix/store/knq92bscsfi5xzvhf8icj2kbwddkk5m4-script.sh'
655declare -A k=(['b']=1 ['bar']=1 ['c']= ['d']=1 )
656declare l=42
657declare m=
658declare name='script.sh'
659declare -A outputs=(['out']='/nix/store/knq92bscsfi5xzvhf8icj2kbwddkk5m4-script.sh' )
660declare system='x86_64-linux'
661"#)
662 }
663 ],
664 working_dir: "build".into(),
665 scratch_paths: vec!["build".into(), "nix/store".into()],
666 refscan_needles: vec!["knq92bscsfi5xzvhf8icj2kbwddkk5m4".into()],
667 };
668
669 assert_eq!(
670 exp_build_request,
671 derivation_into_build_request(derivation, &BTreeMap::from([])).expect("must succeed")
672 );
673 }
674}