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. 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} ### `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. 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`. 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}" 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 # will still be readable even after compilation
# makeDocumentedCWrapper EXECUTABLE ARGS # makeDocumentedCWrapper EXECUTABLE ARGS
# ARGS: same as makeWrapper # ARGS: same as makeWrapper
makeDocumentedCWrapper() { makeDocumentedCWrapper() {
local src docs local src docs
src=$(makeCWrapper "$@") src=$(makeCWrapper "$@")
docs=$(documentationString "$src") docs=$(docstring "$@")
printf '%s\n\n' "$src" printf '%s\n\n' "$src"
printf '%s\n' "$docs" printf '%s\n' "$docs"
} }
@ -66,7 +66,7 @@ makeDocumentedCWrapper() {
# makeCWrapper EXECUTABLE ARGS # makeCWrapper EXECUTABLE ARGS
# ARGS: same as makeWrapper # ARGS: same as makeWrapper
makeCWrapper() { 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 local uses_prefix uses_suffix uses_assert uses_assert_success uses_stdio uses_asprintf
executable=$(escapeStringLiteral "$1") executable=$(escapeStringLiteral "$1")
params=("$@") params=("$@")
@ -76,82 +76,82 @@ makeCWrapper() {
case $p in case $p in
--set) --set)
cmd=$(setEnv "${params[n + 1]}" "${params[n + 2]}") cmd=$(setEnv "${params[n + 1]}" "${params[n + 2]}")
main="$main $cmd"$'\n' main="$main$cmd"$'\n'
n=$((n + 2)) 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) --set-default)
cmd=$(setDefaultEnv "${params[n + 1]}" "${params[n + 2]}") cmd=$(setDefaultEnv "${params[n + 1]}" "${params[n + 2]}")
main="$main $cmd"$'\n' main="$main$cmd"$'\n'
uses_stdio=1 uses_stdio=1
uses_assert_success=1 uses_assert_success=1
n=$((n + 2)) 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) --unset)
cmd=$(unsetEnv "${params[n + 1]}") cmd=$(unsetEnv "${params[n + 1]}")
main="$main $cmd"$'\n' main="$main$cmd"$'\n'
uses_stdio=1 uses_stdio=1
uses_assert_success=1 uses_assert_success=1
n=$((n + 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) --prefix)
cmd=$(setEnvPrefix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}") cmd=$(setEnvPrefix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}")
main="$main $cmd"$'\n' main="$main$cmd"$'\n'
uses_prefix=1 uses_prefix=1
uses_asprintf=1 uses_asprintf=1
uses_stdio=1 uses_stdio=1
uses_assert_success=1 uses_assert_success=1
uses_assert=1 uses_assert=1
n=$((n + 3)) 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) --suffix)
cmd=$(setEnvSuffix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}") cmd=$(setEnvSuffix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}")
main="$main $cmd"$'\n' main="$main$cmd"$'\n'
uses_suffix=1 uses_suffix=1
uses_asprintf=1 uses_asprintf=1
uses_stdio=1 uses_stdio=1
uses_assert_success=1 uses_assert_success=1
uses_assert=1 uses_assert=1
n=$((n + 3)) 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) --chdir)
cmd=$(changeDir "${params[n + 1]}") cmd=$(changeDir "${params[n + 1]}")
main="$main $cmd"$'\n' main="$main$cmd"$'\n'
uses_stdio=1 uses_stdio=1
uses_assert_success=1 uses_assert_success=1
n=$((n + 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) --add-flags)
flags="${params[n + 1]}" flags="${params[n + 1]}"
flagsBefore="$flagsBefore $flags" flagsBefore="$flagsBefore $flags"
uses_assert=1 uses_assert=1
n=$((n + 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)
argv0=$(escapeStringLiteral "${params[n + 1]}") argv0=$(escapeStringLiteral "${params[n + 1]}")
inherit_argv0= inherit_argv0=
n=$((n + 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'
;; ;;
--inherit-argv0) --inherit-argv0)
# Whichever comes last of --argv0 and --inherit-argv0 wins # Whichever comes last of --argv0 and --inherit-argv0 wins
inherit_argv0=1 inherit_argv0=1
;; ;;
*) # Using an error macro, we will make sure the compiler gives an understandable error message *) # 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 esac
done done
# shellcheck disable=SC2086 # shellcheck disable=SC2086
[ -z "$flagsBefore" ] || main="$main"${main:+$'\n'}$(addFlags $flagsBefore)$'\n'$'\n' [ -z "$flagsBefore" ] || main="$main"${main:+$'\n'}$(addFlags $flagsBefore)$'\n'$'\n'
[ -z "$inherit_argv0" ] && main="$main argv[0] = \"${argv0:-${executable}}\";"$'\n' [ -z "$inherit_argv0" ] && main="${main}argv[0] = \"${argv0:-${executable}}\";"$'\n'
main="$main return execv(\"${executable}\", argv);"$'\n' main="${main}return execv(\"${executable}\", argv);"$'\n'
[ -z "$uses_asprintf" ] || printf '%s\n' "#define _GNU_SOURCE /* See feature_test_macros(7) */" [ -z "$uses_asprintf" ] || printf '%s\n' "#define _GNU_SOURCE /* See feature_test_macros(7) */"
printf '%s\n' "#include <unistd.h>" printf '%s\n' "#include <unistd.h>"
@ -162,8 +162,8 @@ makeCWrapper() {
[ -z "$uses_prefix" ] || printf '\n%s\n' "$(setEnvPrefixFn)" [ -z "$uses_prefix" ] || printf '\n%s\n' "$(setEnvPrefixFn)"
[ -z "$uses_suffix" ] || printf '\n%s\n' "$(setEnvSuffixFn)" [ -z "$uses_suffix" ] || printf '\n%s\n' "$(setEnvSuffixFn)"
printf '\n%s' "int main(int argc, char **argv) {" printf '\n%s' "int main(int argc, char **argv) {"
printf '\n%s' "$main" printf '\n%s' "$(indent4 "$main")"
printf '%s\n' "}" printf '\n%s\n' "}"
} }
addFlags() { addFlags() {
@ -172,17 +172,17 @@ addFlags() {
flags=("$@") flags=("$@")
for ((n = 0; n < ${#flags[*]}; n += 1)); do for ((n = 0; n < ${#flags[*]}; n += 1)); do
flag=$(escapeStringLiteral "${flags[$n]}") flag=$(escapeStringLiteral "${flags[$n]}")
result="$result ${var}[$((n+1))] = \"$flag\";"$'\n' result="$result${var}[$((n+1))] = \"$flag\";"$'\n'
done done
printf ' %s\n' "char **$var = calloc($((n+1)) + argc, sizeof(*$var));" printf '%s\n' "char **$var = calloc($((n+1)) + argc, sizeof(*$var));"
printf ' %s\n' "assert($var != NULL);" printf '%s\n' "assert($var != NULL);"
printf ' %s\n' "${var}[0] = argv[0];" printf '%s\n' "${var}[0] = argv[0];"
printf '%s' "$result" printf '%s' "$result"
printf ' %s\n' "for (int i = 1; i < argc; ++i) {" printf '%s\n' "for (int i = 1; i < argc; ++i) {"
printf ' %s\n' " ${var}[$n + i] = argv[i];" printf '%s\n' " ${var}[$n + i] = argv[i];"
printf ' %s\n' "}" printf '%s\n' "}"
printf ' %s\n' "${var}[$n + argc] = NULL;" printf '%s\n' "${var}[$n + argc] = NULL;"
printf ' %s\n' "argv = $var;" printf '%s\n' "argv = $var;"
} }
# chdir DIR # chdir DIR
@ -238,14 +238,6 @@ unsetEnv() {
assertValidEnvName "$1" 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. # Makes it safe to insert STRING within quotes in a C String Literal.
# escapeStringLiteral STRING # escapeStringLiteral STRING
escapeStringLiteral() { escapeStringLiteral() {
@ -257,10 +249,16 @@ escapeStringLiteral() {
printf '%s' "$result" 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() { assertValidEnvName() {
case "$1" in case "$1" in
*=*) printf '\n%s\n' " #error Illegal environment variable name \`$1\` (cannot contain \`=\`)";; *=*) 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 Environment variable name can't be empty.";;
esac 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
}