summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex O'Brien <3541@3541.website>2024-02-17 20:12:41 +1100
committerDomen Kožar <domen@dev.si>2024-03-01 08:09:21 +0000
commitb445bcc431f4e02f3d160916654825c6d79c1dad (patch)
tree7d6ed346eb97c1cc31de4aecdfec9eda11826c84
parentFix venv creation in Python environments (diff)
downloadnixpkgs-b445bcc431f4e02f3d160916654825c6d79c1dad.tar.gz
Make venv change work on Darwin
This patch builds on the previous to make prefix resolution work correctly inside virtual environments on Darwin. The --inherit-argv0 mechanism is insufficient, as on Darwin, Python uses the _NSGetExecutablePath() call to work out the actual path to its binary. Since this resolves to the real unwrapped binary, --inherit-argv0 is little use. This patch adds a new variant, --inherit-argv0-path, which resolves the full path before passing it to argv[0]. On Darwin this is done using _NSGetExecutablePath(), on Linux by reading /proc/self/exe. This bypasses the real_executable lookup, as Python gives precedence to argv[0] when it appears to be a path (contains a slash). While not strictly necessary on Linux (as --inherit-argv0 works anyway), the change is made to both for consistency.
-rw-r--r--pkgs/build-support/setup-hooks/make-binary-wrapper/make-binary-wrapper.sh73
-rw-r--r--pkgs/build-support/setup-hooks/make-wrapper.sh4
-rw-r--r--pkgs/development/interpreters/python/wrapper.nix2
3 files changed, 77 insertions, 2 deletions
diff --git a/pkgs/build-support/setup-hooks/make-binary-wrapper/make-binary-wrapper.sh b/pkgs/build-support/setup-hooks/make-binary-wrapper/make-binary-wrapper.sh
index 6cd01f6bf630..cb33fb9cfd84 100644
--- a/pkgs/build-support/setup-hooks/make-binary-wrapper/make-binary-wrapper.sh
+++ b/pkgs/build-support/setup-hooks/make-binary-wrapper/make-binary-wrapper.sh
@@ -19,6 +19,8 @@ assertExecutable() {
# (if unset or empty, defaults to EXECUTABLE)
# --inherit-argv0 : the executable inherits argv0 from the wrapper.
# (use instead of --argv0 '$0')
+# --inherit-argv0-path : set the name of the executed process to the full path
+# to the wrapper.
# --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
@@ -87,6 +89,7 @@ makeDocumentedCWrapper() {
makeCWrapper() {
local argv0 inherit_argv0 n params cmd main flagsBefore flagsAfter flags executable length
local uses_prefix uses_suffix uses_assert uses_assert_success uses_stdio uses_asprintf
+ local inherit_argv0_path
executable=$(escapeStringLiteral "$1")
params=("$@")
length=${#params[*]}
@@ -169,6 +172,11 @@ makeCWrapper() {
# Whichever comes last of --argv0 and --inherit-argv0 wins
inherit_argv0=1
;;
+ --inherit-argv0-path)
+ inherit_argv0=1
+ inherit_argv0_path=1
+ uses_stdio=1
+ ;;
*) # Using an error macro, we will make sure the compiler gives an understandable error message
main="$main#error makeCWrapper: Unknown argument ${p}"$'\n'
;;
@@ -176,16 +184,19 @@ makeCWrapper() {
done
[[ -z "$flagsBefore" && -z "$flagsAfter" ]] || main="$main"${main:+$'\n'}$(addFlags "$flagsBefore" "$flagsAfter")$'\n'$'\n'
[ -z "$inherit_argv0" ] && main="${main}argv[0] = \"${argv0:-${executable}}\";"$'\n'
+ [[ -n "$inherit_argv0" && -n "$inherit_argv0_path" ]] && main="${main}argv[0] = resolve_path(argv[0]);"$'\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" && -z "$inherit_argv0_path" ]] || 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 "$inherit_argv0_path" ] || printf '%s\n' "$(resolvePathInclude)"
[ -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 "$inherit_argv0_path" ] || printf '\n%s\n' "$(resolvePathFn)"
printf '\n%s' "int main(int argc, char **argv) {"
printf '\n%s' "$(indent4 "$main")"
printf '\n%s\n' "}"
@@ -338,6 +349,66 @@ void set_env_suffix(char *env, char *sep, char *suffix) {
"
}
+resolvePathInclude() {
+ printf '%s' "\
+#ifdef __APPLE__
+#include <mach-o/dyld.h>
+#else
+#include <fcntl.h>
+#include <sys/stat.h>
+#endif
+"
+}
+
+resolvePathFn() {
+ printf '%s' "\
+char* resolve_path(char* b) {
+ if (b[0] == '/' || access(b, R_OK | X_OK) == 0)
+ return b;
+
+#ifdef __APPLE__
+ unsigned len = 0;
+ _NSGetExecutablePath(NULL, &len);
+ char* buf = calloc(1, len);
+ if (!buf)
+ return b;
+
+ return _NSGetExecutablePath(buf, &len) == 0 ? buf : b;
+#else
+ int fd = open(\"/proc/self/exe\", O_PATH | O_NOFOLLOW);
+ if (fd < 0) {
+ perror(\"open(/proc/self/exe)\");
+ return b;
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ perror(\"fstat(/proc/self/exe)\");
+ close(fd);
+ return b;
+ }
+
+ char* buf = calloc(1, st.st_size + 1);
+ if (!buf) {
+ close(fd);
+ return b;
+ }
+
+ ssize_t res = readlinkat(fd, \"\", buf, st.st_size + 1);
+ if (res != st.st_size) {
+ if (res < 0)
+ perror(\"readlinkat(/proc/self/exe)\");
+ free(buf);
+ close(fd);
+ return b;
+ }
+
+ return buf;
+#endif
+}
+"
+}
+
# 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.extractCmd
diff --git a/pkgs/build-support/setup-hooks/make-wrapper.sh b/pkgs/build-support/setup-hooks/make-wrapper.sh
index 11b332bfc3eb..a44fb6d70547 100644
--- a/pkgs/build-support/setup-hooks/make-wrapper.sh
+++ b/pkgs/build-support/setup-hooks/make-wrapper.sh
@@ -15,6 +15,8 @@ assertExecutable() {
# (if unset or empty, defaults to EXECUTABLE)
# --inherit-argv0 : the executable inherits argv0 from the wrapper.
# (use instead of --argv0 '$0')
+# --inherit-argv0-path : set the name of the executed process to the full path
+# to the wrapper.
# --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
@@ -177,6 +179,8 @@ makeShellWrapper() {
elif [[ "$p" == "--inherit-argv0" ]]; then
# Whichever comes last of --argv0 and --inherit-argv0 wins
argv0='$0'
+ elif [[ "$p" == "--inherit-argv0-path" ]]; then
+ argv0='$(which "$0")'
else
die "makeWrapper doesn't understand the arg $p"
fi
diff --git a/pkgs/development/interpreters/python/wrapper.nix b/pkgs/development/interpreters/python/wrapper.nix
index 7c419d320f50..225a2f38b278 100644
--- a/pkgs/development/interpreters/python/wrapper.nix
+++ b/pkgs/development/interpreters/python/wrapper.nix
@@ -49,7 +49,7 @@ let
sed -e '1d' -e '3d' ".$prg-wrapped" >> "$out/bin/$prg"
chmod +x "$out/bin/$prg"
else
- makeWrapper "$path/bin/$prg" "$out/bin/$prg" --argv0 "$out/bin/$prg" ${lib.optionalString (!permitUserSite) ''--set PYTHONNOUSERSITE "true"''} ${lib.concatStringsSep " " makeWrapperArgs}
+ makeWrapper "$path/bin/$prg" "$out/bin/$prg" --inherit-argv0-path ${lib.optionalString (!permitUserSite) ''--set PYTHONNOUSERSITE "true"''} ${lib.concatStringsSep " " makeWrapperArgs}
fi
fi
fi