Something went wrong. Try again.
What happens if I push all of nixpkgs
Something went wrong. Try again.
16 kB · 480 lines
Shell
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
set -euo pipefail
# Assert that FILE exists and is executable## assertExecutable FILEassertExecutable() { local file="$1" [[ -f "$file" && -x "$file" ]] || \ die "Cannot wrap '$file' because it is not an executable file"}
# Generate a binary executable wrapper for wrapping an executable.# The binary is compiled from generated C-code using gcc.# makeWrapper EXECUTABLE OUT_PATH ARGS
# ARGS:# --argv0 NAME : set the name of the executed process to NAME# (if unset or empty, defaults to EXECUTABLE)# --inherit-argv0 : the executable inherits argv0 from the wrapper.# (use instead of --argv0 '$0')# --resolve-argv0 : if argv0 doesn't include a / character, resolve it against PATH# --set VAR VAL : add VAR with value VAL to the executable's environment# --set-default VAR VAL : like --set, but only adds VAR if not already set in# the environment# --unset VAR : remove VAR from the environment# --chdir DIR : change working directory (use instead of --run "cd DIR")# --add-flag ARG : prepend the single argument ARG to the invocation of the executable# (that is, *before* any arguments passed on the command line)# --append-flag ARG : append the single argument ARG to the invocation of the executable# (that is, *after* any arguments passed on the command line)# --add-flags ARGS : prepend the whitespace-separated list of arguments ARGS to the invocation of the executable# --append-flags ARGS : append the whitespace-separated list of arguments ARGS to the invocation of the executable
# --prefix ENV SEP VAL : suffix/prefix ENV with VAL, separated by SEP# --suffix
# To troubleshoot a binary wrapper after you compiled it,# use the `strings` command or open the binary file in a text editor.makeWrapper() { makeBinaryWrapper "$@"; }makeBinaryWrapper() { local NIX_CFLAGS_COMPILE= NIX_CFLAGS_LINK= local original="$1" local wrapper="$2" shift 2
assertExecutable "$original"
mkdir -p "$(dirname "$wrapper")"
makeDocumentedCWrapper "$original" "$@" | \ @cc@ \ -Wall -Werror -Wpedantic \ -Wno-overlength-strings \ -Os \ -x c \ -o "$wrapper" -}
# Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...>wrapProgram() { wrapProgramBinary "$@"; }wrapProgramBinary() { local prog="$1" local hidden
assertExecutable "$prog"
hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped while [ -e "$hidden" ]; do hidden="${hidden}_" done mv "$prog" "$hidden" makeBinaryWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}"}
# Generate source code for the wrapper in such a way that the wrapper inputs# will still be readable even after compilation# makeDocumentedCWrapper EXECUTABLE ARGS# ARGS: same as makeWrappermakeDocumentedCWrapper() { local src docs src=$(makeCWrapper "$@") docs=$(docstring "$@") printf '%s\n\n' "$src" printf '%s\n' "$docs"}
# makeCWrapper EXECUTABLE ARGS# ARGS: same as makeWrappermakeCWrapper() { local argv0 inherit_argv0 n params cmd main flags executable length local uses_prefix uses_suffix uses_assert uses_assert_success uses_stdio uses_asprintf local flagsBefore=() flagsAfter=() executable=$(escapeStringLiteral "$1") params=("$@") length=${#params[*]} for ((n = 1; n < length; n += 1)); do p="${params[n]}" case $p in --set) cmd=$(setEnv "${params[n + 1]}" "${params[n + 2]}") main="$main$cmd"$'\n' n=$((n + 2)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n' ;; --set-default) cmd=$(setDefaultEnv "${params[n + 1]}" "${params[n + 2]}") main="$main$cmd"$'\n' uses_stdio=1 uses_assert_success=1 n=$((n + 2)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n' ;; --unset) cmd=$(unsetEnv "${params[n + 1]}") main="$main$cmd"$'\n' uses_stdio=1 uses_assert_success=1 n=$((n + 1)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' ;; --prefix) cmd=$(setEnvPrefix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}") main="$main$cmd"$'\n' uses_prefix=1 uses_asprintf=1 uses_stdio=1 uses_assert_success=1 uses_assert=1 n=$((n + 3)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n' ;; --suffix) cmd=$(setEnvSuffix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}") main="$main$cmd"$'\n' uses_suffix=1 uses_asprintf=1 uses_stdio=1 uses_assert_success=1 uses_assert=1 n=$((n + 3)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n' ;; --chdir) cmd=$(changeDir "${params[n + 1]}") main="$main$cmd"$'\n' uses_stdio=1 uses_assert_success=1 n=$((n + 1)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' ;; --add-flag) flagsBefore+=("${params[n + 1]}") uses_assert=1 n=$((n + 1)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' ;; --append-flag) flagsAfter+=("${params[n + 1]}") uses_assert=1 n=$((n + 1)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' ;; --add-flags) read -ra flags <<< "${params[n + 1]}" flagsBefore+=("${flags[@]}") uses_assert=1 n=$((n + 1)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' ;; --append-flags) read -ra flags <<< "${params[n + 1]}" flagsAfter+=("${flags[@]}") uses_assert=1 n=$((n + 1)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' ;; --argv0) argv0=$(escapeStringLiteral "${params[n + 1]}") inherit_argv0= n=$((n + 1)) [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' ;; --inherit-argv0) # Whichever comes last of --argv0 and --inherit-argv0 wins inherit_argv0=1 ;; --resolve-argv0) # this gets processed after other argv0 flags uses_stdio=1 uses_string=1 resolve_argv0=1 ;; *) # Using an error macro, we will make sure the compiler gives an understandable error message main="$main#error makeCWrapper: Unknown argument ${p}"$'\n' ;; esac done (( ${#flagsBefore[@]} + ${#flagsAfter[@]} > 0 )) && main="$main"${main:+$'\n'}$(addFlags flagsBefore flagsAfter)$'\n'$'\n' [ -z "$inherit_argv0" ] && main="${main}argv[0] = \"${argv0:-${executable}}\";"$'\n' [ -z "$resolve_argv0" ] || main="${main}argv[0] = resolve_argv0(argv[0]);"$'\n' main="${main}return execv(\"${executable}\", argv);"$'\n'
[ -z "$uses_asprintf" ] || printf '%s\n' "#define _GNU_SOURCE /* See feature_test_macros(7) */" printf '%s\n' "#include <unistd.h>" printf '%s\n' "#include <stdlib.h>" [ -z "$uses_assert" ] || printf '%s\n' "#include <assert.h>" [ -z "$uses_stdio" ] || printf '%s\n' "#include <stdio.h>" [ -z "$uses_string" ] || printf '%s\n' "#include <string.h>" [ -z "$uses_assert_success" ] || printf '\n%s\n' "#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0)" [ -z "$uses_prefix" ] || printf '\n%s\n' "$(setEnvPrefixFn)" [ -z "$uses_suffix" ] || printf '\n%s\n' "$(setEnvSuffixFn)" [ -z "$resolve_argv0" ] || printf '\n%s\n' "$(resolveArgv0Fn)" printf '\n%s' "int main(int argc, char **argv) {" printf '\n%s' "$(indent4 "$main")" printf '\n%s\n' "}"}
addFlags() { local n flag var
local -n before=$1 local -n after=$2
var="argv_tmp" printf '%s\n' "char **$var = calloc(${#before[@]} + argc + ${#after[@]} + 1, sizeof(*$var));" printf '%s\n' "assert($var != NULL);" printf '%s\n' "${var}[0] = argv[0];" for ((n = 0; n < ${#before[@]}; n += 1)); do flag=$(escapeStringLiteral "${before[n]}") printf '%s\n' "${var}[$((n + 1))] = \"$flag\";" done printf '%s\n' "for (int i = 1; i < argc; ++i) {" printf '%s\n' " ${var}[${#before[@]} + i] = argv[i];" printf '%s\n' "}" for ((n = 0; n < ${#after[@]}; n += 1)); do flag=$(escapeStringLiteral "${after[n]}") printf '%s\n' "${var}[${#before[@]} + argc + $n] = \"$flag\";" done printf '%s\n' "${var}[${#before[@]} + argc + ${#after[@]}] = NULL;" printf '%s\n' "argv = $var;"}
# chdir DIRchangeDir() { local dir dir=$(escapeStringLiteral "$1") printf '%s' "assert_success(chdir(\"$dir\"));"}
# prefix ENV SEP VALsetEnvPrefix() { local env sep val env=$(escapeStringLiteral "$1") sep=$(escapeStringLiteral "$2") val=$(escapeStringLiteral "$3") printf '%s' "set_env_prefix(\"$env\", \"$sep\", \"$val\");" assertValidEnvName "$1"}
# suffix ENV SEP VALsetEnvSuffix() { local env sep val env=$(escapeStringLiteral "$1") sep=$(escapeStringLiteral "$2") val=$(escapeStringLiteral "$3") printf '%s' "set_env_suffix(\"$env\", \"$sep\", \"$val\");" assertValidEnvName "$1"}
# setEnv KEY VALUEsetEnv() { local key value key=$(escapeStringLiteral "$1") value=$(escapeStringLiteral "$2") printf '%s' "putenv(\"$key=$value\");" assertValidEnvName "$1"}
# setDefaultEnv KEY VALUEsetDefaultEnv() { local key value key=$(escapeStringLiteral "$1") value=$(escapeStringLiteral "$2") printf '%s' "assert_success(setenv(\"$key\", \"$value\", 0));" assertValidEnvName "$1"}
# unsetEnv KEYunsetEnv() { local key key=$(escapeStringLiteral "$1") printf '%s' "assert_success(unsetenv(\"$key\"));" assertValidEnvName "$1"}
# Makes it safe to insert STRING within quotes in a C String Literal.# escapeStringLiteral STRINGescapeStringLiteral() { local result result=${1//$'\\'/$'\\\\'} result=${result//\"/'\"'} result=${result//$'\n'/"\n"} result=${result//$'\r'/"\r"} printf '%s' "$result"}
# Indents every non-empty line by 4 spaces. To avoid trailing whitespace, we don't indent empty lines# indent4 TEXT_BLOCKindent4() { printf '%s' "$1" | awk '{ if ($0 != "") { print " "$0 } else { print $0 }}'}
assertValidEnvName() { case "$1" in *=*) printf '\n%s\n' "#error Illegal environment variable name \`$1\` (cannot contain \`=\`)";; "") printf '\n%s\n' "#error Environment variable name can't be empty.";; esac}
setEnvPrefixFn() { printf '%s' "\void set_env_prefix(char *env, char *sep, char *prefix) { char *existing = getenv(env); if (existing) { char *val; assert_success(asprintf(&val, \"%s%s%s\", prefix, sep, existing)); assert_success(setenv(env, val, 1)); free(val); } else { assert_success(setenv(env, prefix, 1)); }}"}
setEnvSuffixFn() { printf '%s' "\void set_env_suffix(char *env, char *sep, char *suffix) { char *existing = getenv(env); if (existing) { char *val; assert_success(asprintf(&val, \"%s%s%s\", existing, sep, suffix)); assert_success(setenv(env, val, 1)); free(val); } else { assert_success(setenv(env, suffix, 1)); }}"}
resolveArgv0Fn() { printf '%s' "\char *resolve_argv0(char *argv0) { if (strchr(argv0, '/') != NULL) { return argv0; } char *path = getenv(\"PATH\"); if (path == NULL) { return argv0; } char *path_copy = strdup(path); if (path_copy == NULL) { return argv0; } char *dir = strtok(path_copy, \":\"); while (dir != NULL) { char *candidate = malloc(strlen(dir) + strlen(argv0) + 2); if (candidate == NULL) { free(path_copy); return argv0; } sprintf(candidate, \"%s/%s\", dir, argv0); if (access(candidate, X_OK) == 0) { free(path_copy); return candidate; } free(candidate); dir = strtok(NULL, \":\"); } free(path_copy); return argv0;}"}
# Embed a C string which shows up as readable text in the compiled binary wrapper,# giving instructions for recreating the wrapper.# Keep in sync with makeBinaryWrapper.extractCmddocstring() { printf '%s' "const char * DOCSTRING = \"$(escapeStringLiteral "
# ------------------------------------------------------------------------------------# The C-code for this binary wrapper has been generated using the following command:
makeCWrapper $(formatArgs "$@")
# (Use \`nix-shell -p makeBinaryWrapper\` to get access to makeCWrapper in your shell)# ------------------------------------------------------------------------------------
")\";"}
# formatArgs EXECUTABLE ARGSformatArgs() { printf '%s' "${1@Q}" shift while [ $# -gt 0 ]; do case "$1" in --set) formatArgsLine 2 "$@" shift 2 ;; --set-default) formatArgsLine 2 "$@" shift 2 ;; --unset) formatArgsLine 1 "$@" shift 1 ;; --prefix) formatArgsLine 3 "$@" shift 3 ;; --suffix) formatArgsLine 3 "$@" shift 3 ;; --chdir) formatArgsLine 1 "$@" shift 1 ;; --add-flag) formatArgsLine 1 "$@" shift 1 ;; --append-flag) formatArgsLine 1 "$@" shift 1 ;; --add-flags) formatArgsLine 1 "$@" shift 1 ;; --append-flags) formatArgsLine 1 "$@" shift 1 ;; --argv0) formatArgsLine 1 "$@" shift 1 ;; --inherit-argv0) formatArgsLine 0 "$@" ;; esac shift done printf '%s\n' ""}
# formatArgsLine ARG_COUNT ARGSformatArgsLine() { local ARG_COUNT LENGTH ARG_COUNT=$1 LENGTH=$# shift printf '%s' $' \\\n '"$1" shift while [ "$ARG_COUNT" -gt $((LENGTH - $# - 2)) ]; do printf ' %s' "${1@Q}" shift done}