stdenv: add no-broken-symlinks hook (#370750)
This commit is contained in:
@@ -1371,6 +1371,16 @@ This setup hook moves any systemd user units installed in the `lib/` subdirector
|
||||
|
||||
This hook only runs when compiling for Linux.
|
||||
|
||||
### `no-broken-symlinks.sh` {#no-broken-symlinks.sh}
|
||||
|
||||
This setup hook checks for, reports, and (by default) fails builds when "broken" symlinks are found. A symlink is considered "broken" if it's dangling (the target doesn't exist) or reflexive (it refers to itself).
|
||||
|
||||
This hook can be disabled by setting `dontCheckForBrokenSymlinks`.
|
||||
|
||||
::: {.note}
|
||||
The check for reflexivity is direct and does not account for transitivity, so this hook will not prevent cycles in symlinks.
|
||||
:::
|
||||
|
||||
### `set-source-date-epoch-to-latest.sh` {#set-source-date-epoch-to-latest.sh}
|
||||
|
||||
This sets `SOURCE_DATE_EPOCH` to the modification time of the most recent file.
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Guard against double inclusion.
|
||||
if (("${noBrokenSymlinksHookInstalled:-0}" > 0)); then
|
||||
nixInfoLog "skipping because the hook has been propagated more than once"
|
||||
return 0
|
||||
fi
|
||||
declare -ig noBrokenSymlinksHookInstalled=1
|
||||
|
||||
# symlinks are often created in postFixup
|
||||
# don't use fixupOutputHooks, it is before postFixup
|
||||
postFixupHooks+=(noBrokenSymlinksInAllOutputs)
|
||||
|
||||
# A symlink is "dangling" if it points to a non-existent target.
|
||||
# A symlink is "reflexive" if it points to itself.
|
||||
# A symlink is considered "broken" if it is either dangling or reflexive.
|
||||
noBrokenSymlinks() {
|
||||
local -r output="${1:?}"
|
||||
local path
|
||||
local pathParent
|
||||
local symlinkTarget
|
||||
local -i numDanglingSymlinks=0
|
||||
local -i numReflexiveSymlinks=0
|
||||
|
||||
# NOTE(@connorbaker): This hook doesn't check for cycles in symlinks.
|
||||
|
||||
if [[ ! -e $output ]]; then
|
||||
nixWarnLog "skipping non-existent output $output"
|
||||
return 0
|
||||
fi
|
||||
nixInfoLog "running on $output"
|
||||
|
||||
# NOTE: path is absolute because we're running `find` against an absolute path (`output`).
|
||||
while IFS= read -r -d $'\0' path; do
|
||||
pathParent="$(dirname "$path")"
|
||||
symlinkTarget="$(readlink "$path")"
|
||||
|
||||
# Canonicalize symlinkTarget to an absolute path.
|
||||
if [[ $symlinkTarget == /* ]]; then
|
||||
nixInfoLog "symlink $path points to absolute target $symlinkTarget"
|
||||
else
|
||||
nixInfoLog "symlink $path points to relative target $symlinkTarget"
|
||||
# Use --no-symlinks to avoid dereferencing again and --canonicalize-missing to avoid existence
|
||||
# checks at this step (which can lead to infinite recursion).
|
||||
symlinkTarget="$(realpath --no-symlinks --canonicalize-missing "$pathParent/$symlinkTarget")"
|
||||
fi
|
||||
|
||||
if [[ $path == "$symlinkTarget" ]]; then
|
||||
nixErrorLog "the symlink $path is reflexive $symlinkTarget"
|
||||
numReflexiveSymlinks+=1
|
||||
elif [[ ! -e $symlinkTarget ]]; then
|
||||
nixErrorLog "the symlink $path points to a missing target $symlinkTarget"
|
||||
numDanglingSymlinks+=1
|
||||
else
|
||||
nixDebugLog "the symlink $path is irreflexive and points to a target which exists"
|
||||
fi
|
||||
done < <(find "$output" -type l -print0)
|
||||
|
||||
if ((numDanglingSymlinks > 0 || numReflexiveSymlinks > 0)); then
|
||||
nixErrorLog "found $numDanglingSymlinks dangling symlinks and $numReflexiveSymlinks reflexive symlinks"
|
||||
exit 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
noBrokenSymlinksInAllOutputs() {
|
||||
if [[ -z ${dontCheckForBrokenSymlinks-} ]]; then
|
||||
for output in $(getAllOutputNames); do
|
||||
noBrokenSymlinks "${!output}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
@@ -77,6 +77,7 @@ let
|
||||
../../build-support/setup-hooks/move-sbin.sh
|
||||
../../build-support/setup-hooks/move-systemd-user-units.sh
|
||||
../../build-support/setup-hooks/multiple-outputs.sh
|
||||
../../build-support/setup-hooks/no-broken-symlinks.sh
|
||||
../../build-support/setup-hooks/patch-shebangs.sh
|
||||
../../build-support/setup-hooks/prune-libtool-files.sh
|
||||
../../build-support/setup-hooks/reproducible-builds.sh
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
[[ -e $out/bin/foo ]]
|
||||
'';
|
||||
};
|
||||
no-broken-symlinks = import ./no-broken-symlinks.nix { inherit stdenv lib pkgs; };
|
||||
# TODO: add multiple-outputs
|
||||
patch-shebangs = import ./patch-shebangs.nix { inherit stdenv lib pkgs; };
|
||||
prune-libtool-files =
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
stdenv,
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib.strings) concatStringsSep;
|
||||
inherit (pkgs) runCommand;
|
||||
inherit (pkgs.testers) testBuildFailure;
|
||||
|
||||
mkDanglingSymlink = absolute: ''
|
||||
ln -s${if absolute then "r" else ""} "$out/dangling" "$out/dangling-symlink"
|
||||
'';
|
||||
|
||||
mkReflexiveSymlink = absolute: ''
|
||||
ln -s${if absolute then "r" else ""} "$out/reflexive-symlink" "$out/reflexive-symlink"
|
||||
'';
|
||||
|
||||
mkValidSymlink = absolute: ''
|
||||
touch "$out/valid"
|
||||
ln -s${if absolute then "r" else ""} "$out/valid" "$out/valid-symlink"
|
||||
'';
|
||||
|
||||
testBuilder =
|
||||
{
|
||||
name,
|
||||
commands ? [ ],
|
||||
derivationArgs ? { },
|
||||
}:
|
||||
stdenv.mkDerivation (
|
||||
{
|
||||
inherit name;
|
||||
strictDeps = true;
|
||||
dontUnpack = true;
|
||||
dontPatch = true;
|
||||
dontConfigure = true;
|
||||
dontBuild = true;
|
||||
installPhase =
|
||||
''
|
||||
mkdir -p "$out"
|
||||
|
||||
''
|
||||
+ concatStringsSep "\n" commands;
|
||||
}
|
||||
// derivationArgs
|
||||
);
|
||||
in
|
||||
{
|
||||
fail-dangling-symlink-relative =
|
||||
runCommand "fail-dangling-symlink-relative"
|
||||
{
|
||||
failed = testBuildFailure (testBuilder {
|
||||
name = "fail-dangling-symlink-relative-inner";
|
||||
commands = [ (mkDanglingSymlink false) ];
|
||||
});
|
||||
}
|
||||
''
|
||||
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
|
||||
grep -F 'found 1 dangling symlinks and 0 reflexive symlinks' "$failed/testBuildFailure.log"
|
||||
touch $out
|
||||
'';
|
||||
|
||||
pass-dangling-symlink-relative-allowed = testBuilder {
|
||||
name = "pass-dangling-symlink-relative-allowed";
|
||||
commands = [ (mkDanglingSymlink false) ];
|
||||
derivationArgs.dontCheckForBrokenSymlinks = true;
|
||||
};
|
||||
|
||||
fail-dangling-symlink-absolute =
|
||||
runCommand "fail-dangling-symlink-absolute"
|
||||
{
|
||||
failed = testBuildFailure (testBuilder {
|
||||
name = "fail-dangling-symlink-absolute-inner";
|
||||
commands = [ (mkDanglingSymlink true) ];
|
||||
});
|
||||
}
|
||||
''
|
||||
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
|
||||
grep -F 'found 1 dangling symlinks and 0 reflexive symlinks' "$failed/testBuildFailure.log"
|
||||
touch $out
|
||||
'';
|
||||
|
||||
pass-dangling-symlink-absolute-allowed = testBuilder {
|
||||
name = "pass-dangling-symlink-absolute-allowed";
|
||||
commands = [ (mkDanglingSymlink true) ];
|
||||
derivationArgs.dontCheckForBrokenSymlinks = true;
|
||||
};
|
||||
|
||||
fail-reflexive-symlink-relative =
|
||||
runCommand "fail-reflexive-symlink-relative"
|
||||
{
|
||||
failed = testBuildFailure (testBuilder {
|
||||
name = "fail-reflexive-symlink-relative-inner";
|
||||
commands = [ (mkReflexiveSymlink false) ];
|
||||
});
|
||||
}
|
||||
''
|
||||
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
|
||||
grep -F 'found 0 dangling symlinks and 1 reflexive symlinks' "$failed/testBuildFailure.log"
|
||||
touch $out
|
||||
'';
|
||||
|
||||
pass-reflexive-symlink-relative-allowed = testBuilder {
|
||||
name = "pass-reflexive-symlink-relative-allowed";
|
||||
commands = [ (mkReflexiveSymlink false) ];
|
||||
derivationArgs.dontCheckForBrokenSymlinks = true;
|
||||
};
|
||||
|
||||
fail-reflexive-symlink-absolute =
|
||||
runCommand "fail-reflexive-symlink-absolute"
|
||||
{
|
||||
failed = testBuildFailure (testBuilder {
|
||||
name = "fail-reflexive-symlink-absolute-inner";
|
||||
commands = [ (mkReflexiveSymlink true) ];
|
||||
});
|
||||
}
|
||||
''
|
||||
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
|
||||
grep -F 'found 0 dangling symlinks and 1 reflexive symlinks' "$failed/testBuildFailure.log"
|
||||
touch $out
|
||||
'';
|
||||
|
||||
pass-reflexive-symlink-absolute-allowed = testBuilder {
|
||||
name = "pass-reflexive-symlink-absolute-allowed";
|
||||
commands = [ (mkReflexiveSymlink true) ];
|
||||
derivationArgs.dontCheckForBrokenSymlinks = true;
|
||||
};
|
||||
|
||||
fail-broken-symlinks-relative =
|
||||
runCommand "fail-broken-symlinks-relative"
|
||||
{
|
||||
failed = testBuildFailure (testBuilder {
|
||||
name = "fail-broken-symlinks-relative-inner";
|
||||
commands = [
|
||||
(mkDanglingSymlink false)
|
||||
(mkReflexiveSymlink false)
|
||||
];
|
||||
});
|
||||
}
|
||||
''
|
||||
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
|
||||
grep -F 'found 1 dangling symlinks and 1 reflexive symlinks' "$failed/testBuildFailure.log"
|
||||
touch $out
|
||||
'';
|
||||
|
||||
pass-broken-symlinks-relative-allowed = testBuilder {
|
||||
name = "pass-broken-symlinks-relative-allowed";
|
||||
commands = [
|
||||
(mkDanglingSymlink false)
|
||||
(mkReflexiveSymlink false)
|
||||
];
|
||||
derivationArgs.dontCheckForBrokenSymlinks = true;
|
||||
};
|
||||
|
||||
fail-broken-symlinks-absolute =
|
||||
runCommand "fail-broken-symlinks-absolute"
|
||||
{
|
||||
failed = testBuildFailure (testBuilder {
|
||||
name = "fail-broken-symlinks-absolute-inner";
|
||||
commands = [
|
||||
(mkDanglingSymlink true)
|
||||
(mkReflexiveSymlink true)
|
||||
];
|
||||
});
|
||||
}
|
||||
''
|
||||
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
|
||||
grep -F 'found 1 dangling symlinks and 1 reflexive symlinks' "$failed/testBuildFailure.log"
|
||||
touch $out
|
||||
'';
|
||||
|
||||
pass-broken-symlinks-absolute-allowed = testBuilder {
|
||||
name = "pass-broken-symlinks-absolute-allowed";
|
||||
commands = [
|
||||
(mkDanglingSymlink true)
|
||||
(mkReflexiveSymlink true)
|
||||
];
|
||||
derivationArgs.dontCheckForBrokenSymlinks = true;
|
||||
};
|
||||
|
||||
pass-valid-symlink-relative = testBuilder {
|
||||
name = "pass-valid-symlink-relative";
|
||||
commands = [ (mkValidSymlink false) ];
|
||||
};
|
||||
|
||||
pass-valid-symlink-absolute = testBuilder {
|
||||
name = "pass-valid-symlink-absolute";
|
||||
commands = [ (mkValidSymlink true) ];
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user