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