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