mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-01-31 17:23:34 +00:00
buildFHSEnv: rewrite env building (#351928)
This commit is contained in:
commit
6e80056430
@ -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