Merge branch 'bergkvist/make-c-wrapper' into make-c-wrapper

This commit is contained in:
Tobias Bergkvist 2021-12-09 13:00:24 +01:00 committed by GitHub
commit df13841609
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 122 additions and 43 deletions

View file

@ -796,7 +796,7 @@ A setup-hook very similar to `makeWrapper`, only it creates a tiny _compiled_ wr
Compiled wrappers generated by `makeBinaryWrapper` can be inspected with `less <path-to-wrapper>` - by scrolling past the binary data you should be able to see the C code that generated the executable and there see the environment variables that were injected into the wrapper.
Similarly to `wrapProgram`, the `makeBinaryWrapper` setup-hook provides a `wrapProgramBinary` with similar command line arguments.
Similarly to `wrapProgram`, the `makeBinaryWrapper` setup-hook provides a `binaryWrapProgram` with similar command line arguments.
### `substitute` \<infile\> \<outfile\> \<subs\> {#fun-substitute}
@ -875,7 +875,7 @@ Convenience function for `makeWrapper` that replaces `<\executable\>` with a wra
If you will apply it multiple times, it will overwrite the wrapper file and you will end up with double wrapping, which should be avoided.
### `wrapProgramBinary` \<executable\> \<makeBinaryWrapperArgs\> {#fun-wrapProgramBinary}
### `binaryWrapProgram` \<executable\> \<makeBinaryWrapperArgs\> {#fun-binaryWrapProgram}
Convenience function for `makeBinaryWrapper` that replaces `<\executable\>` with a wrapper that executes the original program. It takes all the same arguments as `makeBinaryWrapper`, except for `--argv0`.

View file

