1{
2 lib,
3 stdenv,
4 pkgs,
5}:
6
7# since the tests are using a early stdenv, the stdenv will have dontPatchShebangs=1, so it has to be unset
8# https://github.com/NixOS/nixpkgs/blob/768a982bfc9d29a6bd3beb963ed4b054451ce3d0/pkgs/stdenv/linux/default.nix#L148-L153
9
10# strictDeps has to be disabled because the shell isn't in buildInputs
11
12let
13 tests = {
14 bad-shebang = stdenv.mkDerivation {
15 name = "bad-shebang";
16 strictDeps = false;
17 dontUnpack = true;
18 installPhase = ''
19 mkdir -p $out/bin
20 echo "#!/bin/bash" > $out/bin/test
21 echo "echo -n hello" >> $out/bin/test
22 chmod +x $out/bin/test
23 dontPatchShebangs=
24 '';
25 passthru = {
26 assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
27 };
28 };
29
30 ignores-nix-store = stdenv.mkDerivation {
31 name = "ignores-nix-store";
32 strictDeps = false;
33 dontUnpack = true;
34 installPhase = ''
35 mkdir -p $out/bin
36 echo "#!$NIX_STORE/path/to/bash" > $out/bin/test
37 echo "echo -n hello" >> $out/bin/test
38 chmod +x $out/bin/test
39 dontPatchShebangs=
40 '';
41 passthru = {
42 assertion = "grep \"^#!$NIX_STORE/path/to/bash\" $out/bin/test > /dev/null";
43 };
44 };
45
46 updates-nix-store = stdenv.mkDerivation {
47 name = "updates-nix-store";
48 strictDeps = false;
49 dontUnpack = true;
50 installPhase = ''
51 mkdir -p $out/bin
52 echo "#!$NIX_STORE/path/to/bash" > $out/bin/test
53 echo "echo -n hello" >> $out/bin/test
54 chmod +x $out/bin/test
55 patchShebangs --update $out/bin/test
56 dontPatchShebangs=1
57 '';
58 passthru = {
59 assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
60 };
61 };
62
63 split-string = stdenv.mkDerivation {
64 name = "split-string";
65 strictDeps = false;
66 dontUnpack = true;
67 installPhase = ''
68 mkdir -p $out/bin
69 echo "#!/usr/bin/env -S bash --posix" > $out/bin/test
70 echo "echo -n hello" >> $out/bin/test
71 chmod +x $out/bin/test
72 dontPatchShebangs=
73 '';
74 passthru = {
75 assertion = "grep -v '^#!${pkgs.coreutils}/bin/env -S ${stdenv.shell} --posix' $out/bin/test > /dev/null";
76 };
77 };
78
79 without-trailing-newline = stdenv.mkDerivation {
80 name = "without-trailing-newline";
81 strictDeps = false;
82 dontUnpack = true;
83 installPhase = ''
84 mkdir -p $out/bin
85 printf "#!/bin/bash" > $out/bin/test
86 chmod +x $out/bin/test
87 dontPatchShebangs=
88 '';
89 passthru = {
90 assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
91 };
92 };
93
94 dont-patch-builtins = stdenv.mkDerivation {
95 name = "dont-patch-builtins";
96 strictDeps = false;
97 dontUnpack = true;
98 installPhase = ''
99 mkdir -p $out/bin
100 echo "#!/usr/bin/builtin" > $out/bin/test
101 chmod +x $out/bin/test
102 dontPatchShebangs=
103 '';
104 passthru = {
105 assertion = "grep '^#!/usr/bin/builtin' $out/bin/test > /dev/null";
106 };
107 };
108
109 read-only-script =
110 (derivation {
111 name = "read-only-script";
112 system = stdenv.buildPlatform.system;
113 builder = "${stdenv.__bootPackages.stdenv.__bootPackages.bashNonInteractive}/bin/bash";
114 initialPath = [
115 stdenv.__bootPackages.stdenv.__bootPackages.coreutils
116 ];
117 strictDeps = false;
118 args = [
119 "-c"
120 ''
121 set -euo pipefail
122 . ${../../stdenv/generic/setup.sh}
123 . ${../../build-support/setup-hooks/patch-shebangs.sh}
124 mkdir -p $out/bin
125 echo "#!/bin/bash" > $out/bin/test
126 echo "echo -n hello" >> $out/bin/test
127 chmod 555 $out/bin/test
128 patchShebangs $out/bin/test
129 ''
130 ];
131 assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
132 })
133 // {
134 meta = { };
135 };
136
137 preserves-read-only =
138 (derivation {
139 name = "preserves-read-only";
140 system = stdenv.buildPlatform.system;
141 builder = "${stdenv.__bootPackages.stdenv.__bootPackages.bashNonInteractive}/bin/bash";
142 initialPath = [
143 stdenv.__bootPackages.stdenv.__bootPackages.coreutils
144 ];
145 strictDeps = false;
146 args = [
147 "-c"
148 ''
149 set -euo pipefail
150 . ${../../stdenv/generic/setup.sh}
151 . ${../../build-support/setup-hooks/patch-shebangs.sh}
152 mkdir -p $out/bin
153 echo "#!/bin/bash" > $out/bin/test
154 echo "echo -n hello" >> $out/bin/test
155 chmod 555 $out/bin/test
156 original_perms=$(stat -c %a $out/bin/test)
157 patchShebangs $out/bin/test
158 new_perms=$(stat -c %a $out/bin/test)
159 if ! [ "$original_perms" = "$new_perms" ]; then
160 echo "Permissions changed from $original_perms to $new_perms"
161 exit 1
162 fi
163 ''
164 ];
165 assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
166 })
167 // {
168 meta = { };
169 };
170
171 # Preserve times, see: https://github.com/NixOS/nixpkgs/pull/33281
172 preserves-timestamp =
173 (derivation {
174 name = "preserves-timestamp";
175 system = stdenv.buildPlatform.system;
176 builder = "${stdenv.__bootPackages.stdenv.__bootPackages.bashNonInteractive}/bin/bash";
177 initialPath = [
178 stdenv.__bootPackages.stdenv.__bootPackages.coreutils
179 ];
180 strictDeps = false;
181 args = [
182 "-c"
183 ''
184 set -euo pipefail
185 . ${../../stdenv/generic/setup.sh}
186 . ${../../build-support/setup-hooks/patch-shebangs.sh}
187 mkdir -p $out/bin
188 echo "#!/bin/bash" > $out/bin/test
189 echo "echo -n hello" >> $out/bin/test
190 chmod +x $out/bin/test
191 # Set a specific timestamp (2000-01-01 00:00:00)
192 touch -t 200001010000 $out/bin/test
193 original_timestamp=$(stat -c %Y $out/bin/test)
194 patchShebangs $out/bin/test
195 new_timestamp=$(stat -c %Y $out/bin/test)
196 if ! [ "$original_timestamp" = "$new_timestamp" ]; then
197 echo "Timestamp changed from $original_timestamp to $new_timestamp"
198 exit 1
199 fi
200 ''
201 ];
202 assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
203 })
204 // {
205 meta = { };
206 };
207
208 preserves-binary-data =
209 (derivation {
210 name = "preserves-binary-data";
211 system = stdenv.buildPlatform.system;
212 builder = "${stdenv.__bootPackages.stdenv.__bootPackages.bashNonInteractive}/bin/bash";
213 initialPath = [
214 stdenv.__bootPackages.stdenv.__bootPackages.coreutils
215 ];
216 strictDeps = false;
217 args = [
218 "-c"
219 ''
220 set -euo pipefail
221 . ${../../stdenv/generic/setup.sh}
222 . ${../../build-support/setup-hooks/patch-shebangs.sh}
223 mkdir -p $out/bin
224 # Create a script with binary data after the shebang
225 echo "#!/bin/bash" > $out/bin/test
226 echo "echo 'script start'" >> $out/bin/test
227 # Add some binary data (null bytes and other non-printable chars)
228 printf '\x00\x01\x02\xff\xfe' >> $out/bin/test
229 echo >> $out/bin/test
230 echo "echo 'script end'" >> $out/bin/test
231 chmod +x $out/bin/test
232 patchShebangs $out/bin/test
233 # Verify binary data is still present by checking file size and content
234 if ! printf '\x00\x01\x02\xff\xfe' | cmp -s - <(sed -n '3p' $out/bin/test | tr -d '\n'); then
235 echo "Binary data corrupted during patching"
236 exit 1
237 fi
238 ''
239 ];
240 assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
241 })
242 // {
243 meta = { };
244 };
245 };
246in
247stdenv.mkDerivation {
248 name = "test-patch-shebangs";
249 passthru = {
250 inherit (tests)
251 bad-shebang
252 ignores-nix-store
253 updates-nix-store
254 split-string
255 without-trailing-newline
256 dont-patch-builtins
257 read-only-script
258 preserves-read-only
259 preserves-timestamp
260 preserves-binary-data
261 ;
262 };
263 buildCommand = ''
264 validate() {
265 local name=$1
266 local testout=$2
267 local assertion=$3
268
269 echo -n "... $name: " >&2
270
271 local rc=0
272 (out=$testout eval "$assertion") || rc=1
273
274 if [ "$rc" -eq 0 ]; then
275 echo "yes" >&2
276 else
277 echo "no" >&2
278 fi
279
280 return "$rc"
281 }
282
283 echo "checking whether patchShebangs works properly... ">&2
284
285 fail=
286 ${lib.concatStringsSep "\n" (
287 lib.mapAttrsToList (_: test: ''
288 validate "${test.name}" "${test}" ${lib.escapeShellArg test.assertion} || fail=1
289 '') tests
290 )}
291
292 if [ "$fail" ]; then
293 echo "failed"
294 exit 1
295 else
296 echo "succeeded"
297 touch $out
298 fi
299 '';
300}