mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-21 22:43:01 +00:00
buildFHSEnv: rewrite env building
This replaces the mess of buildEnvs with a single Rust binary that spits out a mostly-complete root filesystem for an fhsenv. The main goal is to have includeClosures, as we want all of the dependencies to be in the fhsenv to avoid Steam's (and others') LD_LIBRARY_PATH shenanigans, but without 32-bit libraries leaking into lib64 when a 64-bit package like mangohud depends on a 32-bit version of itself. We "fix" this by actually looking at the files and explicitly moving 32-bit stuff to $out/lib32. This could be avoided if we had recursive Nix, or at least system info in exportReferencesGraph, but alas. For some reason this also shrinks the fhsenvs massively, even though there's currently no layout optimization (e.g. a package with paths like lib/foo/{bar,baz} will produce two symlinks in the output, even when it's more optimal to symlink lib/foo to $out/lib/foo directly).
This commit is contained in:
parent
ccac709d91
commit
a182a53243
@ -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)
|
||||
@ -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
|
||||
@ -77,7 +74,7 @@ let
|
||||
xz
|
||||
];
|
||||
baseMultiPaths = with pkgsi686Linux; [
|
||||
(toString gcc.cc.lib)
|
||||
gcc.cc.lib
|
||||
];
|
||||
|
||||
ldconfig = writeShellScriptBin "ldconfig" ''
|
||||
@ -85,190 +82,139 @@ 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" ''
|
||||
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"
|
||||
export TZDIR='/etc/zoneinfo'
|
||||
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"
|
||||
export TZDIR='/etc/zoneinfo'
|
||||
|
||||
# XDG_DATA_DIRS is used by pressure-vessel (steam proton) and vulkan loaders to find the corresponding icd
|
||||
export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}/run/opengl-driver/share:/run/opengl-driver-32/share
|
||||
# XDG_DATA_DIRS is used by pressure-vessel (steam proton) and vulkan loaders to find the corresponding icd
|
||||
export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}/run/opengl-driver/share:/run/opengl-driver-32/share
|
||||
|
||||
# Following XDG spec [1], XDG_DATA_DIRS should default to "/usr/local/share:/usr/share".
|
||||
# In nix, it is commonly set without containing these values, so we add them as fallback.
|
||||
#
|
||||
# [1] <https://specifications.freedesktop.org/basedir-spec/latest>
|
||||
case ":$XDG_DATA_DIRS:" in
|
||||
*:/usr/local/share:*) ;;
|
||||
*) export XDG_DATA_DIRS="$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}/usr/local/share" ;;
|
||||
esac
|
||||
case ":$XDG_DATA_DIRS:" in
|
||||
*:/usr/share:*) ;;
|
||||
*) export XDG_DATA_DIRS="$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}/usr/share" ;;
|
||||
esac
|
||||
# Following XDG spec [1], XDG_DATA_DIRS should default to "/usr/local/share:/usr/share".
|
||||
# In nix, it is commonly set without containing these values, so we add them as fallback.
|
||||
#
|
||||
# [1] <https://specifications.freedesktop.org/basedir-spec/latest>
|
||||
case ":$XDG_DATA_DIRS:" in
|
||||
*:/usr/local/share:*) ;;
|
||||
*) export XDG_DATA_DIRS="$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}/usr/local/share" ;;
|
||||
esac
|
||||
case ":$XDG_DATA_DIRS:" in
|
||||
*:/usr/share:*) ;;
|
||||
*) export XDG_DATA_DIRS="$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}/usr/share" ;;
|
||||
esac
|
||||
|
||||
# Force compilers and other tools to look in default search paths
|
||||
unset NIX_ENFORCE_PURITY
|
||||
export NIX_BINTOOLS_WRAPPER_TARGET_HOST_${stdenv.cc.suffixSalt}=1
|
||||
export NIX_CC_WRAPPER_TARGET_HOST_${stdenv.cc.suffixSalt}=1
|
||||
export NIX_CFLAGS_COMPILE='-idirafter /usr/include'
|
||||
export NIX_CFLAGS_LINK='-L/usr/lib -L/usr/lib32'
|
||||
export NIX_LDFLAGS='-L/usr/lib -L/usr/lib32'
|
||||
export PKG_CONFIG_PATH=/usr/lib/pkgconfig
|
||||
export ACLOCAL_PATH=/usr/share/aclocal
|
||||
# Force compilers and other tools to look in default search paths
|
||||
unset NIX_ENFORCE_PURITY
|
||||
export NIX_BINTOOLS_WRAPPER_TARGET_HOST_${stdenv.cc.suffixSalt}=1
|
||||
export NIX_CC_WRAPPER_TARGET_HOST_${stdenv.cc.suffixSalt}=1
|
||||
export NIX_CFLAGS_COMPILE='-idirafter /usr/include'
|
||||
export NIX_CFLAGS_LINK='-L/usr/lib -L/usr/lib32'
|
||||
export NIX_LDFLAGS='-L/usr/lib -L/usr/lib32'
|
||||
export PKG_CONFIG_PATH=/usr/lib/pkgconfig
|
||||
export ACLOCAL_PATH=/usr/share/aclocal
|
||||
|
||||
# GStreamer searches for plugins relative to its real binary's location
|
||||
# https://gitlab.freedesktop.org/gstreamer/gstreamer/-/commit/bd97973ce0f2c5495bcda5cccd4f7ef7dcb7febc
|
||||
export GST_PLUGIN_SYSTEM_PATH_1_0=/usr/lib/gstreamer-1.0:/usr/lib32/gstreamer-1.0
|
||||
# GStreamer searches for plugins relative to its real binary's location
|
||||
# https://gitlab.freedesktop.org/gstreamer/gstreamer/-/commit/bd97973ce0f2c5495bcda5cccd4f7ef7dcb7febc
|
||||
export GST_PLUGIN_SYSTEM_PATH_1_0=/usr/lib/gstreamer-1.0:/usr/lib32/gstreamer-1.0
|
||||
|
||||
${profile}
|
||||
${profile}
|
||||
'';
|
||||
};
|
||||
|
||||
ensureGsettingsSchemasIsDirectory = runCommandLocal "fhsenv-ensure-gsettings-schemas-directory" {} ''
|
||||
mkdir -p $out/share/glib-2.0/schemas
|
||||
touch $out/share/glib-2.0/schemas/.keep
|
||||
'';
|
||||
|
||||
# Compose /etc for the fhs environment
|
||||
etcPkg = runCommandLocal "${name}-fhs-etc" { } ''
|
||||
mkdir -p $out/etc
|
||||
pushd $out/etc
|
||||
# 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;
|
||||
|
||||
# environment variables
|
||||
ln -s ${etcProfile} profile
|
||||
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"]
|
||||
);
|
||||
|
||||
allPaths = paths ++ paths32;
|
||||
|
||||
rootfs-builder = pkgs.rustPlatform.buildRustPackage {
|
||||
name = "fhs-rootfs-bulder";
|
||||
src = ./rootfs-builder;
|
||||
cargoLock.lockFile = ./rootfs-builder/Cargo.lock;
|
||||
doCheck = false;
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
# symlink /etc/mtab -> /proc/mounts (compat for old userspace progs)
|
||||
ln -s /proc/mounts mtab
|
||||
'';
|
||||
ln -s /proc/mounts $out/etc/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
|
||||
|
||||
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
|
||||
'';
|
||||
inherit includeClosures;
|
||||
};
|
||||
|
||||
staticUsrProfileMulti = buildEnv {
|
||||
name = "${name}-usr-multi";
|
||||
paths = baseMultiPaths ++ multiPaths;
|
||||
extraOutputsToInstall = [ "out" "lib" ] ++ extraOutputsToInstall;
|
||||
ignoreCollisions = true;
|
||||
inherit includeClosures;
|
||||
};
|
||||
|
||||
# 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"}
|
||||
'';
|
||||
|
||||
# setup /lib, /lib32 and /lib64
|
||||
setupLibDirsMulti = ''
|
||||
mkdir -m0755 lib32
|
||||
mkdir -m0755 lib64
|
||||
ln -s lib64 lib
|
||||
|
||||
# copy glibc stuff
|
||||
cp -rsHf ${staticUsrProfileTarget}/lib/32/* lib32/
|
||||
chmod u+w -R lib32/
|
||||
|
||||
# copy content of multiPaths (32bit libs)
|
||||
if [ -d ${staticUsrProfileMulti}/lib ]; then
|
||||
cp -rsHf ${staticUsrProfileMulti}/lib/* lib32/
|
||||
chmod u+w -R lib32/
|
||||
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
|
||||
${pkgs.glib.dev}/bin/glib-compile-schemas $out/usr/share/glib-2.0/schemas
|
||||
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/
|
||||
${extraBuildCommands}
|
||||
${lib.optionalString isMultiBuild extraBuildCommandsMulti}
|
||||
'';
|
||||
|
||||
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 ..
|
||||
|
||||
for i in etc opt; do
|
||||
if [ -d "${staticUsrProfileTarget}/$i" ]; then
|
||||
cp -rsHf "${staticUsrProfileTarget}/$i" "$i"
|
||||
fi
|
||||
done
|
||||
for i in usr/{bin,sbin,lib,lib32,lib64}; do
|
||||
if [ -d "$i" ]; then
|
||||
ln -s "$i"
|
||||
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
|
||||
|
249
pkgs/build-support/build-fhsenv-bubblewrap/rootfs-builder/Cargo.lock
generated
Normal file
249
pkgs/build-support/build-fhsenv-bubblewrap/rootfs-builder/Cargo.lock
generated
Normal file
@ -0,0 +1,249 @@
|
||||
# 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"
|
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "rootfs-builder"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "*"
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
serde_json = "*"
|
||||
walkdir = "*"
|
||||
goblin = "*"
|
@ -0,0 +1,293 @@
|
||||
#![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…
Reference in New Issue
Block a user