@ -51,14 +51,14 @@ wrapProgram() {
makeWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}"
}
# Generate source code for the wrapper in such a way that the wrapper source code
# 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 makeWrapper
makeDocumentedCWrapper() {
local src docs
src=$(makeCWrapper "$@")
docs=$(documentationString "$src")
docs=$(docstring "$@")
printf '%s\n\n' "$src"
printf '%s\n' "$docs"
}
@ -66,7 +66,7 @@ makeDocumentedCWrapper() {
# makeCWrapper EXECUTABLE ARGS
# ARGS: same as makeWrapper
makeCWrapper() {
local argv0 inherit_argv0 n params cmd main flagsBefore flags executable params length
local argv0 inherit_argv0 n params cmd main flagsBefore flags executable length
local uses_prefix uses_suffix uses_assert uses_assert_success uses_stdio uses_asprintf
executable=$(escapeStringLiteral "$1")
params=("$@")
@ -76,82 +76,82 @@ makeCWrapper() {
case $p in
--set)
cmd=$(setEnv "${params[n + 1]}" "${params[n + 2]}")
main="$main $cmd"$'\n'
main="$main$cmd"$'\n'
n=$((n + 2))
[ $n -ge "$length" ] && main="$main #error makeCWrapper: $p takes 2 arguments"$'\n'
[ $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'
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'
[ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n'
;;
--unset)
cmd=$(unsetEnv "${params[n + 1]}")
main="$main $cmd"$'\n'
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'
[ $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'
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'
[ $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'
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'
[ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n'
;;
--chdir)
cmd=$(changeDir "${params[n + 1]}")
main="$main $cmd"$'\n'
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'
[ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
;;
--add-flags)
flags="${params[n + 1]}"
flagsBefore="$flagsBefore $flags"
uses_assert=1
n=$((n + 1))
[ $n -ge "$length" ] && main="$main #error makeCWrapper: $p takes 1 argument"$'\n'
[ $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'
[ $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
;;
*) # Using an error macro, we will make sure the compiler gives an understandable error message
main="$main #error makeCWrapper: Unknown argument ${p}"$'\n'
main="$main#error makeCWrapper: Unknown argument ${p}"$'\n'
;;
esac
done
# shellcheck disable=SC2086
[ -z "$flagsBefore" ] || main="$main"${main:+$'\n'}$(addFlags $flagsBefore)$'\n'$'\n'
[ -z "$inherit_argv0" ] && main="$main argv[0] = \"${argv0:-${executable}}\";"$'\n'
main="$main return execv(\"${executable}\", argv);"$'\n'
[ -z "$inherit_argv0" ] && main="${main}argv[0] = \"${argv0:-${executable}}\";"$'\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>"
@ -162,8 +162,8 @@ makeCWrapper() {
[ -z "$uses_prefix" ] || printf '\n%s\n' "$(setEnvPrefixFn)"
[ -z "$uses_suffix" ] || printf '\n%s\n' "$(setEnvSuffixFn)"
printf '\n%s' "int main(int argc, char **argv) {"
printf '\n%s' "$main"
printf '%s\n' "}"
printf '\n%s' "$(indent4 "$main")"
printf '\n%s\n' "}"
}
addFlags() {
@ -172,17 +172,17 @@ addFlags() {
flags=("$@")
for ((n = 0; n < ${#flags[*]}; n += 1)); do
flag=$(escapeStringLiteral "${flags[$n]}")
result="$result ${var}[$((n+1))] = \"$flag\";"$'\n'
result="$result${var}[$((n+1))] = \"$flag\";"$'\n'
done
printf ' %s\n' "char **$var = calloc($((n+1)) + argc, sizeof(*$var));"
printf ' %s\n' "assert($var != NULL);"
printf ' %s\n' "${var}[0] = argv[0];"
printf '%s\n' "char **$var = calloc($((n+1)) + argc, sizeof(*$var));"
printf '%s\n' "assert($var != NULL);"
printf '%s\n' "${var}[0] = argv[0];"
printf '%s' "$result"
printf ' %s\n' "for (int i = 1; i < argc; ++i) {"
printf ' %s\n' " ${var}[$n + i] = argv[i];"
printf ' %s\n' "}"
printf ' %s\n' "${var}[$n + argc] = NULL;"
printf ' %s\n' "argv = $var;"
printf '%s\n' "for (int i = 1; i < argc; ++i) {"
printf '%s\n' " ${var}[$n + i] = argv[i];"
printf '%s\n' "}"
printf '%s\n' "${var}[$n + argc] = NULL;"
printf '%s\n' "argv = $var;"
}
# chdir DIR
@ -238,14 +238,6 @@ unsetEnv() {
assertValidEnvName "$1"
}
# Put the entire source code into const char* SOURCE_CODE to make it readable after compilation.
# documentationString SOURCE_CODE
documentationString() {
local docs
docs=$(escapeStringLiteral $'\n----------\n// This binary wrapper was compiled from the following generated C-code:\n'"$1"$'\n----------\n')
printf '%s' "const char * SOURCE_CODE = \"$docs\";"
}
# Makes it safe to insert STRING within quotes in a C String Literal.
# escapeStringLiteral STRING
escapeStringLiteral() {
@ -257,10 +249,16 @@ escapeStringLiteral() {
printf '%s' "$result"
}
# Indents every non-empty line by 4 spaces. To avoid trailing whitespace, we don't indent empty lines
# indent4 TEXT_BLOCK
indent4() {
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.";;
*=*) 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
}
@ -295,3 +293,84 @@ void set_env_suffix(char *env, char *sep, char *suffix) {
}
"
}
# Embed a C string which shows up as readable text in the compiled binary wrapper
# documentationString ARGS
docstring() {
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 ARGS
formatArgs() {
printf '%s' "$1"
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-flags)
formatArgsLine 1 "$@"
shift 1
;;
--argv0)
formatArgsLine 1 "$@"
shift 1
;;
--inherit-argv0)
formatArgsLine 0 "$@"
;;
esac
shift
done
printf '%s\n' ""
}
# formatArgsLine ARG_COUNT ARGS
formatArgsLine() {
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
}