Loading pkgs/build-support/build-fhsenv-bubblewrap/buildFHSEnv.nix +128 −182 Original line number Diff line number Diff line Loading @@ -33,18 +33,15 @@ # follows: # /lib32 will include 32bit libraries from multiPkgs # /lib64 will include 64bit libraries from multiPkgs and targetPkgs # /lib will link to /lib32 # /lib will link to /lib64 let inherit (stdenv.hostPlatform) is64bit; name = if (args ? pname && args ? version) then "${args.pname}-${args.version}" else args.name; # "use of glibc_multi is only supported on x86_64-linux" isMultiBuild = multiArch && stdenv.system == "x86_64-linux"; isTargetBuild = !isMultiBuild; # list of packages (usually programs) which match the host's architecture # (which includes stuff from multiPkgs) Loading @@ -60,7 +57,7 @@ let baseTargetPaths = with pkgs; [ glibcLocales (if isMultiBuild then glibc_multi else glibc) (toString gcc.cc.lib) gcc.cc.lib bashInteractiveFHS coreutils less Loading @@ -77,7 +74,7 @@ let xz ]; baseMultiPaths = with pkgsi686Linux; [ (toString gcc.cc.lib) gcc.cc.lib ]; ldconfig = writeShellScriptBin "ldconfig" '' Loading @@ -85,7 +82,10 @@ let exec ${if isMultiBuild then pkgsi686Linux.glibc.bin else pkgs.glibc.bin}/bin/ldconfig -f /etc/ld.so.conf -C /etc/ld.so.cache "$@" ''; etcProfile = writeText "profile" '' etcProfile = pkgs.writeTextFile { name = "${name}-fhsenv-profile"; destination = "/etc/profile"; text = '' export PS1='${name}-fhsenv:\u@\h:\w\$ ' export LOCALE_ARCHIVE='/usr/lib/locale/locale-archive' export PATH="/run/wrappers/bin:/usr/bin:/usr/sbin:$PATH" Loading Loading @@ -123,152 +123,98 @@ let ${profile} ''; }; # Compose /etc for the fhs environment etcPkg = runCommandLocal "${name}-fhs-etc" { } '' mkdir -p $out/etc pushd $out/etc # environment variables ln -s ${etcProfile} profile # symlink /etc/mtab -> /proc/mounts (compat for old userspace progs) ln -s /proc/mounts mtab ''; # Composes a /usr-like directory structure staticUsrProfileTarget = buildEnv { name = "${name}-usr-target"; # ldconfig wrapper must come first so it overrides the original ldconfig paths = [ etcPkg ldconfig ] ++ baseTargetPaths ++ targetPaths; extraOutputsToInstall = [ "out" "lib" "bin" ] ++ extraOutputsToInstall; ignoreCollisions = true; postBuild = '' if [[ -d $out/share/gsettings-schemas/ ]]; then # Recreate the standard schemas directory if its a symlink to make it writable if [[ -L $out/share/glib-2.0 ]]; then target=$(readlink $out/share/glib-2.0) rm $out/share/glib-2.0 mkdir $out/share/glib-2.0 ln -fsr $target/* $out/share/glib-2.0 fi if [[ -L $out/share/glib-2.0/schemas ]]; then target=$(readlink $out/share/glib-2.0/schemas) rm $out/share/glib-2.0/schemas mkdir $out/share/glib-2.0/schemas ln -fsr $target/* $out/share/glib-2.0/schemas fi ensureGsettingsSchemasIsDirectory = runCommandLocal "fhsenv-ensure-gsettings-schemas-directory" {} '' mkdir -p $out/share/glib-2.0/schemas for d in $out/share/gsettings-schemas/*; do # Force symlink, in case there are duplicates ln -fsr $d/glib-2.0/schemas/*.xml $out/share/glib-2.0/schemas ln -fsr $d/glib-2.0/schemas/*.gschema.override $out/share/glib-2.0/schemas done # and compile them ${pkgs.glib.dev}/bin/glib-compile-schemas $out/share/glib-2.0/schemas fi touch $out/share/glib-2.0/schemas/.keep ''; inherit includeClosures; }; staticUsrProfileMulti = buildEnv { name = "${name}-usr-multi"; paths = baseMultiPaths ++ multiPaths; extraOutputsToInstall = [ "out" "lib" ] ++ extraOutputsToInstall; ignoreCollisions = true; inherit includeClosures; # Shamelessly stolen (and cleaned up) from original buildEnv. # Should be semantically equivalent, except we also take # a list of default extra outputs that will be installed # for derivations that don't explicitly specify one. # Note that this is not the same as `extraOutputsToInstall`, # as that will apply even to derivations with an output # explicitly specified, so this does change the behavior # very slightly for that particular edge case. pickOutputs = let pickOutputsOne = outputs: drv: let isSpecifiedOutput = drv.outputSpecified or false; outputsToInstall = drv.meta.outputsToInstall or null; pickedOutputs = if isSpecifiedOutput || outputsToInstall == null then [ drv ] else map (out: drv.${out} or null) (outputsToInstall ++ outputs); extraOutputs = map (out: drv.${out} or null) extraOutputsToInstall; cleanOutputs = lib.filter (o: o != null) (pickedOutputs ++ extraOutputs); in { paths = cleanOutputs; priority = drv.meta.priority or lib.meta.defaultPriority; }; in paths: outputs: map (pickOutputsOne outputs) paths; # setup library paths only for the targeted architecture setupLibDirsTarget = '' # link content of targetPaths cp -rsHf ${staticUsrProfileTarget}/lib lib ln -s lib lib${if is64bit then "64" else "32"} ''; paths = let basePaths = [ etcProfile # ldconfig wrapper must come first so it overrides the original ldconfig ldconfig # magic package that just creates a directory, to ensure that # the entire directory can't be a symlink, as we will write # compiled schemas to it ensureGsettingsSchemasIsDirectory ] ++ baseTargetPaths ++ targetPaths; in pickOutputs basePaths ["out" "lib" "bin"]; paths32 = lib.optionals isMultiBuild ( let basePaths = baseMultiPaths ++ multiPaths; in pickOutputs basePaths ["out" "lib"] ); # setup /lib, /lib32 and /lib64 setupLibDirsMulti = '' mkdir -m0755 lib32 mkdir -m0755 lib64 ln -s lib64 lib allPaths = paths ++ paths32; # copy glibc stuff cp -rsHf ${staticUsrProfileTarget}/lib/32/* lib32/ chmod u+w -R lib32/ rootfs-builder = pkgs.rustPlatform.buildRustPackage { name = "fhs-rootfs-bulder"; src = ./rootfs-builder; cargoLock.lockFile = ./rootfs-builder/Cargo.lock; doCheck = false; }; # copy content of multiPaths (32bit libs) if [ -d ${staticUsrProfileMulti}/lib ]; then cp -rsHf ${staticUsrProfileMulti}/lib/* lib32/ chmod u+w -R lib32/ rootfs = pkgs.runCommand "${name}-fhsenv-rootfs" { __structuredAttrs = true; exportReferencesGraph.graph = lib.concatMap (p: p.paths) allPaths; inherit paths paths32 isMultiBuild includeClosures; nativeBuildInputs = [ pkgs.jq ]; } '' ${rootfs-builder}/bin/rootfs-builder # create a bunch of symlinks for usrmerge ln -s /usr/bin $out/bin ln -s /usr/sbin $out/sbin ln -s /usr/lib $out/lib ln -s /usr/lib32 $out/lib32 ln -s /usr/lib64 $out/lib64 ln -s /usr/lib64 $out/usr/lib # symlink 32-bit ld-linux so it's visible in /lib if [ -e $out/usr/lib32/ld-linux.so.2 ]; then ln -s /usr/lib32/ld-linux.so.2 $out/usr/lib64/ld-linux.so.2 fi # copy content of targetPaths (64bit libs) cp -rsHf ${staticUsrProfileTarget}/lib/* lib64/ chmod u+w -R lib64/ # symlink 32-bit ld-linux.so ln -Lsf ${staticUsrProfileTarget}/lib/32/ld-linux.so.2 lib/ ''; setupLibDirs = if isTargetBuild then setupLibDirsTarget else setupLibDirsMulti; # the target profile is the actual profile that will be used for the fhs setupTargetProfile = '' mkdir -m0755 usr pushd usr ${setupLibDirs} '' + lib.optionalString isMultiBuild '' if [ -d "${staticUsrProfileMulti}/share" ]; then cp -rLf ${staticUsrProfileMulti}/share share fi '' + '' if [ -d "${staticUsrProfileTarget}/share" ]; then if [ -d share ]; then chmod -R 755 share cp -rLTf ${staticUsrProfileTarget}/share share else cp -rsHf ${staticUsrProfileTarget}/share share fi fi for i in bin sbin include; do if [ -d "${staticUsrProfileTarget}/$i" ]; then cp -rsHf "${staticUsrProfileTarget}/$i" "$i" fi done cd .. # symlink /etc/mtab -> /proc/mounts (compat for old userspace progs) ln -s /proc/mounts $out/etc/mtab for i in etc opt; do if [ -d "${staticUsrProfileTarget}/$i" ]; then cp -rsHf "${staticUsrProfileTarget}/$i" "$i" fi if [[ -d $out/usr/share/gsettings-schemas/ ]]; then for d in $out/usr/share/gsettings-schemas/*; do # Force symlink, in case there are duplicates ln -fsr $d/glib-2.0/schemas/*.xml $out/usr/share/glib-2.0/schemas ln -fsr $d/glib-2.0/schemas/*.gschema.override $out/usr/share/glib-2.0/schemas done for i in usr/{bin,sbin,lib,lib32,lib64}; do if [ -d "$i" ]; then ln -s "$i" ${pkgs.glib.dev}/bin/glib-compile-schemas $out/usr/share/glib-2.0/schemas fi done popd ''; in runCommandLocal "${name}-fhs" { inherit nativeBuildInputs; passthru = { inherit args baseTargetPaths targetPaths baseMultiPaths ldconfig isMultiBuild; }; } '' mkdir -p $out pushd $out ${setupTargetProfile} ${extraBuildCommands} ${lib.optionalString isMultiBuild extraBuildCommandsMulti} '' ''; in rootfs pkgs/build-support/build-fhsenv-bubblewrap/rootfs-builder/Cargo.lock 0 → 100644 +249 −0 Original line number Diff line number Diff line # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anyhow" version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "goblin" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53ab3f32d1d77146981dea5d6b1e8fe31eedcb7013e5e00d6ccd1259a4b4d923" dependencies = [ "log", "plain", "scroll", ] [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "plain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "proc-macro2" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rootfs-builder" version = "0.1.0" dependencies = [ "anyhow", "goblin", "serde", "serde_json", "walkdir", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scroll" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" dependencies = [ "scroll_derive", ] [[package]] name = "scroll_derive" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde" version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "syn" version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" pkgs/build-support/build-fhsenv-bubblewrap/rootfs-builder/Cargo.toml 0 → 100644 +11 −0 Original line number Diff line number Diff line [package] name = "rootfs-builder" version = "0.1.0" edition = "2021" [dependencies] anyhow = "*" serde = { version = "*", features = ["derive"] } serde_json = "*" walkdir = "*" goblin = "*" pkgs/build-support/build-fhsenv-bubblewrap/rootfs-builder/src/main.rs 0 → 100644 +293 −0 Original line number Diff line number Diff line #![deny(clippy::pedantic)] use std::{ collections::{hash_map::Entry, HashMap}, env, fs::{self, File}, io::{BufReader, Read}, os::unix::fs as ufs, path::{Path, PathBuf}, }; use anyhow::{anyhow, Context}; use goblin::{Hint, Object}; use serde::Deserialize; use walkdir::WalkDir; #[derive(Debug, Deserialize)] struct RefGraphNode { path: PathBuf, references: Vec<PathBuf>, } #[derive(Debug, Deserialize)] struct InputDrv { paths: Vec<PathBuf>, priority: i64, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct StructuredAttrsRoot { graph: Vec<RefGraphNode>, paths: Vec<InputDrv>, paths32: Vec<InputDrv>, include_closures: bool, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] struct PriorityKey { implicit: bool, group_priority: i64, priority: i64, root_index: usize, } fn build_reference_map(refs: Vec<RefGraphNode>) -> HashMap<PathBuf, Vec<PathBuf>> { refs.into_iter() .map(|mut gn| { gn.references.retain_mut(|x| x != &gn.path); (gn.path, gn.references) }) .collect() } fn build_priority_keys(roots: &[InputDrv], group_priority: i64) -> HashMap<PathBuf, PriorityKey> { let mut roots_map = HashMap::new(); for (idx, path) in roots.iter().enumerate() { for subpath in &path.paths { let priority = PriorityKey { group_priority, priority: path.priority, root_index: idx, implicit: false, }; roots_map.entry(subpath.clone()).or_insert(priority); } } roots_map } fn extend_to_closure( roots: HashMap<PathBuf, PriorityKey>, refs: &HashMap<PathBuf, Vec<PathBuf>>, ) -> anyhow::Result<HashMap<PathBuf, PriorityKey>> { let mut path_map = HashMap::new(); for (root, priority) in roots { path_map.insert(root.clone(), priority); let mut priority = priority; priority.implicit = true; let mut stack = vec![root.clone()]; while let Some(next) = stack.pop() { match path_map.entry(next.clone()) { Entry::Occupied(mut occupied_entry) => { let old_priority: &PriorityKey = occupied_entry.get(); if old_priority > &priority { occupied_entry.insert(priority); } } Entry::Vacant(vacant_entry) => { vacant_entry.insert(priority); } } stack.extend_from_slice(refs.get(&next).ok_or(anyhow!("encountered unknown path"))?); } } Ok(path_map) } #[derive(Clone, Debug)] struct CandidatePath { root: PathBuf, relative: PathBuf, priority: PriorityKey, } fn collect_candidate_paths( paths: HashMap<PathBuf, PriorityKey>, mapper: impl Fn(&Path, &Path) -> Option<PathBuf>, ) -> anyhow::Result<HashMap<PathBuf, Vec<CandidatePath>>> { let mut candidates: HashMap<_, Vec<_>> = HashMap::new(); for (path, priority) in paths { for entry in WalkDir::new(&path).follow_links(true) { let entry: PathBuf = match entry { Ok(ent) => { // we don't care about directory structure if ent.file_type().is_dir() { continue; } ent.path().into() } Err(e) => { match e.io_error() { // could be a broken symlink, that's fine, we still want to handle those Some(_) => e .path() .ok_or_else(|| anyhow!("I/O error when walking {path:?}"))? .into(), None => { // symlink loop continue; } } } }; let relative = entry.strip_prefix(&path)?.to_owned(); if let Some(mapped) = mapper(&path, &relative) { candidates.entry(mapped).or_default().push(CandidatePath { root: path.clone(), relative, priority, }); } } } Ok(candidates) } fn remap_native_path(root: &Path, p: &Path) -> Option<PathBuf> { if p.starts_with("bin/") || p.starts_with("sbin/") { return Some(PathBuf::from("usr/").join(p)); } // glibc-multilib special case if let Ok(no_lib32) = p.strip_prefix("lib/32/") { return Some(PathBuf::from("usr/lib32/").join(no_lib32)); } remap_multilib_path(root, p) } fn is_32_bit(path: &Path) -> bool { // Be as pessimistic as possible, at least for now. let Ok(mut f) = File::open(path) else { return false; }; let Ok(Hint::Elf(hint)) = goblin::peek(&mut f) else { return false; }; if let Some(is64) = hint.is_64 { return !is64; } let mut buf = vec![]; let Ok(_) = f.read_to_end(&mut buf) else { return false; }; let Ok(Object::Elf(e)) = goblin::Object::parse(&buf) else { return false; }; !e.is_64 } fn remap_multilib_path(root: &Path, p: &Path) -> Option<PathBuf> { if let Ok(no_lib) = p.strip_prefix("lib/") { let full = root.join(p); let libdir = if is_32_bit(&full) { "lib32" } else { "lib64" }; return Some(PathBuf::from("usr/").join(libdir).join(no_lib)); } if p.starts_with("etc/") || p.starts_with("opt/") { return Some(p.into()); } if p.starts_with("share/") || p.starts_with("include/") { return Some(PathBuf::from("usr/").join(p)); } None } fn build_plan( paths: HashMap<PathBuf, PriorityKey>, paths32: HashMap<PathBuf, PriorityKey>, ) -> anyhow::Result<HashMap<PathBuf, PathBuf>> { let candidates_native = collect_candidate_paths(paths, remap_native_path)?; let candidates_32 = collect_candidate_paths(paths32, remap_multilib_path)?; let mut all_candidates: HashMap<_, Vec<_>> = HashMap::new(); for map in [candidates_native, candidates_32] { for (path, candidates) in map { all_candidates .entry(path) .or_default() .extend_from_slice(&candidates); } } let mut final_plan: HashMap<PathBuf, PathBuf> = HashMap::new(); for (path, candidates) in all_candidates { let best = candidates .into_iter() .min_by_key(|&CandidatePath { priority, .. }| priority) .ok_or(anyhow!("candidate list empty"))?; final_plan.insert(path, best.root.join(best.relative)); } Ok(final_plan) } fn build_env(out: &Path, plan: HashMap<PathBuf, PathBuf>) -> anyhow::Result<()> { fs::create_dir_all(out)?; for (dest, src) in plan { let full_dest = out.join(&dest); let dest_dir = full_dest .parent() .ok_or(anyhow!("destination directory is root")) .with_context(|| { format!("When trying to determine destination directory for {full_dest:?}") })?; fs::create_dir_all(dest_dir) .with_context(|| format!("When trying to create directory {dest_dir:?}"))?; ufs::symlink(&src, &full_dest) .with_context(|| format!("When symlinking {src:?} to {full_dest:?}"))?; } Ok(()) } fn main() -> anyhow::Result<()> { let filename = env::var("NIX_ATTRS_JSON_FILE")?; let reader = File::open(filename)?; let buf_reader = BufReader::new(reader); let attrs: StructuredAttrsRoot = serde_json::from_reader(buf_reader)?; let mut paths = build_priority_keys(&attrs.paths, 1); let mut paths32 = build_priority_keys(&attrs.paths32, 2); if attrs.include_closures { let refs = build_reference_map(attrs.graph); paths = extend_to_closure(paths, &refs)?; paths32 = extend_to_closure(paths32, &refs)?; }; let plan = build_plan(paths, paths32)?; let out_dir = env::var("out")?; build_env(&PathBuf::from(out_dir), plan) } Loading
pkgs/build-support/build-fhsenv-bubblewrap/buildFHSEnv.nix +128 −182 Original line number Diff line number Diff line Loading @@ -33,18 +33,15 @@ # follows: # /lib32 will include 32bit libraries from multiPkgs # /lib64 will include 64bit libraries from multiPkgs and targetPkgs # /lib will link to /lib32 # /lib will link to /lib64 let inherit (stdenv.hostPlatform) is64bit; name = if (args ? pname && args ? version) then "${args.pname}-${args.version}" else args.name; # "use of glibc_multi is only supported on x86_64-linux" isMultiBuild = multiArch && stdenv.system == "x86_64-linux"; isTargetBuild = !isMultiBuild; # list of packages (usually programs) which match the host's architecture # (which includes stuff from multiPkgs) Loading @@ -60,7 +57,7 @@ let baseTargetPaths = with pkgs; [ glibcLocales (if isMultiBuild then glibc_multi else glibc) (toString gcc.cc.lib) gcc.cc.lib bashInteractiveFHS coreutils less Loading @@ -77,7 +74,7 @@ let xz ]; baseMultiPaths = with pkgsi686Linux; [ (toString gcc.cc.lib) gcc.cc.lib ]; ldconfig = writeShellScriptBin "ldconfig" '' Loading @@ -85,7 +82,10 @@ let exec ${if isMultiBuild then pkgsi686Linux.glibc.bin else pkgs.glibc.bin}/bin/ldconfig -f /etc/ld.so.conf -C /etc/ld.so.cache "$@" ''; etcProfile = writeText "profile" '' etcProfile = pkgs.writeTextFile { name = "${name}-fhsenv-profile"; destination = "/etc/profile"; text = '' export PS1='${name}-fhsenv:\u@\h:\w\$ ' export LOCALE_ARCHIVE='/usr/lib/locale/locale-archive' export PATH="/run/wrappers/bin:/usr/bin:/usr/sbin:$PATH" Loading Loading @@ -123,152 +123,98 @@ let ${profile} ''; }; # Compose /etc for the fhs environment etcPkg = runCommandLocal "${name}-fhs-etc" { } '' mkdir -p $out/etc pushd $out/etc # environment variables ln -s ${etcProfile} profile # symlink /etc/mtab -> /proc/mounts (compat for old userspace progs) ln -s /proc/mounts mtab ''; # Composes a /usr-like directory structure staticUsrProfileTarget = buildEnv { name = "${name}-usr-target"; # ldconfig wrapper must come first so it overrides the original ldconfig paths = [ etcPkg ldconfig ] ++ baseTargetPaths ++ targetPaths; extraOutputsToInstall = [ "out" "lib" "bin" ] ++ extraOutputsToInstall; ignoreCollisions = true; postBuild = '' if [[ -d $out/share/gsettings-schemas/ ]]; then # Recreate the standard schemas directory if its a symlink to make it writable if [[ -L $out/share/glib-2.0 ]]; then target=$(readlink $out/share/glib-2.0) rm $out/share/glib-2.0 mkdir $out/share/glib-2.0 ln -fsr $target/* $out/share/glib-2.0 fi if [[ -L $out/share/glib-2.0/schemas ]]; then target=$(readlink $out/share/glib-2.0/schemas) rm $out/share/glib-2.0/schemas mkdir $out/share/glib-2.0/schemas ln -fsr $target/* $out/share/glib-2.0/schemas fi ensureGsettingsSchemasIsDirectory = runCommandLocal "fhsenv-ensure-gsettings-schemas-directory" {} '' mkdir -p $out/share/glib-2.0/schemas for d in $out/share/gsettings-schemas/*; do # Force symlink, in case there are duplicates ln -fsr $d/glib-2.0/schemas/*.xml $out/share/glib-2.0/schemas ln -fsr $d/glib-2.0/schemas/*.gschema.override $out/share/glib-2.0/schemas done # and compile them ${pkgs.glib.dev}/bin/glib-compile-schemas $out/share/glib-2.0/schemas fi touch $out/share/glib-2.0/schemas/.keep ''; inherit includeClosures; }; staticUsrProfileMulti = buildEnv { name = "${name}-usr-multi"; paths = baseMultiPaths ++ multiPaths; extraOutputsToInstall = [ "out" "lib" ] ++ extraOutputsToInstall; ignoreCollisions = true; inherit includeClosures; # Shamelessly stolen (and cleaned up) from original buildEnv. # Should be semantically equivalent, except we also take # a list of default extra outputs that will be installed # for derivations that don't explicitly specify one. # Note that this is not the same as `extraOutputsToInstall`, # as that will apply even to derivations with an output # explicitly specified, so this does change the behavior # very slightly for that particular edge case. pickOutputs = let pickOutputsOne = outputs: drv: let isSpecifiedOutput = drv.outputSpecified or false; outputsToInstall = drv.meta.outputsToInstall or null; pickedOutputs = if isSpecifiedOutput || outputsToInstall == null then [ drv ] else map (out: drv.${out} or null) (outputsToInstall ++ outputs); extraOutputs = map (out: drv.${out} or null) extraOutputsToInstall; cleanOutputs = lib.filter (o: o != null) (pickedOutputs ++ extraOutputs); in { paths = cleanOutputs; priority = drv.meta.priority or lib.meta.defaultPriority; }; in paths: outputs: map (pickOutputsOne outputs) paths; # setup library paths only for the targeted architecture setupLibDirsTarget = '' # link content of targetPaths cp -rsHf ${staticUsrProfileTarget}/lib lib ln -s lib lib${if is64bit then "64" else "32"} ''; paths = let basePaths = [ etcProfile # ldconfig wrapper must come first so it overrides the original ldconfig ldconfig # magic package that just creates a directory, to ensure that # the entire directory can't be a symlink, as we will write # compiled schemas to it ensureGsettingsSchemasIsDirectory ] ++ baseTargetPaths ++ targetPaths; in pickOutputs basePaths ["out" "lib" "bin"]; paths32 = lib.optionals isMultiBuild ( let basePaths = baseMultiPaths ++ multiPaths; in pickOutputs basePaths ["out" "lib"] ); # setup /lib, /lib32 and /lib64 setupLibDirsMulti = '' mkdir -m0755 lib32 mkdir -m0755 lib64 ln -s lib64 lib allPaths = paths ++ paths32; # copy glibc stuff cp -rsHf ${staticUsrProfileTarget}/lib/32/* lib32/ chmod u+w -R lib32/ rootfs-builder = pkgs.rustPlatform.buildRustPackage { name = "fhs-rootfs-bulder"; src = ./rootfs-builder; cargoLock.lockFile = ./rootfs-builder/Cargo.lock; doCheck = false; }; # copy content of multiPaths (32bit libs) if [ -d ${staticUsrProfileMulti}/lib ]; then cp -rsHf ${staticUsrProfileMulti}/lib/* lib32/ chmod u+w -R lib32/ rootfs = pkgs.runCommand "${name}-fhsenv-rootfs" { __structuredAttrs = true; exportReferencesGraph.graph = lib.concatMap (p: p.paths) allPaths; inherit paths paths32 isMultiBuild includeClosures; nativeBuildInputs = [ pkgs.jq ]; } '' ${rootfs-builder}/bin/rootfs-builder # create a bunch of symlinks for usrmerge ln -s /usr/bin $out/bin ln -s /usr/sbin $out/sbin ln -s /usr/lib $out/lib ln -s /usr/lib32 $out/lib32 ln -s /usr/lib64 $out/lib64 ln -s /usr/lib64 $out/usr/lib # symlink 32-bit ld-linux so it's visible in /lib if [ -e $out/usr/lib32/ld-linux.so.2 ]; then ln -s /usr/lib32/ld-linux.so.2 $out/usr/lib64/ld-linux.so.2 fi # copy content of targetPaths (64bit libs) cp -rsHf ${staticUsrProfileTarget}/lib/* lib64/ chmod u+w -R lib64/ # symlink 32-bit ld-linux.so ln -Lsf ${staticUsrProfileTarget}/lib/32/ld-linux.so.2 lib/ ''; setupLibDirs = if isTargetBuild then setupLibDirsTarget else setupLibDirsMulti; # the target profile is the actual profile that will be used for the fhs setupTargetProfile = '' mkdir -m0755 usr pushd usr ${setupLibDirs} '' + lib.optionalString isMultiBuild '' if [ -d "${staticUsrProfileMulti}/share" ]; then cp -rLf ${staticUsrProfileMulti}/share share fi '' + '' if [ -d "${staticUsrProfileTarget}/share" ]; then if [ -d share ]; then chmod -R 755 share cp -rLTf ${staticUsrProfileTarget}/share share else cp -rsHf ${staticUsrProfileTarget}/share share fi fi for i in bin sbin include; do if [ -d "${staticUsrProfileTarget}/$i" ]; then cp -rsHf "${staticUsrProfileTarget}/$i" "$i" fi done cd .. # symlink /etc/mtab -> /proc/mounts (compat for old userspace progs) ln -s /proc/mounts $out/etc/mtab for i in etc opt; do if [ -d "${staticUsrProfileTarget}/$i" ]; then cp -rsHf "${staticUsrProfileTarget}/$i" "$i" fi if [[ -d $out/usr/share/gsettings-schemas/ ]]; then for d in $out/usr/share/gsettings-schemas/*; do # Force symlink, in case there are duplicates ln -fsr $d/glib-2.0/schemas/*.xml $out/usr/share/glib-2.0/schemas ln -fsr $d/glib-2.0/schemas/*.gschema.override $out/usr/share/glib-2.0/schemas done for i in usr/{bin,sbin,lib,lib32,lib64}; do if [ -d "$i" ]; then ln -s "$i" ${pkgs.glib.dev}/bin/glib-compile-schemas $out/usr/share/glib-2.0/schemas fi done popd ''; in runCommandLocal "${name}-fhs" { inherit nativeBuildInputs; passthru = { inherit args baseTargetPaths targetPaths baseMultiPaths ldconfig isMultiBuild; }; } '' mkdir -p $out pushd $out ${setupTargetProfile} ${extraBuildCommands} ${lib.optionalString isMultiBuild extraBuildCommandsMulti} '' ''; in rootfs
pkgs/build-support/build-fhsenv-bubblewrap/rootfs-builder/Cargo.lock 0 → 100644 +249 −0 Original line number Diff line number Diff line # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anyhow" version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "goblin" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53ab3f32d1d77146981dea5d6b1e8fe31eedcb7013e5e00d6ccd1259a4b4d923" dependencies = [ "log", "plain", "scroll", ] [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "plain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "proc-macro2" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rootfs-builder" version = "0.1.0" dependencies = [ "anyhow", "goblin", "serde", "serde_json", "walkdir", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scroll" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" dependencies = [ "scroll_derive", ] [[package]] name = "scroll_derive" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde" version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "syn" version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
pkgs/build-support/build-fhsenv-bubblewrap/rootfs-builder/Cargo.toml 0 → 100644 +11 −0 Original line number Diff line number Diff line [package] name = "rootfs-builder" version = "0.1.0" edition = "2021" [dependencies] anyhow = "*" serde = { version = "*", features = ["derive"] } serde_json = "*" walkdir = "*" goblin = "*"
pkgs/build-support/build-fhsenv-bubblewrap/rootfs-builder/src/main.rs 0 → 100644 +293 −0 Original line number Diff line number Diff line #![deny(clippy::pedantic)] use std::{ collections::{hash_map::Entry, HashMap}, env, fs::{self, File}, io::{BufReader, Read}, os::unix::fs as ufs, path::{Path, PathBuf}, }; use anyhow::{anyhow, Context}; use goblin::{Hint, Object}; use serde::Deserialize; use walkdir::WalkDir; #[derive(Debug, Deserialize)] struct RefGraphNode { path: PathBuf, references: Vec<PathBuf>, } #[derive(Debug, Deserialize)] struct InputDrv { paths: Vec<PathBuf>, priority: i64, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct StructuredAttrsRoot { graph: Vec<RefGraphNode>, paths: Vec<InputDrv>, paths32: Vec<InputDrv>, include_closures: bool, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] struct PriorityKey { implicit: bool, group_priority: i64, priority: i64, root_index: usize, } fn build_reference_map(refs: Vec<RefGraphNode>) -> HashMap<PathBuf, Vec<PathBuf>> { refs.into_iter() .map(|mut gn| { gn.references.retain_mut(|x| x != &gn.path); (gn.path, gn.references) }) .collect() } fn build_priority_keys(roots: &[InputDrv], group_priority: i64) -> HashMap<PathBuf, PriorityKey> { let mut roots_map = HashMap::new(); for (idx, path) in roots.iter().enumerate() { for subpath in &path.paths { let priority = PriorityKey { group_priority, priority: path.priority, root_index: idx, implicit: false, }; roots_map.entry(subpath.clone()).or_insert(priority); } } roots_map } fn extend_to_closure( roots: HashMap<PathBuf, PriorityKey>, refs: &HashMap<PathBuf, Vec<PathBuf>>, ) -> anyhow::Result<HashMap<PathBuf, PriorityKey>> { let mut path_map = HashMap::new(); for (root, priority) in roots { path_map.insert(root.clone(), priority); let mut priority = priority; priority.implicit = true; let mut stack = vec![root.clone()]; while let Some(next) = stack.pop() { match path_map.entry(next.clone()) { Entry::Occupied(mut occupied_entry) => { let old_priority: &PriorityKey = occupied_entry.get(); if old_priority > &priority { occupied_entry.insert(priority); } } Entry::Vacant(vacant_entry) => { vacant_entry.insert(priority); } } stack.extend_from_slice(refs.get(&next).ok_or(anyhow!("encountered unknown path"))?); } } Ok(path_map) } #[derive(Clone, Debug)] struct CandidatePath { root: PathBuf, relative: PathBuf, priority: PriorityKey, } fn collect_candidate_paths( paths: HashMap<PathBuf, PriorityKey>, mapper: impl Fn(&Path, &Path) -> Option<PathBuf>, ) -> anyhow::Result<HashMap<PathBuf, Vec<CandidatePath>>> { let mut candidates: HashMap<_, Vec<_>> = HashMap::new(); for (path, priority) in paths { for entry in WalkDir::new(&path).follow_links(true) { let entry: PathBuf = match entry { Ok(ent) => { // we don't care about directory structure if ent.file_type().is_dir() { continue; } ent.path().into() } Err(e) => { match e.io_error() { // could be a broken symlink, that's fine, we still want to handle those Some(_) => e .path() .ok_or_else(|| anyhow!("I/O error when walking {path:?}"))? .into(), None => { // symlink loop continue; } } } }; let relative = entry.strip_prefix(&path)?.to_owned(); if let Some(mapped) = mapper(&path, &relative) { candidates.entry(mapped).or_default().push(CandidatePath { root: path.clone(), relative, priority, }); } } } Ok(candidates) } fn remap_native_path(root: &Path, p: &Path) -> Option<PathBuf> { if p.starts_with("bin/") || p.starts_with("sbin/") { return Some(PathBuf::from("usr/").join(p)); } // glibc-multilib special case if let Ok(no_lib32) = p.strip_prefix("lib/32/") { return Some(PathBuf::from("usr/lib32/").join(no_lib32)); } remap_multilib_path(root, p) } fn is_32_bit(path: &Path) -> bool { // Be as pessimistic as possible, at least for now. let Ok(mut f) = File::open(path) else { return false; }; let Ok(Hint::Elf(hint)) = goblin::peek(&mut f) else { return false; }; if let Some(is64) = hint.is_64 { return !is64; } let mut buf = vec![]; let Ok(_) = f.read_to_end(&mut buf) else { return false; }; let Ok(Object::Elf(e)) = goblin::Object::parse(&buf) else { return false; }; !e.is_64 } fn remap_multilib_path(root: &Path, p: &Path) -> Option<PathBuf> { if let Ok(no_lib) = p.strip_prefix("lib/") { let full = root.join(p); let libdir = if is_32_bit(&full) { "lib32" } else { "lib64" }; return Some(PathBuf::from("usr/").join(libdir).join(no_lib)); } if p.starts_with("etc/") || p.starts_with("opt/") { return Some(p.into()); } if p.starts_with("share/") || p.starts_with("include/") { return Some(PathBuf::from("usr/").join(p)); } None } fn build_plan( paths: HashMap<PathBuf, PriorityKey>, paths32: HashMap<PathBuf, PriorityKey>, ) -> anyhow::Result<HashMap<PathBuf, PathBuf>> { let candidates_native = collect_candidate_paths(paths, remap_native_path)?; let candidates_32 = collect_candidate_paths(paths32, remap_multilib_path)?; let mut all_candidates: HashMap<_, Vec<_>> = HashMap::new(); for map in [candidates_native, candidates_32] { for (path, candidates) in map { all_candidates .entry(path) .or_default() .extend_from_slice(&candidates); } } let mut final_plan: HashMap<PathBuf, PathBuf> = HashMap::new(); for (path, candidates) in all_candidates { let best = candidates .into_iter() .min_by_key(|&CandidatePath { priority, .. }| priority) .ok_or(anyhow!("candidate list empty"))?; final_plan.insert(path, best.root.join(best.relative)); } Ok(final_plan) } fn build_env(out: &Path, plan: HashMap<PathBuf, PathBuf>) -> anyhow::Result<()> { fs::create_dir_all(out)?; for (dest, src) in plan { let full_dest = out.join(&dest); let dest_dir = full_dest .parent() .ok_or(anyhow!("destination directory is root")) .with_context(|| { format!("When trying to determine destination directory for {full_dest:?}") })?; fs::create_dir_all(dest_dir) .with_context(|| format!("When trying to create directory {dest_dir:?}"))?; ufs::symlink(&src, &full_dest) .with_context(|| format!("When symlinking {src:?} to {full_dest:?}"))?; } Ok(()) } fn main() -> anyhow::Result<()> { let filename = env::var("NIX_ATTRS_JSON_FILE")?; let reader = File::open(filename)?; let buf_reader = BufReader::new(reader); let attrs: StructuredAttrsRoot = serde_json::from_reader(buf_reader)?; let mut paths = build_priority_keys(&attrs.paths, 1); let mut paths32 = build_priority_keys(&attrs.paths32, 2); if attrs.include_closures { let refs = build_reference_map(attrs.graph); paths = extend_to_closure(paths, &refs)?; paths32 = extend_to_closure(paths32, &refs)?; }; let plan = build_plan(paths, paths32)?; let out_dir = env::var("out")?; build_env(&PathBuf::from(out_dir), plan) }