-
-
Notifications
You must be signed in to change notification settings - Fork 14.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
stdenv: add no-broken-symlinks hook (#370750)
- Loading branch information
Showing
5 changed files
with
275 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) ]; | ||
}; | ||
} |