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