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