Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1# This setup hook causes the fixup phase to rewrite all script
2# interpreter file names (`#! /path') to paths found in $PATH. E.g.,
3# /bin/sh will be rewritten to /nix/store/<hash>-some-bash/bin/sh.
4# /usr/bin/env gets special treatment so that ".../bin/env python" is
5# rewritten to /nix/store/<hash>/bin/python. Interpreters that are
6# already in the store are left untouched.
7# A script file must be marked as executable, otherwise it will not be
8# considered.
9
10fixupOutputHooks+=(patchShebangsAuto)
11
12# Run patch shebangs on a directory or file.
13# Can take multiple paths as arguments.
14# patchShebangs [--build | --host | --update] [--] PATH...
15
16# Flags:
17# --build : Lookup commands available at build-time
18# --host : Lookup commands available at runtime
19# --update : Update shebang paths that are in Nix store
20
21# Example use cases,
22# $ patchShebangs --host /nix/store/...-hello-1.0/bin
23# $ patchShebangs --build configure
24
25patchShebangs() {
26 local pathName
27 local update=false
28
29 while [[ $# -gt 0 ]]; do
30 case "$1" in
31 --host)
32 pathName=HOST_PATH
33 shift
34 ;;
35 --build)
36 pathName=PATH
37 shift
38 ;;
39 --update)
40 update=true
41 shift
42 ;;
43 --)
44 shift
45 break
46 ;;
47 -*|--*)
48 echo "Unknown option $1 supplied to patchShebangs" >&2
49 return 1
50 ;;
51 *)
52 break
53 ;;
54 esac
55 done
56
57 echo "patching script interpreter paths in $@"
58 local f
59 local oldPath
60 local newPath
61 local arg0
62 local args
63 local oldInterpreterLine
64 local newInterpreterLine
65
66 if [[ $# -eq 0 ]]; then
67 echo "No arguments supplied to patchShebangs" >&2
68 return 0
69 fi
70
71 local f
72 while IFS= read -r -d $'\0' f; do
73 isScript "$f" || continue
74
75 # read exits unclean if the shebang does not end with a newline, but still assigns the variable.
76 # So if read returns errno != 0, we check if the assigned variable is non-empty and continue.
77 read -r oldInterpreterLine < "$f" || [ "$oldInterpreterLine" ]
78
79 read -r oldPath arg0 args <<< "${oldInterpreterLine:2}"
80
81 if [[ -z "${pathName:-}" ]]; then
82 if [[ -n $strictDeps && $f == "$NIX_STORE"* ]]; then
83 pathName=HOST_PATH
84 else
85 pathName=PATH
86 fi
87 fi
88
89 if [[ "$oldPath" == *"/bin/env" ]]; then
90 if [[ $arg0 == "-S" ]]; then
91 arg0=${args%% *}
92 [[ "$args" == *" "* ]] && args=${args#* } || args=
93 newPath="$(PATH="${!pathName}" type -P "env" || true)"
94 args="-S $(PATH="${!pathName}" type -P "$arg0" || true) $args"
95
96 # Check for unsupported 'env' functionality:
97 # - options: something starting with a '-' besides '-S'
98 # - environment variables: foo=bar
99 elif [[ $arg0 == "-"* || $arg0 == *"="* ]]; then
100 echo "$f: unsupported interpreter directive \"$oldInterpreterLine\" (set dontPatchShebangs=1 and handle shebang patching yourself)" >&2
101 exit 1
102 else
103 newPath="$(PATH="${!pathName}" type -P "$arg0" || true)"
104 fi
105 else
106 if [[ -z $oldPath ]]; then
107 # If no interpreter is specified linux will use /bin/sh. Set
108 # oldpath="/bin/sh" so that we get /nix/store/.../sh.
109 oldPath="/bin/sh"
110 fi
111
112 newPath="$(PATH="${!pathName}" type -P "$(basename "$oldPath")" || true)"
113
114 args="$arg0 $args"
115 fi
116
117 # Strip trailing whitespace introduced when no arguments are present
118 newInterpreterLine="$newPath $args"
119 newInterpreterLine=${newInterpreterLine%${newInterpreterLine##*[![:space:]]}}
120
121 if [[ -n "$oldPath" && ( "$update" == true || "${oldPath:0:${#NIX_STORE}}" != "$NIX_STORE" ) ]]; then
122 if [[ -n "$newPath" && "$newPath" != "$oldPath" ]]; then
123 echo "$f: interpreter directive changed from \"$oldInterpreterLine\" to \"$newInterpreterLine\""
124 # escape the escape chars so that sed doesn't interpret them
125 escapedInterpreterLine=${newInterpreterLine//\\/\\\\}
126
127 # Preserve times, see: https://github.com/NixOS/nixpkgs/pull/33281
128 timestamp=$(stat --printf "%y" "$f")
129
130 # Manually create temporary file instead of using sed -i
131 # (sed -i on $out/x creates tmpfile /nix/store/x which fails on macos + sandbox)
132 tmpFile=$(mktemp -t patchShebangs.XXXXXXXXXX)
133 sed -e "1 s|.*|#\!$escapedInterpreterLine|" "$f" > "$tmpFile"
134
135 # Make original file writable if it is read-only
136 local restoreReadOnly
137 if [[ ! -w "$f" ]]; then
138 chmod +w "$f"
139 restoreReadOnly=true
140 fi
141
142 # Replace the original file's content with the patched content
143 # (preserving permissions)
144 cat "$tmpFile" > "$f"
145 rm "$tmpFile"
146 if [[ -n "${restoreReadOnly:-}" ]]; then
147 chmod -w "$f"
148 fi
149
150 touch --date "$timestamp" "$f"
151 fi
152 fi
153 done < <(find "$@" -type f -perm -0100 -print0)
154}
155
156patchShebangsAuto () {
157 if [[ -z "${dontPatchShebangs-}" && -e "$prefix" ]]; then
158
159 # Dev output will end up being run on the build platform. An
160 # example case of this is sdl2-config. Otherwise, we can just
161 # use the runtime path (--host).
162 if [[ "$output" != out && "$output" = "$outputDev" ]]; then
163 patchShebangs --build "$prefix"
164 else
165 patchShebangs --host "$prefix"
166 fi
167 fi
168}