acr-cli: init at 0.14 (#359508)
[NixPkgs.git] / pkgs / by-name / ma / makeBinaryWrapper / make-binary-wrapper.sh
blob3948342a36fc9b9b36ff81228aaec04714eb3b5e
2 set -euo pipefail
4 # Assert that FILE exists and is executable
6 # assertExecutable FILE
7 assertExecutable() {
8 local file="$1"
9 [[ -f "$file" && -x "$file" ]] || \
10 die "Cannot wrap '$file' because it is not an executable file"
13 # Generate a binary executable wrapper for wrapping an executable.
14 # The binary is compiled from generated C-code using gcc.
15 # makeWrapper EXECUTABLE OUT_PATH ARGS
17 # ARGS:
18 # --argv0 NAME : set the name of the executed process to NAME
19 # (if unset or empty, defaults to EXECUTABLE)
20 # --inherit-argv0 : the executable inherits argv0 from the wrapper.
21 # (use instead of --argv0 '$0')
22 # --resolve-argv0 : if argv0 doesn't include a / character, resolve it against PATH
23 # --set VAR VAL : add VAR with value VAL to the executable's environment
24 # --set-default VAR VAL : like --set, but only adds VAR if not already set in
25 # the environment
26 # --unset VAR : remove VAR from the environment
27 # --chdir DIR : change working directory (use instead of --run "cd DIR")
28 # --add-flags ARGS : prepend ARGS to the invocation of the executable
29 # (that is, *before* any arguments passed on the command line)
30 # --append-flags ARGS : append ARGS to the invocation of the executable
31 # (that is, *after* any arguments passed on the command line)
33 # --prefix ENV SEP VAL : suffix/prefix ENV with VAL, separated by SEP
34 # --suffix
36 # To troubleshoot a binary wrapper after you compiled it,
37 # use the `strings` command or open the binary file in a text editor.
38 makeWrapper() { makeBinaryWrapper "$@"; }
39 makeBinaryWrapper() {
40 local NIX_CFLAGS_COMPILE= NIX_CFLAGS_LINK=
41 local original="$1"
42 local wrapper="$2"
43 shift 2
45 assertExecutable "$original"
47 mkdir -p "$(dirname "$wrapper")"
49 makeDocumentedCWrapper "$original" "$@" | \
50 @cc@ \
51 -Wall -Werror -Wpedantic \
52 -Wno-overlength-strings \
53 -Os \
54 -x c \
55 -o "$wrapper" -
58 # Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...>
59 wrapProgram() { wrapProgramBinary "$@"; }
60 wrapProgramBinary() {
61 local prog="$1"
62 local hidden
64 assertExecutable "$prog"
66 hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped
67 while [ -e "$hidden" ]; do
68 hidden="${hidden}_"
69 done
70 mv "$prog" "$hidden"
71 makeBinaryWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}"
74 # Generate source code for the wrapper in such a way that the wrapper inputs
75 # will still be readable even after compilation
76 # makeDocumentedCWrapper EXECUTABLE ARGS
77 # ARGS: same as makeWrapper
78 makeDocumentedCWrapper() {
79 local src docs
80 src=$(makeCWrapper "$@")
81 docs=$(docstring "$@")
82 printf '%s\n\n' "$src"
83 printf '%s\n' "$docs"
86 # makeCWrapper EXECUTABLE ARGS
87 # ARGS: same as makeWrapper
88 makeCWrapper() {
89 local argv0 inherit_argv0 n params cmd main flagsBefore flagsAfter flags executable length
90 local uses_prefix uses_suffix uses_assert uses_assert_success uses_stdio uses_asprintf
91 local resolve_path
92 executable=$(escapeStringLiteral "$1")
93 params=("$@")
94 length=${#params[*]}
95 for ((n = 1; n < length; n += 1)); do
96 p="${params[n]}"
97 case $p in
98 --set)
99 cmd=$(setEnv "${params[n + 1]}" "${params[n + 2]}")
100 main="$main$cmd"$'\n'
101 n=$((n + 2))
102 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n'
104 --set-default)
105 cmd=$(setDefaultEnv "${params[n + 1]}" "${params[n + 2]}")
106 main="$main$cmd"$'\n'
107 uses_stdio=1
108 uses_assert_success=1
109 n=$((n + 2))
110 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n'
112 --unset)
113 cmd=$(unsetEnv "${params[n + 1]}")
114 main="$main$cmd"$'\n'
115 uses_stdio=1
116 uses_assert_success=1
117 n=$((n + 1))
118 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
120 --prefix)
121 cmd=$(setEnvPrefix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}")
122 main="$main$cmd"$'\n'
123 uses_prefix=1
124 uses_asprintf=1
125 uses_stdio=1
126 uses_assert_success=1
127 uses_assert=1
128 n=$((n + 3))
129 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n'
131 --suffix)
132 cmd=$(setEnvSuffix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}")
133 main="$main$cmd"$'\n'
134 uses_suffix=1
135 uses_asprintf=1
136 uses_stdio=1
137 uses_assert_success=1
138 uses_assert=1
139 n=$((n + 3))
140 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n'
142 --chdir)
143 cmd=$(changeDir "${params[n + 1]}")
144 main="$main$cmd"$'\n'
145 uses_stdio=1
146 uses_assert_success=1
147 n=$((n + 1))
148 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
150 --add-flags)
151 flags="${params[n + 1]}"
152 flagsBefore="$flagsBefore $flags"
153 uses_assert=1
154 n=$((n + 1))
155 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
157 --append-flags)
158 flags="${params[n + 1]}"
159 flagsAfter="$flagsAfter $flags"
160 uses_assert=1
161 n=$((n + 1))
162 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
164 --argv0)
165 argv0=$(escapeStringLiteral "${params[n + 1]}")
166 inherit_argv0=
167 n=$((n + 1))
168 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
170 --inherit-argv0)
171 # Whichever comes last of --argv0 and --inherit-argv0 wins
172 inherit_argv0=1
174 --resolve-argv0)
175 # this gets processed after other argv0 flags
176 uses_stdio=1
177 uses_string=1
178 resolve_argv0=1
180 *) # Using an error macro, we will make sure the compiler gives an understandable error message
181 main="$main#error makeCWrapper: Unknown argument ${p}"$'\n'
183 esac
184 done
185 [[ -z "$flagsBefore" && -z "$flagsAfter" ]] || main="$main"${main:+$'\n'}$(addFlags "$flagsBefore" "$flagsAfter")$'\n'$'\n'
186 [ -z "$inherit_argv0" ] && main="${main}argv[0] = \"${argv0:-${executable}}\";"$'\n'
187 [ -z "$resolve_argv0" ] || main="${main}argv[0] = resolve_argv0(argv[0]);"$'\n'
188 main="${main}return execv(\"${executable}\", argv);"$'\n'
190 [ -z "$uses_asprintf" ] || printf '%s\n' "#define _GNU_SOURCE /* See feature_test_macros(7) */"
191 printf '%s\n' "#include <unistd.h>"
192 printf '%s\n' "#include <stdlib.h>"
193 [ -z "$uses_assert" ] || printf '%s\n' "#include <assert.h>"
194 [ -z "$uses_stdio" ] || printf '%s\n' "#include <stdio.h>"
195 [ -z "$uses_string" ] || printf '%s\n' "#include <string.h>"
196 [ -z "$uses_assert_success" ] || printf '\n%s\n' "#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0)"
197 [ -z "$uses_prefix" ] || printf '\n%s\n' "$(setEnvPrefixFn)"
198 [ -z "$uses_suffix" ] || printf '\n%s\n' "$(setEnvSuffixFn)"
199 [ -z "$resolve_argv0" ] || printf '\n%s\n' "$(resolveArgv0Fn)"
200 printf '\n%s' "int main(int argc, char **argv) {"
201 printf '\n%s' "$(indent4 "$main")"
202 printf '\n%s\n' "}"
205 addFlags() {
206 local n flag before after var
208 # Disable file globbing, since bash will otherwise try to find
209 # filenames matching the the value to be prefixed/suffixed if
210 # it contains characters considered wildcards, such as `?` and
211 # `*`. We want the value as is, except we also want to split
212 # it on on the separator; hence we can't quote it.
213 local reenableGlob=0
214 if [[ ! -o noglob ]]; then
215 reenableGlob=1
217 set -o noglob
218 # shellcheck disable=SC2086
219 before=($1) after=($2)
220 if (( reenableGlob )); then
221 set +o noglob
224 var="argv_tmp"
225 printf '%s\n' "char **$var = calloc(${#before[@]} + argc + ${#after[@]} + 1, sizeof(*$var));"
226 printf '%s\n' "assert($var != NULL);"
227 printf '%s\n' "${var}[0] = argv[0];"
228 for ((n = 0; n < ${#before[@]}; n += 1)); do
229 flag=$(escapeStringLiteral "${before[n]}")
230 printf '%s\n' "${var}[$((n + 1))] = \"$flag\";"
231 done
232 printf '%s\n' "for (int i = 1; i < argc; ++i) {"
233 printf '%s\n' " ${var}[${#before[@]} + i] = argv[i];"
234 printf '%s\n' "}"
235 for ((n = 0; n < ${#after[@]}; n += 1)); do
236 flag=$(escapeStringLiteral "${after[n]}")
237 printf '%s\n' "${var}[${#before[@]} + argc + $n] = \"$flag\";"
238 done
239 printf '%s\n' "${var}[${#before[@]} + argc + ${#after[@]}] = NULL;"
240 printf '%s\n' "argv = $var;"
243 # chdir DIR
244 changeDir() {
245 local dir
246 dir=$(escapeStringLiteral "$1")
247 printf '%s' "assert_success(chdir(\"$dir\"));"
250 # prefix ENV SEP VAL
251 setEnvPrefix() {
252 local env sep val
253 env=$(escapeStringLiteral "$1")
254 sep=$(escapeStringLiteral "$2")
255 val=$(escapeStringLiteral "$3")
256 printf '%s' "set_env_prefix(\"$env\", \"$sep\", \"$val\");"
257 assertValidEnvName "$1"
260 # suffix ENV SEP VAL
261 setEnvSuffix() {
262 local env sep val
263 env=$(escapeStringLiteral "$1")
264 sep=$(escapeStringLiteral "$2")
265 val=$(escapeStringLiteral "$3")
266 printf '%s' "set_env_suffix(\"$env\", \"$sep\", \"$val\");"
267 assertValidEnvName "$1"
270 # setEnv KEY VALUE
271 setEnv() {
272 local key value
273 key=$(escapeStringLiteral "$1")
274 value=$(escapeStringLiteral "$2")
275 printf '%s' "putenv(\"$key=$value\");"
276 assertValidEnvName "$1"
279 # setDefaultEnv KEY VALUE
280 setDefaultEnv() {
281 local key value
282 key=$(escapeStringLiteral "$1")
283 value=$(escapeStringLiteral "$2")
284 printf '%s' "assert_success(setenv(\"$key\", \"$value\", 0));"
285 assertValidEnvName "$1"
288 # unsetEnv KEY
289 unsetEnv() {
290 local key
291 key=$(escapeStringLiteral "$1")
292 printf '%s' "assert_success(unsetenv(\"$key\"));"
293 assertValidEnvName "$1"
296 # Makes it safe to insert STRING within quotes in a C String Literal.
297 # escapeStringLiteral STRING
298 escapeStringLiteral() {
299 local result
300 result=${1//$'\\'/$'\\\\'}
301 result=${result//\"/'\"'}
302 result=${result//$'\n'/"\n"}
303 result=${result//$'\r'/"\r"}
304 printf '%s' "$result"
307 # Indents every non-empty line by 4 spaces. To avoid trailing whitespace, we don't indent empty lines
308 # indent4 TEXT_BLOCK
309 indent4() {
310 printf '%s' "$1" | awk '{ if ($0 != "") { print " "$0 } else { print $0 }}'
313 assertValidEnvName() {
314 case "$1" in
315 *=*) printf '\n%s\n' "#error Illegal environment variable name \`$1\` (cannot contain \`=\`)";;
316 "") printf '\n%s\n' "#error Environment variable name can't be empty.";;
317 esac
320 setEnvPrefixFn() {
321 printf '%s' "\
322 void set_env_prefix(char *env, char *sep, char *prefix) {
323 char *existing = getenv(env);
324 if (existing) {
325 char *val;
326 assert_success(asprintf(&val, \"%s%s%s\", prefix, sep, existing));
327 assert_success(setenv(env, val, 1));
328 free(val);
329 } else {
330 assert_success(setenv(env, prefix, 1));
336 setEnvSuffixFn() {
337 printf '%s' "\
338 void set_env_suffix(char *env, char *sep, char *suffix) {
339 char *existing = getenv(env);
340 if (existing) {
341 char *val;
342 assert_success(asprintf(&val, \"%s%s%s\", existing, sep, suffix));
343 assert_success(setenv(env, val, 1));
344 free(val);
345 } else {
346 assert_success(setenv(env, suffix, 1));
352 resolveArgv0Fn() {
353 printf '%s' "\
354 char *resolve_argv0(char *argv0) {
355 if (strchr(argv0, '/') != NULL) {
356 return argv0;
358 char *path = getenv(\"PATH\");
359 if (path == NULL) {
360 return argv0;
362 char *path_copy = strdup(path);
363 if (path_copy == NULL) {
364 return argv0;
366 char *dir = strtok(path_copy, \":\");
367 while (dir != NULL) {
368 char *candidate = malloc(strlen(dir) + strlen(argv0) + 2);
369 if (candidate == NULL) {
370 free(path_copy);
371 return argv0;
373 sprintf(candidate, \"%s/%s\", dir, argv0);
374 if (access(candidate, X_OK) == 0) {
375 free(path_copy);
376 return candidate;
378 free(candidate);
379 dir = strtok(NULL, \":\");
381 free(path_copy);
382 return argv0;
387 # Embed a C string which shows up as readable text in the compiled binary wrapper,
388 # giving instructions for recreating the wrapper.
389 # Keep in sync with makeBinaryWrapper.extractCmd
390 docstring() {
391 printf '%s' "const char * DOCSTRING = \"$(escapeStringLiteral "
394 # ------------------------------------------------------------------------------------
395 # The C-code for this binary wrapper has been generated using the following command:
398 makeCWrapper $(formatArgs "$@")
401 # (Use \`nix-shell -p makeBinaryWrapper\` to get access to makeCWrapper in your shell)
402 # ------------------------------------------------------------------------------------
405 ")\";"
408 # formatArgs EXECUTABLE ARGS
409 formatArgs() {
410 printf '%s' "${1@Q}"
411 shift
412 while [ $# -gt 0 ]; do
413 case "$1" in
414 --set)
415 formatArgsLine 2 "$@"
416 shift 2
418 --set-default)
419 formatArgsLine 2 "$@"
420 shift 2
422 --unset)
423 formatArgsLine 1 "$@"
424 shift 1
426 --prefix)
427 formatArgsLine 3 "$@"
428 shift 3
430 --suffix)
431 formatArgsLine 3 "$@"
432 shift 3
434 --chdir)
435 formatArgsLine 1 "$@"
436 shift 1
438 --add-flags)
439 formatArgsLine 1 "$@"
440 shift 1
442 --append-flags)
443 formatArgsLine 1 "$@"
444 shift 1
446 --argv0)
447 formatArgsLine 1 "$@"
448 shift 1
450 --inherit-argv0)
451 formatArgsLine 0 "$@"
453 esac
454 shift
455 done
456 printf '%s\n' ""
459 # formatArgsLine ARG_COUNT ARGS
460 formatArgsLine() {
461 local ARG_COUNT LENGTH
462 ARG_COUNT=$1
463 LENGTH=$#
464 shift
465 printf '%s' $' \\\n '"$1"
466 shift
467 while [ "$ARG_COUNT" -gt $((LENGTH - $# - 2)) ]; do
468 printf ' %s' "${1@Q}"
469 shift
470 done