1use std::{
3 fs,
4 path::{Path, PathBuf},
5};
6
7use super::scratch_name;
8use crate::buildservice::BuildRequest;
9use anyhow::{Context, bail};
10use tracing::{debug, instrument};
11
12#[instrument(err)]
15pub(crate) fn make_bundle<'a>(
16 request: &BuildRequest,
17 runtime_spec: &oci_spec::runtime::Spec,
18 path: &Path,
19) -> anyhow::Result<()> {
20 fs::create_dir_all(path).context("failed to create bundle path")?;
21
22 let spec_json = serde_json::to_string(runtime_spec).context("failed to render spec to json")?;
23 fs::write(path.join("config.json"), spec_json).context("failed to write config.json")?;
24
25 fs::create_dir_all(path.join("inputs")).context("failed to create inputs dir")?;
26
27 let root_path = path.join("root");
28
29 fs::create_dir_all(&root_path).context("failed to create root path dir")?;
30 fs::create_dir_all(root_path.join("etc")).context("failed to create root/etc dir")?;
31
32 let scratch_root = path.join("scratch");
35 fs::create_dir_all(&scratch_root).context("failed to create scratch/ dir")?;
36
37 for p in request.scratch_paths.iter() {
40 let scratch_path = scratch_root.join(scratch_name(p));
41 debug!(scratch_path=?scratch_path, path=?p, "about to create scratch dir");
42 fs::create_dir_all(scratch_path).context("Unable to create scratch dir")?;
43 }
44
45 Ok(())
46}
47
48pub(crate) fn get_host_output_paths(
54 request: &BuildRequest,
55 bundle_path: &Path,
56) -> anyhow::Result<Vec<PathBuf>> {
57 let scratch_root = bundle_path.join("scratch");
58
59 let mut host_output_paths: Vec<PathBuf> = Vec::with_capacity(request.outputs.len());
60
61 for output_path in request.outputs.iter() {
62 if let Some((mp, relpath)) = find_path_in_scratchs(output_path, &request.scratch_paths) {
64 host_output_paths.push(scratch_root.join(scratch_name(mp)).join(relpath));
65 } else {
66 bail!("unable to find path {output_path:?}");
67 }
68 }
69
70 Ok(host_output_paths)
71}
72
73fn find_path_in_scratchs<'a, 'b, I>(
79 search_path: &'a Path,
80 mountpoints: I,
81) -> Option<(&'b Path, &'a Path)>
82where
83 I: IntoIterator<Item = &'b PathBuf>,
84 I::IntoIter: DoubleEndedIterator,
85{
86 mountpoints
87 .into_iter()
88 .rev()
89 .find_map(|mp| Some((mp.as_path(), search_path.strip_prefix(mp).ok()?)))
90}
91
92#[cfg(test)]
93mod tests {
94 use std::path::{Path, PathBuf};
95
96 use rstest::rstest;
97
98 use crate::{buildservice::BuildRequest, oci::scratch_name};
99
100 use super::{find_path_in_scratchs, get_host_output_paths};
101
102 #[rstest]
103 #[case::simple("nix/store/aaaa", &["nix/store".into()], Some(("nix/store", "aaaa")))]
104 #[case::prefix_no_sep("nix/store/aaaa", &["nix/sto".into()], None)]
105 #[case::not_found("nix/store/aaaa", &["build".into()], None)]
106 fn test_test_find_path_in_scratchs(
107 #[case] search_path: &str,
108 #[case] mountpoints: &[String],
109 #[case] expected: Option<(&str, &str)>,
110 ) {
111 let expected = expected.map(|e| (Path::new(e.0), Path::new(e.1)));
112 assert_eq!(
113 find_path_in_scratchs(
114 Path::new(search_path),
115 mountpoints
116 .iter()
117 .map(PathBuf::from)
118 .collect::<Vec<_>>()
119 .as_slice()
120 ),
121 expected
122 );
123 }
124
125 #[test]
126 fn test_get_host_output_paths_simple() {
127 let request = BuildRequest {
128 outputs: vec!["nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo".into()],
129 scratch_paths: vec!["build".into(), "nix/store".into()],
130 ..Default::default()
131 };
132
133 let paths =
134 get_host_output_paths(&request, Path::new("bundle-root")).expect("must succeed");
135
136 let mut expected_path = PathBuf::new();
137 expected_path.push("bundle-root");
138 expected_path.push("scratch");
139 expected_path.push(scratch_name(Path::new("nix/store")));
140 expected_path.push("fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo");
141
142 assert_eq!(vec![expected_path], paths)
143 }
144}