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
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#* }
93 newPath="$(PATH="${!pathName}" command -v "env" || true)"
94 args="-S $(PATH="${!pathName}" command -v "$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}" command -v "$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}" command -v "$(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 sed -i -e "1 s|.*|#\!$escapedInterpreterLine|" "$f"
130 touch --date "$timestamp" "$f"
131 fi
132 fi
133 done < <(find "$@" -type f -perm -0100 -print0)
134}
135
136patchShebangsAuto () {
137 if [[ -z "${dontPatchShebangs-}" && -e "$prefix" ]]; then
138
139 # Dev output will end up being run on the build platform. An
140 # example case of this is sdl2-config. Otherwise, we can just
141 # use the runtime path (--host).
142 if [[ "$output" != out && "$output" = "$outputDev" ]]; then
143 patchShebangs --build "$prefix"
144 else
145 patchShebangs --host "$prefix"
146 fi
147 fi
148}