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::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;
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!(
291 "/build/.attr-{}",
292 nixbase32::encode(&Sha256::digest_bytes(k))
293 ),
294 )
295}
296
297fn replace_placeholders<'i, I>(s: &str, outputs: I) -> String
299where
300 I: IntoIterator<Item = (&'i OutputName, &'i Output)>,
301{
302 let mut s = s.to_owned();
303 for (out_name, output) in outputs {
304 let placeholder = hash_placeholder(out_name.as_str());
305 if let Some(path) = output.path.as_ref() {
306 s = s.replace(&placeholder, &path.to_absolute_path());
307 } else {
308 warn!(
309 output.name = %out_name,
310 "output should have a path during placeholder replacement"
311 );
312 }
313 }
314 s
315}
316
317fn replace_placeholders_b<'i, I>(s: &BString, outputs: I) -> BString
319where
320 I: IntoIterator<Item = (&'i OutputName, &'i Output)>,
321{
322 use bstr::ByteSlice;
323 let mut s = s.clone();
324 for (out_name, output) in outputs {
325 let placeholder = hash_placeholder(out_name.as_str());
326 if let Some(path) = output.path.as_ref() {
327 s = s
328 .replace(placeholder.as_bytes(), path.to_absolute_path().as_bytes())
329 .into();
330 } else {
331 warn!(
332 output.name = %out_name,
333 "output should have a path during placeholder replacement"
334 );
335 }
336 }
337 s
338}
339
340#[cfg(test)]
341mod test {
342 use bytes::Bytes;
343 use nix_compat::store_path::hash_placeholder;
344 use nix_compat::{derivation::Derivation, store_path::StorePath};
345 use snix_castore::fixtures::DUMMY_DIGEST;
346 use snix_castore::{Node, PathComponent};
347 use std::collections::{BTreeMap, HashSet};
348 use std::sync::LazyLock;
349
350 use snix_build::buildservice::{AdditionalFile, BuildConstraints, BuildRequest, EnvVar};
351
352 use crate::builder::NIX_ENVIRONMENT_VARS;
353 use crate::known_paths::KnownPaths;
354
355 use super::derivation_into_build_request;
356
357 static INPUT_NODE_FOO_NAME: LazyLock<Bytes> =
358 LazyLock::new(|| "mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar".into());
359
360 static INPUT_NODE_FOO: LazyLock<Node> = LazyLock::new(|| Node::Directory {
361 digest: *DUMMY_DIGEST,
362 size: 42,
363 });
364
365 #[test]
366 fn test_derivation_to_build_request() {
367 let aterm_bytes =
368 include_bytes!("../../test-data/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv");
369
370 let dep_drv_bytes =
371 include_bytes!("../../test-data/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv");
372
373 let derivation1 = Derivation::from_aterm_bytes(aterm_bytes).expect("drv1 must parse");
374 let drv_path1 =
375 StorePath::from_bytes("ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv".as_bytes())
376 .expect("drv path1 must parse");
377 let derivation2 = Derivation::from_aterm_bytes(dep_drv_bytes).expect("drv2 must parse");
378 let drv_path2 =
379 StorePath::from_bytes("ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv".as_bytes())
380 .expect("drv path2 must parse");
381
382 let mut known_paths = KnownPaths::default();
383
384 known_paths.add_derivation(drv_path2, derivation2);
385 known_paths.add_derivation(drv_path1, derivation1.clone());
386
387 let build_request = derivation_into_build_request(
388 derivation1.clone(),
389 &BTreeMap::from([(
390 StorePath::from_bytes(&INPUT_NODE_FOO_NAME.clone()).unwrap(),
391 INPUT_NODE_FOO.clone(),
392 )]),
393 )
394 .expect("must succeed");
395
396 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
397 expected_environment_vars.extend([
398 ("bar", "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),
399 ("builder", ":"),
400 ("name", "foo"),
401 ("out", "/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo"),
402 ("system", ":"),
403 ]);
404
405 assert_eq!(
406 BuildRequest {
407 command_args: vec![":".into()],
408 outputs: vec!["nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo".into()],
409 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
410 |(k, v)| EnvVar {
411 key: k.into(),
412 value: v.into(),
413 }
414 )),
415 inputs: BTreeMap::from([(
416 PathComponent::try_from(INPUT_NODE_FOO_NAME.clone()).unwrap(),
417 INPUT_NODE_FOO.clone()
418 )]),
419 inputs_dir: "nix/store".into(),
420 constraints: HashSet::from([
421 BuildConstraints::System(derivation1.system.to_owned()),
422 BuildConstraints::ProvideBinSh
423 ]),
424 additional_files: vec![],
425 working_dir: "build".into(),
426 scratch_paths: vec!["build".into(), "nix/store".into()],
427 refscan_needles: vec![
428 "fhaj6gmwns62s6ypkcldbaj2ybvkhx3p".into(),
429 "mp57d33657rf34lzvlbpfa1gjfv5gmpg".into()
430 ],
431 },
432 build_request
433 );
434 }
435
436 #[test]
437 fn test_drv_with_placeholders_to_build_request() {
438 let aterm_bytes = include_bytes!(
439 "../../test-data/18m7y1d025lqgrzx8ypnhjbvq23z2kda-with-placeholders.drv"
440 );
441 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
442
443 let mut expected_environment_vars: BTreeMap<&str, String> =
444 BTreeMap::from_iter(NIX_ENVIRONMENT_VARS.map(|(k, v)| (k, v.to_owned())));
445
446 expected_environment_vars.extend([
447 (
448 "FOO",
449 "/nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".to_owned(),
450 ),
451 (
452 "BAR",
453 hash_placeholder("non-existent"),
455 ),
456 ("builder", "/bin/sh".to_owned()),
457 ("name", "with-placeholders".to_owned()),
458 (
459 "out",
460 "/nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".to_owned(),
461 ),
462 ("system", "x86_64-linux".to_owned()),
463 ]);
464
465 let exp_build_request = BuildRequest {
466 command_args: vec![
467 "/bin/sh".into(),
468 "-c".into(),
469 "/nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".into(),
470 hash_placeholder("non-existent"),
472 ],
473 outputs: vec!["nix/store/dgapb8kh5gis4w7hzfl5725sx5gam0nz-with-placeholders".into()],
474 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
475 |(k, v)| EnvVar {
476 key: k.into(),
477 value: v.into(),
478 },
479 )),
480 inputs: BTreeMap::new(),
481 inputs_dir: "nix/store".into(),
482 constraints: HashSet::from([
483 BuildConstraints::System(derivation.system.clone()),
484 BuildConstraints::System(derivation.system.to_owned()),
485 BuildConstraints::ProvideBinSh,
486 ]),
487 additional_files: vec![],
488 working_dir: "build".into(),
489 scratch_paths: vec!["build".into(), "nix/store".into()],
490 refscan_needles: vec!["dgapb8kh5gis4w7hzfl5725sx5gam0nz".into()],
491 };
492
493 assert_eq!(
494 exp_build_request,
495 derivation_into_build_request(derivation.clone(), &BTreeMap::from([]))
496 .expect("must succeed"),
497 );
498 }
499
500 #[test]
501 fn test_fod_to_build_request() {
502 let aterm_bytes =
503 include_bytes!("../../test-data/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv");
504
505 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
506
507 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
508 expected_environment_vars.extend([
509 ("builder", ":"),
510 ("name", "bar"),
511 ("out", "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),
512 (
513 "outputHash",
514 "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
515 ),
516 ("outputHashAlgo", "sha256"),
517 ("outputHashMode", "recursive"),
518 ("system", ":"),
519 ]);
520
521 let exp_build_request = BuildRequest {
522 command_args: vec![":".to_string()],
523 outputs: vec!["nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".into()],
524 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
525 |(k, v)| EnvVar {
526 key: k.into(),
527 value: v.into(),
528 },
529 )),
530 inputs: BTreeMap::new(),
531 inputs_dir: "nix/store".into(),
532 constraints: HashSet::from([
533 BuildConstraints::System(derivation.system.to_owned()),
534 BuildConstraints::System(derivation.system.to_owned()),
535 BuildConstraints::NetworkAccess,
536 BuildConstraints::ProvideBinSh,
537 ]),
538 additional_files: vec![],
539 working_dir: "build".into(),
540 scratch_paths: vec!["build".into(), "nix/store".into()],
541 refscan_needles: vec!["4q0pg5zpfmznxscq3avycvf9xdvx50n3".into()],
542 };
543
544 assert_eq!(
545 exp_build_request,
546 derivation_into_build_request(derivation, &BTreeMap::from([])).expect("must succeed")
547 );
548 }
549
550 #[test]
551 fn test_pass_as_file() {
552 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();
554
555 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
556
557 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
558 expected_environment_vars.extend([
559 (
562 "barPath",
563 "/build/.attr-1fcgpy7vc4ammr7s17j2xq88scswkgz23dqzc04g8sx5vcp2pppw",
564 ),
565 (
566 "bazPath",
567 "/build/.attr-15l04iksj1280dvhbzdq9ai3wlf8ac2188m9qv0gn81k9nba19ds",
568 ),
569 ("builder", ":"),
570 ("name", "foo"),
571 ("out", "/nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo"),
572 ("passAsFile", "bar baz"),
574 ("system", ":"),
575 ]);
576
577 let exp_build_request = BuildRequest {
578 command_args: vec![":".to_string()],
579 outputs: vec!["nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo".into()],
580 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
581 |(k, v)| EnvVar {
582 key: k.into(),
583 value: v.into(),
584 },
585 )),
586 inputs: BTreeMap::new(),
587 inputs_dir: "nix/store".into(),
588 constraints: HashSet::from([
589 BuildConstraints::System(derivation.system.to_owned()),
590 BuildConstraints::ProvideBinSh,
591 ]),
592 additional_files: vec![
593 AdditionalFile {
595 path: "build/.attr-15l04iksj1280dvhbzdq9ai3wlf8ac2188m9qv0gn81k9nba19ds".into(),
596 contents: "bar".into(),
597 },
598 AdditionalFile {
600 path: "build/.attr-1fcgpy7vc4ammr7s17j2xq88scswkgz23dqzc04g8sx5vcp2pppw".into(),
601 contents: "baz".into(),
602 },
603 ],
604 working_dir: "build".into(),
605 scratch_paths: vec!["build".into(), "nix/store".into()],
606 refscan_needles: vec!["pp17lwra2jkx8rha15qabg2q3wij72lj".into()],
607 };
608
609 assert_eq!(
610 exp_build_request,
611 derivation_into_build_request(derivation, &BTreeMap::from([])).expect("must succeed")
612 );
613 }
614
615 #[test]
616 fn test_structured_attrs() {
617 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();
619
620 let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
621
622 let mut expected_environment_vars = BTreeMap::from_iter(NIX_ENVIRONMENT_VARS);
623 expected_environment_vars.extend([
624 ("NIX_ATTRS_JSON_FILE", "/build/.attrs.json"),
625 ("NIX_ATTRS_SH_FILE", "/build/.attrs.sh"),
626 ]);
646
647 let exp_build_request = BuildRequest {
648 command_args: vec![
649 "/nix/store/sfvyavxai6qvzmv9p9x6mp4wwdz4v41m-bash-interactive-5.3p9/bin/bash".to_string(),
650 "-xc".to_string(),
651 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()
652 ],
653 outputs: vec!["nix/store/knq92bscsfi5xzvhf8icj2kbwddkk5m4-script.sh".into()],
654 environment_vars: Vec::from_iter(expected_environment_vars.into_iter().map(
655 |(k, v)| EnvVar {
656 key: k.into(),
657 value: v.into(),
658 }
659 )),
660 inputs: BTreeMap::new(),
661 inputs_dir: "nix/store".into(),
662 constraints: HashSet::from([
663 BuildConstraints::System(derivation.system.to_owned()),
664 BuildConstraints::ProvideBinSh,
665 ]),
666 additional_files: vec![
667 AdditionalFile {
668 path: "build/.attrs.json".into(),
669 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"}"#)
670 },
671 AdditionalFile {
672 path: "build/.attrs.sh".into(),
673 contents: Bytes::from_static(br#"declare PATH='/nix/store/74sind1d6vf2bfwd7yklg8chsvzqxmmq-coreutils-9.10/bin'
674declare builder='/nix/store/sfvyavxai6qvzmv9p9x6mp4wwdz4v41m-bash-interactive-5.3p9/bin/bash'
675declare hello='/nix/store/knq92bscsfi5xzvhf8icj2kbwddkk5m4-script.sh'
676declare -A k=(['b']=1 ['bar']=1 ['c']= ['d']=1 )
677declare l=42
678declare m=
679declare name='script.sh'
680declare -A outputs=(['out']='/nix/store/knq92bscsfi5xzvhf8icj2kbwddkk5m4-script.sh' )
681declare system='x86_64-linux'
682"#)
683 }
684 ],
685 working_dir: "build".into(),
686 scratch_paths: vec!["build".into(), "nix/store".into()],
687 refscan_needles: vec!["knq92bscsfi5xzvhf8icj2kbwddkk5m4".into()],
688 };
689
690 assert_eq!(
691 exp_build_request,
692 derivation_into_build_request(derivation, &BTreeMap::from([])).expect("must succeed")
693 );
694 }
695}