replacement in src/main.rs at line 226
[4.1]→[4.3392:3465](∅→∅),
[4.3392]→[4.3392:3465](∅→∅) − let p = download_extract_deps(&index, &self.deb_client, &r.name)
+ let link_extra: Vec<_> = r
+ .link_extra
+ .into_iter()
+ .map(|l| {
+ (
+ regex::Regex::new(&l.pkg).unwrap(),
+ regex::Regex::new(&l.dep).unwrap(),
+ )
+ })
+ .collect();
+ let p = download_extract_deps(&index, &self.deb_client, &r.name, &link_extra)
replacement in src/lib.rs at line 355
[4.12451]→[4.12451:12517](∅→∅) − let p = extract::download_extract_deps(&index, self, pkg)
+ let p = extract::download_extract_deps(&index, self, pkg, &[])
replacement in src/extract.rs at line 35
− transitive_deps: Vec<Arc<PathBuf>>,
− transitive_deps_h: BTreeSet<Arc<PathBuf>>,
+ last_transitive: (usize, usize),
+ transitive_deps: Vec<Arc<(usize, PathBuf)>>,
+ transitive_deps_h: BTreeSet<Arc<(usize, PathBuf)>>,
edit in src/extract.rs at line 41
edit in src/extract.rs at line 50
+ link_extra: &[(regex::Regex, regex::Regex)],
edit in src/extract.rs at line 54
− let mut seen: HashMap<_, (Vec<&str>, usize)> = HashMap::new();
− let mut stack = vec![(pkg, None)];
edit in src/extract.rs at line 55
+
+ // We first run a DFS of the dependencies and store them in
+ // `vertices` for later.
+ let mut stack = vec![(pkg, None)];
+ let mut seen: HashMap<_, (Vec<&str>, usize)> = HashMap::new();
edit in src/extract.rs at line 67
+ let v_len = vertices.len();
edit in src/extract.rs at line 82
+ last_transitive: (0, 0),
edit in src/extract.rs at line 85
edit in src/extract.rs at line 97
+ // Turn dependencies into their index in `vertices`.
edit in src/extract.rs at line 117
+
+ // Two independent missions in one loop:
+ // - Hash the context
+ // - Collect the `ld_paths` of each package in the SCC.
replacement in src/extract.rs at line 122
[4.2289]→[4.2289:2883](∅→∅),
[4.2883]→[4.21419:21437](∅→∅),
[4.18790]→[4.21419:21437](∅→∅),
[4.21419]→[4.21419:21437](∅→∅),
[4.18952]→[4.21705:21719](∅→∅),
[4.21705]→[4.21705:21719](∅→∅),
[4.21719]→[4.2884:3089](∅→∅) − let mut context_hasher = blake3::Hasher::new();
− context_hasher.update(vertices[*v].pkg.sha256.unwrap().as_bytes());
− for d in vertices[*v].deps.iter() {
− let w = &vertices[*d];
− if w.scc == vertices[*v].scc {
− // If in the same SCC, we can't know anything other than the SHA256.
− context_hasher.update(w.pkg.sha256.unwrap().as_bytes());
− } else {
− context_hasher
− .update(w.final_path.as_ref().unwrap().to_str().unwrap().as_bytes());
− }
− }
− vertices[*v].context_path =
− Some(Arc::new(client.store_path.join(
− data_encoding::HEXLOWER.encode(context_hasher.finalize().as_bytes()),
− )));
+ hash_context(&mut vertices, *v, &client.store_path, link_extra);
edit in src/extract.rs at line 133
+
+ // Once we have the ld paths of the entire SCC, add them to
+ // each vertex of the SCC.
edit in src/extract.rs at line 143
+
+ // Then, find the libraries in the `ld_paths` of each package
+ // of the SCC.
replacement in src/extract.rs at line 147
[2.1080]→[2.1080:1126](∅→∅) − // Find the libs in this package.
+ let a = vertices[*v].transitive_deps.len();
edit in src/extract.rs at line 149
+ vertices[*v].last_transitive = (a, vertices[*v].transitive_deps.len());
edit in src/extract.rs at line 152
+ // And finalize each of the component (patch ELFs etc).
replacement in src/extract.rs at line 154
[4.3130]→[4.3130:3202](∅→∅) − let f = finalize(&mut vertices, client, &files, *v).await?;
+ span!(Level::DEBUG, "Finalize");
+ let f = finalize(&mut vertices, client, link_extra, &files, *v).await?;
edit in src/extract.rs at line 359
+
+ fn hash_context(
+ vertices: &mut [Vertex],
+ v: usize,
+ store_path: &Path,
+ link_extra: &[(regex::Regex, regex::Regex)],
+ ) {
+ let mut context_hasher = blake3::Hasher::new();
+ context_hasher.update(vertices[v].pkg.sha256.unwrap().as_bytes());
+ for d in vertices[v].deps.iter() {
+ let w = &vertices[*d];
+ if w.scc == vertices[v].scc {
+ // If in the same SCC, we can't know anything other than the SHA256.
+ context_hasher.update(w.pkg.sha256.unwrap().as_bytes());
+ } else {
+ context_hasher.update(w.final_path.as_ref().unwrap().to_str().unwrap().as_bytes());
+ }
+ }
+ for (a, b) in link_extra {
+ context_hasher.update(a.to_string().as_bytes());
+ context_hasher.update(b.to_string().as_bytes());
+ }
+ vertices[v].context_path = Some(Arc::new(
+ store_path.join(data_encoding::HEXLOWER.encode(context_hasher.finalize().as_bytes())),
+ ));
+ }
replacement in src/extract.rs at line 390
[4.21181]→[4.21181:21274](∅→∅) − async fn link_extra(&mut self, final_path: &Path, extra: &[&str]) -> Result<(), Error> {
+ async fn link_extra<S: AsRef<str> + std::fmt::Debug>(
+ &mut self,
+ final_path: &Path,
+ extra: &[S],
+ ) -> Result<(), Error> {
edit in src/extract.rs at line 397
+ let path = path.as_ref();
replacement in src/extract.rs at line 463
[4.27399]→[4.7195:7419](∅→∅) − let path = Arc::new(
− self.context_path
− .as_ref()
− .unwrap()
− .join(ld.strip_prefix("/").unwrap()),
− );
+ let path = Arc::new((self.v, ld.strip_prefix("/").unwrap().to_path_buf()));
edit in src/extract.rs at line 471
replacement in src/extract.rs at line 473
[4.27623]→[4.23224:23365](∅→∅),
[4.23365]→[4.27719:27899](∅→∅),
[4.27719]→[4.27719:27899](∅→∅) − async fn patch_elf(
− &mut self,
− f: &Path,
− dest_path: &Path,
− files: &Files,
− ) -> Result<bool, Error> {
− use elfedit::*;
− info!("patch_elf {:?}", f);
− let file = std::fs::OpenOptions::new()
− .read(true)
− .write(true)
− .open(&f)?;
+ async fn patch_elf<'a>(
+ vertices: &mut [Vertex<'a>],
+ v: usize,
+ f: &Path,
+ dest_path: &Path,
+ files: &Files,
+ ) -> Result<bool, Error> {
+ use elfedit::*;
+ info!("patch_elf {:?}", f);
+ let file = std::fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&f)?;
replacement in src/extract.rs at line 487
[4.27900]→[4.23366:23573](∅→∅),
[4.23573]→[4.27980:28122](∅→∅),
[4.27980]→[4.27980:28122](∅→∅) − let mut elf = match Elf::open(&file) {
− Ok(elf) => elf,
− Err(e) => {
− info!("error opening {:?}: {:?}", file, e);
− return Ok(false);
− }
− };
− info!("patching {:?}", f);
− let Some(parsed) = elf.parse().unwrap() else {
− info!("No dynamic section");
+ let mut elf = match Elf::open(&file) {
+ Ok(elf) => elf,
+ Err(e) => {
+ info!("error opening {:?}: {:?}", file, e);
replacement in src/extract.rs at line 492
[4.28152]→[4.28152:28163](∅→∅),
[4.28163]→[4.23574:23719](∅→∅),
[4.23719]→[4.28222:28411](∅→∅),
[4.28222]→[4.28222:28411](∅→∅) − };
− let needed: Vec<_> = parsed
− .needed()
− .map(|x| x.unwrap().to_str().unwrap().to_string())
− .collect();
− let interp = parsed.interpreter();
− if let Some(interp) = interp.unwrap() {
− let interp = interp.to_str().unwrap();
− let files = files.lock().unwrap();
+ }
+ };
+ info!("patching {:?}", f);
+ let Some(parsed) = elf.parse().unwrap() else {
+ info!("No dynamic section");
+ return Ok(false);
+ };
+ let needed: Vec<_> = parsed
+ .needed()
+ .map(|x| x.unwrap().to_str().unwrap().to_string())
+ .collect();
+ let interp = parsed.interpreter();
+ if let Some(interp) = interp.unwrap() {
+ let interp = interp.to_str().unwrap();
+ let files = files.lock().unwrap();
replacement in src/extract.rs at line 508
[4.28412]→[4.28412:28478](∅→∅) − let p = Path::new(interp).strip_prefix("/").unwrap();
+ let p = Path::new(interp).strip_prefix("/").unwrap();
replacement in src/extract.rs at line 510
[4.28479]→[4.28479:29348](∅→∅) − let subst = if let Some(subst) = files.get(p) {
− subst.join(p)
− } else if interp.starts_with("/usr")
− || interp.starts_with("/lib")
− || interp.starts_with("/bin")
− {
− // https://www.freedesktop.org/wiki/Software/systemd/TheCaseForTheUsrMerge/
− let p2 = "usr".to_string() + interp;
− let p2 = Path::new(&p2);
− debug!("{:?}", p2);
− if let Some(subst) = files.get(p2) {
− subst.join(p2)
− } else {
− error!("No subst for {:?}", p2);
− let mut p2 = p2.to_path_buf();
− while p2.pop() {
− debug!("p2 = {:?} {:?}", p2, files.get(&p2));
− }
− return Ok(false);
− }
+ let subst = if let Some(subst) = files.get(p) {
+ subst.join(p)
+ } else if interp.starts_with("/usr")
+ || interp.starts_with("/lib")
+ || interp.starts_with("/bin")
+ {
+ // https://www.freedesktop.org/wiki/Software/systemd/TheCaseForTheUsrMerge/
+ let p2 = "usr".to_string() + interp;
+ let p2 = Path::new(&p2);
+ debug!("{:?}", p2);
+ if let Some(subst) = files.get(p2) {
+ subst.join(p2)
replacement in src/extract.rs at line 523
[4.29369]→[4.29369:29657](∅→∅) − // TODO: not sure what else to do here, we
− // might want to set the interpreter to a
− // different value (equivalent to "recompiling
− // everything" on Nix).
− info!("Interpreter is {interp}. Already patched?");
+ error!("No subst for {:?}", p2);
+ let mut p2 = p2.to_path_buf();
+ while p2.pop() {
+ debug!("p2 = {:?} {:?}", p2, files.get(&p2));
+ }
replacement in src/extract.rs at line 529
[4.29691]→[4.29691:29778](∅→∅),
[4.29778]→[4.23720:23770](∅→∅),
[4.23770]→[4.29812:29872](∅→∅),
[4.29812]→[4.29812:29872](∅→∅),
[4.29872]→[4.23771:23809](∅→∅),
[4.23809]→[4.29904:29936](∅→∅),
[4.29904]→[4.29904:29936](∅→∅) − };
− let subst = CString::new(subst.to_str().unwrap()).unwrap();
− info!("set interpreter {:?}", subst);
− elf.set_interpreter(subst.to_bytes_with_nul());
− } else if needed.is_empty() {
− // No need to patch
+ }
+ } else {
+ // TODO: not sure what else to do here, we
+ // might want to set the interpreter to a
+ // different value (equivalent to "recompiling
+ // everything" on Nix).
+ info!("Interpreter is {interp}. Already patched?");
replacement in src/extract.rs at line 537
[4.29966]→[4.29966:30057](∅→∅),
[4.30057]→[2.2298:2349](∅→∅) − }
−
− let mut deps_h = BTreeSet::new();
− let mut path = String::new();
− debug!("needed: {:?} -> {:?}", f, needed);
+ };
+ let subst = CString::new(subst.to_str().unwrap()).unwrap();
+ info!("set interpreter {:?}", subst);
+ elf.set_interpreter(subst.to_bytes_with_nul());
+ } else if needed.is_empty() {
+ // No need to patch
+ return Ok(false);
+ }
replacement in src/extract.rs at line 546
[2.2350]→[2.2350:2405](∅→∅),
[2.2405]→[4.23865:23914](∅→∅),
[4.7552]→[4.23865:23914](∅→∅),
[4.23865]→[4.23865:23914](∅→∅),
[4.23914]→[4.30140:30177](∅→∅),
[4.30140]→[4.30140:30177](∅→∅) − for dep in self.transitive_deps.iter().rev() {
− debug!("dep of {:?}: {:?}", f, dep);
− if !deps_h.insert(dep) {
+ let mut deps_h = BTreeSet::new();
+ let mut path = String::new();
+ debug!("needed: {:?} -> {:?}", f, needed);
+ let mut satisfied = HashMap::new();
+ // First, prefer local deps.
+ for ld in vertices[v].ld_path.iter() {
+ let mut p = vertices[v]
+ .context_path
+ .clone()
+ .unwrap()
+ .join(ld.strip_prefix("/").unwrap());
+ let mut at_least_one = false;
+ for n in needed.iter() {
+ let e = satisfied.entry(n);
+ use std::collections::hash_map::Entry;
+ if let Entry::Occupied(_) = e {
replacement in src/extract.rs at line 564
[4.23955]→[4.23955:24106](∅→∅),
[4.24106]→[2.2406:2552](∅→∅) − let mut dep = dep.to_path_buf();
− let mut is_needed = false;
− for n in needed.iter() {
− dep.push(&n);
− let exists = tokio::fs::metadata(&dep).await.is_ok();
− if exists {
− debug!("exists {:?}", dep);
+
+ p.push(&n);
+ debug!("trying local path {:?}", p);
+ if tokio::fs::metadata(&p).await.is_ok() {
+ if let Entry::Vacant(e) = e {
+ e.insert(());
replacement in src/extract.rs at line 571
[2.2570]→[2.2570:2607](∅→∅),
[2.2607]→[4.24176:24203](∅→∅),
[4.24176]→[4.24176:24203](∅→∅) − is_needed |= exists;
− dep.pop();
+ at_least_one = true;
+ p.pop();
+ break;
+ } else {
+ p.pop();
replacement in src/extract.rs at line 577
[4.24217]→[4.24217:24245](∅→∅),
[4.24245]→[4.30177:30217](∅→∅),
[4.30177]→[4.30177:30217](∅→∅) − if !is_needed {
− continue;
− }
replacement in src/extract.rs at line 582
[4.30296]→[4.30296:30346](∅→∅) − path.push_str(dep.to_str().unwrap());
+ path.push_str(p.to_str().unwrap())
edit in src/extract.rs at line 584
replacement in src/extract.rs at line 586
[2.2609]→[2.2609:2800](∅→∅) − // Add potential local libs.
− let mut last_added = None;
− for (d, _) in find_files(self.downloaded.path.clone())? {
− if last_added.as_deref() == d.parent() {
+ for dep_ in vertices[v].transitive_deps.iter().rev() {
+ let (dep, dep_path) = dep_.as_ref();
+ if !deps_h.insert(dep) {
+ continue;
+ }
+ let mut dep = if vertices[*dep].scc == vertices[v].scc {
+ vertices[*dep].context_path.clone().unwrap().join(dep_path)
+ } else {
+ vertices[*dep].final_path.clone().unwrap().join(dep_path)
+ };
+ debug!("dep of {:?}: {:?}", f, dep);
+ let mut is_needed = false;
+ for n in needed.iter() {
+ let e = satisfied.entry(n);
+ use std::collections::hash_map::Entry;
+ if let Entry::Occupied(_) = e {
replacement in src/extract.rs at line 604
[2.2840]→[2.2840:3611](∅→∅) − for n in needed.iter() {
− if d.file_name().unwrap().to_str().unwrap() == n {
− last_added = d.parent().map(|x| x.to_path_buf());
− if !path.is_empty() {
− path.push(':')
− }
− let suffix = d
− .parent()
− .unwrap()
− .strip_prefix(&self.downloaded.path)
− .unwrap();
− path.push_str(
− self.context_path
− .as_ref()
− .unwrap()
− .join(suffix)
− .to_str()
− .unwrap(),
− );
+ dep.push(&n);
+ let exists = tokio::fs::metadata(&dep).await.is_ok();
+ if exists {
+ if let Entry::Vacant(e) = e {
+ e.insert(());
edit in src/extract.rs at line 610
+ debug!("exists {:?}", dep);
+ is_needed = true;
edit in src/extract.rs at line 613
+ dep.pop();
+ }
+ if !is_needed {
+ continue;
edit in src/extract.rs at line 618
+ if !path.is_empty() {
+ path.push(':')
+ }
+ path.push_str(dep.to_str().unwrap());
+ }
replacement in src/extract.rs at line 624
[2.3654]→[4.30356:30423](∅→∅),
[4.30356]→[4.30356:30423](∅→∅),
[4.30423]→[4.24246:24274](∅→∅),
[4.24274]→[4.30447:30494](∅→∅),
[4.30447]→[4.30447:30494](∅→∅) − path.push('\0');
− info!("Setting path {:?}", path);
− if path.len() > 1 {
− elf.set_runpath(&path.as_bytes());
+ /*
+ // Add potential local libs.
+ let mut last_added = None;
+ for (d, _) in find_files(vertices[v].downloaded.path.clone())? {
+ if last_added.as_deref() == d.parent() {
+ continue;
replacement in src/extract.rs at line 631
[4.30504]→[4.30504:30626](∅→∅) −
− debug!("patching into {:?}", dest_path);
− Ok(elf.update(Some(dest_path)).unwrap()) // map_err(From::from)
+ for n in needed.iter() {
+ if d.file_name().unwrap().to_str().unwrap() == n {
+ last_added = d.parent().map(|x| x.to_path_buf());
+ if !path.is_empty() {
+ path.push(':')
+ }
+ let suffix = d
+ .parent()
+ .unwrap()
+ .strip_prefix(&vertices[v].downloaded.path)
+ .unwrap();
+ path.push_str(
+ vertices[v]
+ .context_path
+ .as_ref()
+ .unwrap()
+ .join(suffix)
+ .to_str()
+ .unwrap(),
+ );
+ }
+ }
edit in src/extract.rs at line 654
+ */
+ path.push('\0');
+ info!("Setting path {:?}", path);
+ if path.len() > 1 {
+ elf.set_runpath(&path.as_bytes());
+ }
+
+ debug!("patching into {:?}", dest_path);
+ Ok(elf.update(Some(dest_path)).unwrap()) // map_err(From::from)
edit in src/extract.rs at line 668
+ link_extra: &[(regex::Regex, regex::Regex)],
replacement in src/extract.rs at line 683
[4.8224]→[4.8224:8273](∅→∅) − for dep in vertices[v].deps.clone().iter() {
+
+ for dep in vertices[v].deps.iter() {
edit in src/extract.rs at line 689
+
+ let direct = if vertices[*dep].scc == vertices[v].scc {
+ vertices[*dep].context_path.clone().unwrap()
+ } else {
+ vertices[*dep].final_path.clone().unwrap()
+ };
+
edit in src/extract.rs at line 700
+ let l = ld.strip_prefix("/").unwrap();
+ if std::fs::metadata(&direct.join(l)).is_ok() {
+ let path = Arc::new((*dep, l.to_path_buf()));
+ if transitive_deps_h.insert(path.clone()) {
+ transitive_deps.push(path);
+ }
+ }
replacement in src/extract.rs at line 708
[4.31099]→[2.3790:3863](∅→∅) − debug!("transitive {:?}", vertices[*dep].transitive_deps.len());
replacement in src/extract.rs at line 718
[4.8760]→[4.8760:8827](∅→∅) − deps_paths.push(if vertices[*dep].scc == vertices[v].scc {
+
+ debug!(
+ "adding direct dep: {:?} {:?} {:?}",
+ vertices[*dep].scc, vertices[v].scc, direct
+ );
+ deps_paths.push(direct)
+ }
+
+ debug!("transitive {:#?}", transitive_deps);
+
+ // Direct deps should come after transitive ones, in order to be
+ // picked up earlier when patching the ELFs.
+ for dep in vertices[v].deps.iter() {
+ let direct = if vertices[*dep].scc == vertices[v].scc {
replacement in src/extract.rs at line 735
[4.8956]→[4.8956:8967](∅→∅) + };
+
+ for ld in vertices[*dep].ld_path.iter() {
+ let l = ld.strip_prefix("/").unwrap();
+ if std::fs::metadata(&direct.join(l)).is_ok() {
+ let path = Arc::new((*dep, l.to_path_buf()));
+ // Push even if it already exists since we need direct
+ // deps to come after indirect ones.
+ transitive_deps.push(path);
+ }
+ }
edit in src/extract.rs at line 747
edit in src/extract.rs at line 758
[4.31482]→[4.9394:9456](∅→∅),
[4.9523]→[4.31725:31726](∅→∅),
[4.31725]→[4.31725:31726](∅→∅) − let initial_deps_len = vertices[v].transitive_deps.len();
−
replacement in src/extract.rs at line 769
[4.9874]→[4.9874:10042](∅→∅) − info!("create final path for {dest:?}");
− match vertices[v]
− .create_final_path(client, &files, &dest, &base_package_name)
− .await
+ info!(
+ "create final path for {dest:?} ({:?})",
+ vertices[v].pkg.package
+ );
+ match create_final_path(
+ client,
+ &files,
+ vertices,
+ v,
+ &dest,
+ &base_package_name,
+ link_extra,
+ )
+ .await
edit in src/extract.rs at line 823
[4.11311]→[4.11311:11587](∅→∅) − // Replace prefix for all library deps we've just added,
− // in order to get a Merkle tree.
− for dep in &mut vertices[v].transitive_deps[initial_deps_len..] {
− let end = dep.strip_prefix(&*dest).unwrap();
− *dep = Arc::new(final_path.join(&end));
− }
edit in src/extract.rs at line 846
[4.12379]→[4.29808:29809](∅→∅),
[4.29808]→[4.29808:29809](∅→∅),
[4.29809]→[4.12380:12402](∅→∅),
[4.12402]→[4.29809:29908](∅→∅),
[4.29809]→[4.29809:29908](∅→∅),
[4.29940]→[4.29940:30415](∅→∅),
[4.30415]→[4.12403:12542](∅→∅),
[4.12542]→[4.30544:30579](∅→∅),
[4.30544]→[4.30544:30579](∅→∅),
[4.30579]→[4.12543:12613](∅→∅),
[4.12613]→[4.30644:30699](∅→∅),
[4.30644]→[4.30644:30699](∅→∅) −
− impl<'a> Vertex<'a> {
− async fn create_final_path(
− &mut self,
− client: &Client,
− files: &Files,
− dest: &Path,
− base_package_name: &str,
− ) -> Result<PathBuf, Error> {
− // Link the required libexec before hashing.
− let tmp = async_tempfile::TempDir::new_in(dest.parent().unwrap()).await?;
− self.link_extra(&tmp.dir_path(), &["usr/libexec", "usr/lib/gcc"])
− .await?;
−
− // Patch the ELFs, now that we have all the deps.
− let mut hashing = Vec::new();
− let mut hashes = Vec::with_capacity(hashing.len());
− debug!("create_final_path {:?}", self.downloaded.path);
− for (f, meta) in find_files(self.downloaded.path.to_path_buf())? {
− debug!("f = {:?}", f);
− let rel = f.strip_prefix(&self.downloaded.path).unwrap();
− let dest_path = tmp.dir_path().join(&rel);
replacement in src/extract.rs at line 847
[4.30700]→[4.30700:30831](∅→∅) − if meta.is_dir() {
− tokio::fs::create_dir_all(dest_path).await.unwrap_or(());
− continue;
+ async fn copy(from: &Path, to: &Path) -> Result<(), std::io::Error> {
+ let mut stack = vec![from.to_path_buf()];
+ while let Some(elt) = stack.pop() {
+ if let Ok(mut dir) = tokio::fs::read_dir(&elt).await {
+ let p = elt.strip_prefix(&from).unwrap();
+ tokio::fs::create_dir_all(&to.join(&p)).await.unwrap();
+ while let Some(e) = dir.next_entry().await.unwrap() {
+ stack.push(e.path());
edit in src/extract.rs at line 856
+ } else {
+ let p = elt.strip_prefix(&from).unwrap();
+ debug!("copy {:?} to {:?}", elt, to.join(&p));
+ tokio::fs::hard_link(&elt, &to.join(&p)).await.unwrap_or(());
+ }
+ }
+ Ok(())
+ }
replacement in src/extract.rs at line 865
[4.30846]→[4.30846:32085](∅→∅) − if meta.is_symlink() {
− // Relink the file to the subst.
− let target = tokio::fs::read_link(&f).await?;
− debug!("relink {:?} -> {:?} {:?}", f, target, target.is_relative());
− let subst = {
− let l = files.lock().unwrap();
− let target_rel = rel.parent().unwrap().join(&target);
− if let Some(subst) = l.get(&target_rel) {
− Some(subst.join(&target_rel))
− } else {
− None
− }
− };
− if let Some(subst) = subst {
− debug!("relink to {:?}", dest_path);
− tokio::fs::symlink(&subst, &dest_path).await?;
− hashes.push((f, subst.to_str().unwrap().to_string()))
− } else {
− // Leave the symlink untouched
− // tokio::fs::create_dir_all(dest_path.parent().unwrap()).await.unwrap_or(());
− debug!("symlink {:?} {:?} {:?}", target, dest_path, f);
− tokio::fs::symlink(&target, &dest_path).await?;
− hashes.push((f, target.to_str().unwrap().to_string()));
+ async fn create_final_path<'a>(
+ client: &Client,
+ files: &Files,
+ vertices: &mut [Vertex<'a>],
+ v: usize,
+ dest: &Path,
+ base_package_name: &str,
+ link_extra: &[(regex::Regex, regex::Regex)],
+ ) -> Result<PathBuf, Error> {
+ // Link the required libexec before hashing.
+ let tmp = async_tempfile::TempDir::new_in(dest.parent().unwrap()).await?;
+ vertices[v]
+ .link_extra(&tmp.dir_path(), &["usr/libexec", "usr/lib/gcc"])
+ .await
+ .unwrap();
+
+ for (pkg, dep) in link_extra {
+ if pkg.is_match(vertices[v].pkg.package) {
+ for d in (0..v).rev() {
+ if dep.is_match(vertices[d].pkg.package) {
+ debug!(
+ "match, copying {:?} to {:?}",
+ vertices[d].context_path, vertices[v].context_path
+ );
+ copy(&vertices[d].context_path.clone().unwrap(), &tmp.dir_path()).await?
edit in src/extract.rs at line 891
[4.32103]→[4.32103:32129](∅→∅) edit in src/extract.rs at line 892
replacement in src/extract.rs at line 895
[4.32144]→[4.32144:32465](∅→∅) − if !self
− .patch_elf(&f, &dest_path, &files)
− .await
− .unwrap_or(false)
− {
− // Hard link
− debug!("hard link {:?} {:?}", f, dest_path);
− tokio::fs::hard_link(&f, &dest_path).await.unwrap_or(());
− }
+ // Patch the ELFs, now that we have all the deps.
+ let mut hashing = Vec::new();
+ let mut hashes = Vec::with_capacity(hashing.len());
+ debug!("create_final_path {:?}", vertices[v].downloaded.path);
+ for (f, meta) in find_files(vertices[v].downloaded.path.to_path_buf())? {
+ debug!("f = {:?}", f);
+ let rel = f.strip_prefix(&vertices[v].downloaded.path).unwrap();
+ let dest_path = tmp.dir_path().join(&rel);
replacement in src/extract.rs at line 904
[4.32466]→[4.32466:33026](∅→∅) − hashing.push(tokio::spawn(async move {
− // hash + write
− info!("hashing {:?}", f);
− if let Ok(file) = tokio::fs::File::open(&dest_path).await {
− let mut hasher = blake3::Hasher::new();
− hash_reader(file, &mut hasher).await.unwrap();
− let hex = data_encoding::HEXLOWER.encode(hasher.finalize().as_bytes());
− Ok::<_, Error>(Some((f, hex)))
− } else {
− Ok(None)
− }
− }));
+ if meta.is_dir() {
+ tokio::fs::create_dir_all(dest_path).await.unwrap_or(());
+ continue;
edit in src/extract.rs at line 908
[4.33036]→[4.33036:33066](∅→∅) replacement in src/extract.rs at line 909
[4.33067]→[4.33067:33194](∅→∅) − for h in hashing.into_iter() {
− if let Some(h) = h.await.unwrap().unwrap() {
− hashes.push(h)
+ if meta.is_symlink() {
+ // Relink the file to the subst.
+ let target = tokio::fs::read_link(&f).await?;
+ debug!("relink {:?} -> {:?} {:?}", f, target, target.is_relative());
+ let subst = {
+ let l = files.lock().unwrap();
+ let target_rel = rel.parent().unwrap().join(&target);
+ if let Some(subst) = l.get(&target_rel) {
+ Some(subst.join(&target_rel))
+ } else {
+ None
+ }
+ };
+ if let Some(subst) = subst {
+ debug!("relink to {:?}", dest_path);
+ tokio::fs::symlink(&subst, &dest_path).await?;
+ hashes.push((f, subst.to_str().unwrap().to_string()))
+ } else {
+ // Leave the symlink untouched
+ // tokio::fs::create_dir_all(dest_path.parent().unwrap()).await.unwrap_or(());
+ debug!("symlink {:?} {:?} {:?}", target, dest_path, f);
+ tokio::fs::symlink(&target, &dest_path).await?;
+ hashes.push((f, target.to_str().unwrap().to_string()));
edit in src/extract.rs at line 933
edit in src/extract.rs at line 935
[4.33218]→[4.33218:33293](∅→∅) − hashes.sort_by(|a, b| a.0.cmp(&b.0));
− info!("hashed all");
replacement in src/extract.rs at line 936
[4.33294]→[4.33294:33349](∅→∅) − let mut output_hasher = blake3::Hasher::new();
+ if !patch_elf(vertices, v, &f, &dest_path, &files)
+ .await
+ .unwrap_or(false)
replacement in src/extract.rs at line 940
[4.33359]→[4.33359:33566](∅→∅),
[4.33566]→[4.12614:12671](∅→∅),
[4.12671]→[4.33618:33975](∅→∅),
[4.33618]→[4.33618:33975](∅→∅),
[4.33975]→[4.37569:37583](∅→∅),
[4.37569]→[4.37569:37583](∅→∅) − let blakesums = tmp.dir_path().join("blake3sums");
− let mut file = tokio::fs::File::create(&blakesums).await?;
− for (path, hash) in hashes {
− let path = path
− .strip_prefix(&self.downloaded.path)
− .unwrap()
− .to_str()
− .unwrap();
− file.write_all(hash.as_bytes()).await?;
− file.write_all(b" ").await?;
− file.write_all(path.as_bytes()).await?;
− file.write_all(b"\n").await?;
− writeln!(output_hasher, "{} {}", hash, path)?;
− }
+ // Hard link
+ debug!("hard link {:?} {:?}", f, dest_path);
+ tokio::fs::hard_link(&f, &dest_path).await.unwrap_or(());
edit in src/extract.rs at line 944
[4.37593]→[4.33976:34179](∅→∅) − let final_path = client.store_path.join(&format!(
− "{}-{}",
− data_encoding::HEXLOWER.encode(output_hasher.finalize().as_bytes()),
− base_package_name,
− ));
replacement in src/extract.rs at line 945
[4.34180]→[4.34180:34467](∅→∅) − {
− let transitive = tmp.dir_path().join("paths");
− let mut file = tokio::fs::File::create(&transitive).await?;
− for path in self.files.iter() {
− file.write_all(path.as_bytes()).await?;
− file.write_all(b"\n").await?;
+ hashing.push(tokio::spawn(async move {
+ // hash + write
+ info!("hashing {:?}", f);
+ if let Ok(file) = tokio::fs::File::open(&dest_path).await {
+ let mut hasher = blake3::Hasher::new();
+ hash_reader(file, &mut hasher).await.unwrap();
+ let hex = data_encoding::HEXLOWER.encode(hasher.finalize().as_bytes());
+ Ok::<_, Error>(Some((f, hex)))
+ } else {
+ Ok(None)
edit in src/extract.rs at line 956
+ }));
+ }
+ info!("patched all");
+
+ for h in hashing.into_iter() {
+ if let Some(h) = h.await.unwrap().unwrap() {
+ hashes.push(h)
edit in src/extract.rs at line 964
+ }
+ hashes.sort_by(|a, b| a.0.cmp(&b.0));
+ info!("hashed all");
replacement in src/extract.rs at line 968
[4.34492]→[4.12672:12813](∅→∅),
[4.12813]→[4.34623:34733](∅→∅),
[4.34623]→[4.34623:34733](∅→∅) − for (f, _) in find_dirs(self.downloaded.path.to_path_buf())? {
− let rel = f.strip_prefix(&self.downloaded.path).unwrap();
− self.files
− .insert(Arc::new(final_path.join(rel).to_str().unwrap().to_string()));
+ let mut output_hasher = blake3::Hasher::new();
+ {
+ let blakesums = tmp.dir_path().join("blake3sums");
+ let mut file = tokio::fs::File::create(&blakesums).await?;
+ for (path, hash) in hashes {
+ let path = path
+ .strip_prefix(&vertices[v].downloaded.path)
+ .unwrap()
+ .to_str()
+ .unwrap();
+ file.write_all(hash.as_bytes()).await?;
+ file.write_all(b" ").await?;
+ file.write_all(path.as_bytes()).await?;
+ file.write_all(b"\n").await?;
+ writeln!(output_hasher, "{} {}", hash, path)?;
edit in src/extract.rs at line 984
+ }
+ let final_path = client.store_path.join(&format!(
+ "{}-{}",
+ data_encoding::HEXLOWER.encode(output_hasher.finalize().as_bytes()),
+ base_package_name,
+ ));
replacement in src/extract.rs at line 991
[4.34744]→[4.34744:34891](∅→∅) − info!("rename {:?} to {:?}", tmp.dir_path(), dest);
− tokio::fs::rename(tmp.dir_path(), dest).await?;
− std::mem::forget(tmp);
+ {
+ let transitive = tmp.dir_path().join("paths");
+ let mut file = tokio::fs::File::create(&transitive).await?;
+ for path in vertices[v].files.iter() {
+ file.write_all(path.as_bytes()).await?;
+ file.write_all(b"\n").await?;
+ }
+ }
replacement in src/extract.rs at line 1000
[4.34892]→[4.34892:34915](∅→∅) + for (f, _) in find_dirs(vertices[v].downloaded.path.to_path_buf())? {
+ let rel = f.strip_prefix(&vertices[v].downloaded.path).unwrap();
+ vertices[v]
+ .files
+ .insert(Arc::new(final_path.join(rel).to_str().unwrap().to_string()));
edit in src/extract.rs at line 1006
+
+ info!("rename {:?} to {:?}", tmp.dir_path(), dest);
+ tokio::fs::rename(tmp.dir_path(), dest).await.unwrap();
+ std::mem::forget(tmp);
+
+ Ok(final_path)
edit in elpe/lib/elpegrpc.proto at line 78
+ message LinkExtra {
+ string pkg = 1;
+ string dep = 2;
+ }
+
replacement in elpe/lib/elpegrpc.proto at line 86
[4.55903]→[4.63857:63894](∅→∅) − repeated string path_patterns = 3;
+ repeated LinkExtra link_extra = 3;
edit in elpe/lib/derivation.ml at line 61
+
+ let link_extra = [ ("libstd-rust-.*-dev", "libstd-rust.*") ]
replacement in elpe/lib/derivation.ml at line 65
[3.2334]→[3.2334:2405](∅→∅) − let req = Elpegrpc.Elpe.UbuntuPackageRequest.make ~index ~name () in
+ let link_extra =
+ List.map
+ (fun (pkg, dep) -> Elpegrpc.Elpe.LinkExtra.make ~pkg ~dep ())
+ link_extra
+ in
+ let req =
+ Elpegrpc.Elpe.UbuntuPackageRequest.make ~index ~name ~link_extra ()
+ in