-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
stdenv: add no-broken-symlinks hook #370750
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
philiptaron
merged 8 commits into
NixOS:staging
from
ConnorBaker:feat/stdenv-no-broken-symlinks-hook
Jan 23, 2025
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
aa1405b
stdenv: add no-broken-symlinks hook
ConnorBaker 0ad7c9e
no-broken-symlinks: guard against double inclusions
ConnorBaker 9b9badd
no-broken-symlinks: check for reflexivity before dangling
ConnorBaker ba1297b
no-broken-symlinks: exit instead of returning 1 for cleaner log
ConnorBaker 4e8e175
doc: add stdenv entry for no-broken-symlinks.sh
ConnorBaker 229fdf0
test.stdenv.hooks.no-broken-symlinks: init
ConnorBaker 51b2764
no-broken-symlinks: provide only dontCheckForBrokenSymlinks and test …
ConnorBaker 34539b2
no-broken-symlinks: actually interpolate relative paths
ConnorBaker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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) ]; | ||
| }; | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.