snix_build_glue/
known_paths.rs1use hashbrown::HashMap;
12use nix_compat::{
13 derivation::Derivation,
14 store_path::{ParseStorePathError, StorePath, StorePathRef},
15};
16
17use crate::fetchers::Fetch;
18
19#[derive(Debug, Default)]
23pub struct KnownPaths {
24 derivations: HashMap<StorePath, ([u8; 32], Derivation)>,
29
30 outputs_to_drvpath: HashMap<StorePath, StorePath>,
34
35 outputs_to_fetches: HashMap<StorePath, (String, Fetch)>,
37}
38
39impl KnownPaths {
40 pub fn get_hash_derivation_modulo(&self, drv_path: &StorePathRef) -> Option<&[u8; 32]> {
42 self.derivations
43 .get(drv_path)
44 .map(|(hash_derivation_modulo, _derivation)| hash_derivation_modulo)
45 }
46
47 pub fn get_drv_by_drvpath(&self, drv_path: &StorePathRef) -> Option<&Derivation> {
49 self.derivations
50 .get(drv_path)
51 .map(|(_hash_derivation_modulo, derivation)| derivation)
52 }
53
54 pub fn get_drv_path_for_output_path(&self, output_path: &StorePathRef) -> Option<&StorePath> {
58 self.outputs_to_drvpath.get(output_path)
59 }
60
61 pub fn add_derivation(&mut self, drv_path: StorePath, drv: Derivation) {
67 #[cfg(debug_assertions)]
69 {
70 for input_drv_path in drv.input_derivations.keys() {
71 debug_assert!(self.derivations.contains_key(input_drv_path));
72 }
73 }
74
75 let hash_derivation_modulo = drv.hash_derivation_modulo(|drv_path| {
77 self.get_hash_derivation_modulo(drv_path)
78 .unwrap_or_else(|| panic!("{drv_path} not found"))
79 .to_owned()
80 });
81
82 for output in drv.outputs.values() {
85 self.outputs_to_drvpath
86 .entry(output.path.as_ref().expect("missing store path").clone())
87 .or_insert(drv_path.to_owned());
88 }
89
90 #[allow(unused_variables)] let old = self
93 .derivations
94 .insert(drv_path.to_owned(), (hash_derivation_modulo, drv));
95
96 #[cfg(debug_assertions)]
97 {
98 if let Some(old) = old {
99 debug_assert!(
100 old.0 == hash_derivation_modulo,
101 "hash derivation modulo for a given derivation should always be calculated the same"
102 );
103 }
104 }
105 }
106
107 pub fn add_fetch<'a>(
111 &mut self,
112 fetch: Fetch,
113 name: &'a str,
114 ) -> Result<StorePathRef<'a>, ParseStorePathError> {
115 let store_path = fetch
116 .store_path(name)?
117 .expect("Snix bug: fetch must have an expected hash");
118 self.outputs_to_fetches
120 .insert(store_path.to_owned(), (name.to_owned(), fetch));
121
122 Ok(store_path)
123 }
124
125 pub fn get_fetch_for_output_path(
128 &self,
129 output_path: &StorePathRef<'_>,
130 ) -> Option<(String, Fetch)> {
131 self.outputs_to_fetches
132 .get(output_path)
133 .map(|(name, fetch)| (name.to_owned(), fetch.to_owned()))
134 }
135
136 pub fn get_derivations<'a>(
138 &'a self,
139 ) -> impl Iterator<Item = (StorePathRef<'a>, &'a Derivation)> {
140 self.derivations.iter().map(|(k, v)| (k.as_ref(), &v.1))
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use std::sync::LazyLock;
147
148 use hex_literal::hex;
149 use nix_compat::{derivation::Derivation, nixbase32, nixhash::NixHash, store_path::StorePath};
150 use url::Url;
151
152 use super::KnownPaths;
153 use crate::fetchers::Fetch;
154
155 static BAR_DRV: LazyLock<Derivation> = LazyLock::new(|| {
156 Derivation::from_aterm_bytes(include_bytes!(
157 "../test-data/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv"
158 ))
159 .expect("must parse")
160 });
161
162 static FOO_DRV: LazyLock<Derivation> = LazyLock::new(|| {
163 Derivation::from_aterm_bytes(include_bytes!(
164 "../test-data/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv"
165 ))
166 .expect("must parse")
167 });
168
169 static BAR_DRV_PATH: LazyLock<StorePath> = LazyLock::new(|| {
170 StorePath::from_bytes(b"ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv").expect("must parse")
171 });
172
173 static FOO_DRV_PATH: LazyLock<StorePath> = LazyLock::new(|| {
174 StorePath::from_bytes(b"ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv").expect("must parse")
175 });
176
177 static BAR_OUT_PATH: LazyLock<StorePath> = LazyLock::new(|| {
178 StorePath::from_bytes(b"mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar").expect("must parse")
179 });
180
181 static FOO_OUT_PATH: LazyLock<StorePath> = LazyLock::new(|| {
182 StorePath::from_bytes(b"fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo").expect("must parse")
183 });
184
185 static FETCH_URL: LazyLock<Fetch> = LazyLock::new(|| {
186 Fetch::URL {
187 url: Url::parse("https://raw.githubusercontent.com/aaptel/notmuch-extract-patch/f732a53e12a7c91a06755ebfab2007adc9b3063b/notmuch-extract-patch").unwrap(),
188 exp_hash: Some(NixHash::from_sri("sha256-Xa1Jbl2Eq5+L0ww+Ph1osA3Z/Dxe/RkN1/dITQCdXFk=").unwrap())
189 }
190 });
191
192 static FETCH_URL_OUT_PATH: LazyLock<StorePath> = LazyLock::new(|| {
193 StorePath::from_bytes(b"06qi00hylriyfm0nl827crgjvbax84mz-notmuch-extract-patch").unwrap()
194 });
195
196 static FETCH_TARBALL: LazyLock<Fetch> = LazyLock::new(|| {
197 Fetch::Tarball {
198 url: Url::parse("https://github.com/NixOS/nixpkgs/archive/91050ea1e57e50388fa87a3302ba12d188ef723a.tar.gz").unwrap(),
199 exp_nar_sha256: Some(nixbase32::decode_fixed("1hf6cgaci1n186kkkjq106ryf8mmlq9vnwgfwh625wa8hfgdn4dm").unwrap())
200 }
201 });
202
203 static FETCH_TARBALL_OUT_PATH: LazyLock<StorePath> = LazyLock::new(|| {
204 StorePath::from_bytes(b"7adgvk5zdfq4pwrhsm3n9lzypb12gw0g-source").unwrap()
205 });
206
207 #[test]
210 #[should_panic]
211 fn drv_reject_if_missing_input_drv() {
212 let mut known_paths = KnownPaths::default();
213
214 known_paths.add_derivation(FOO_DRV_PATH.clone(), FOO_DRV.clone());
216 }
217
218 #[test]
219 fn drv_happy_path() {
220 let mut known_paths = KnownPaths::default();
221
222 assert_eq!(None, known_paths.get_drv_by_drvpath(&BAR_DRV_PATH.as_ref()));
225 assert_eq!(
226 None,
227 known_paths.get_hash_derivation_modulo(&BAR_DRV_PATH.as_ref())
228 );
229 assert_eq!(
230 None,
231 known_paths.get_drv_path_for_output_path(&BAR_OUT_PATH.as_ref())
232 );
233
234 known_paths.add_derivation(BAR_DRV_PATH.clone(), BAR_DRV.clone());
236
237 assert_eq!(
239 Some(&BAR_DRV.clone()),
240 known_paths.get_drv_by_drvpath(&BAR_DRV_PATH.as_ref())
241 );
242
243 assert_eq!(
245 Some(&BAR_DRV_PATH.clone()),
246 known_paths.get_drv_path_for_output_path(&BAR_OUT_PATH.as_ref())
247 );
248
249 assert_eq!(
251 Some(&hex!(
252 "c79aebd0ce3269393d4a1fde2cbd1d975d879b40f0bf40a48f550edc107fd5df"
253 )),
254 known_paths.get_hash_derivation_modulo(&BAR_DRV_PATH.as_ref())
255 );
256
257 known_paths.add_derivation(FOO_DRV_PATH.clone(), FOO_DRV.clone());
260
261 assert_eq!(
262 Some(&FOO_DRV.clone()),
263 known_paths.get_drv_by_drvpath(&FOO_DRV_PATH.as_ref())
264 );
265 assert_eq!(
266 Some(&hex!(
267 "af030d36d63d3d7f56a71adaba26b36f5fa1f9847da5eed953ed62e18192762f"
268 )),
269 known_paths.get_hash_derivation_modulo(&FOO_DRV_PATH.as_ref())
270 );
271
272 assert_eq!(
274 Some(&FOO_DRV_PATH.clone()),
275 known_paths.get_drv_path_for_output_path(&FOO_OUT_PATH.as_ref())
276 );
277 }
278
279 #[test]
280 fn fetch_happy_path() {
281 let mut known_paths = KnownPaths::default();
282
283 assert!(
285 known_paths
286 .get_fetch_for_output_path(&FETCH_TARBALL_OUT_PATH.as_ref())
287 .is_none()
288 );
289
290 assert_eq!(
292 *FETCH_TARBALL_OUT_PATH,
293 known_paths
294 .add_fetch(FETCH_TARBALL.clone(), "source")
295 .unwrap()
296 .to_owned()
297 );
298
299 assert_eq!(
300 *FETCH_URL_OUT_PATH,
301 known_paths
302 .add_fetch(FETCH_URL.clone(), "notmuch-extract-patch")
303 .unwrap()
304 .to_owned()
305 );
306 }
307
308 #[test]
309 fn get_derivations_working() {
310 let mut known_paths = KnownPaths::default();
311
312 known_paths.add_derivation(BAR_DRV_PATH.clone(), BAR_DRV.clone());
314
315 assert_eq!(
317 Some((BAR_DRV_PATH.as_ref(), &BAR_DRV.clone())),
318 known_paths
319 .get_derivations()
320 .find(|(s, d)| (s, *d) == (&BAR_DRV_PATH.as_ref(), &*BAR_DRV))
321 );
322 }
323
324 }