about summary refs log tree commit diff
path: root/pkgs/build-support/setup-hooks
diff options
context:
space:
mode:
authorColin Putney <colin.putney@vannevarlabs.com>2024-03-21 19:22:03 -0600
committerColin Putney <colin.putney@vannevarlabs.com>2024-03-21 19:26:57 -0600
commit234bb31f611f43f8b744b305ab82035de637aaca (patch)
treeb42bfbe7bbd4e1f1d605d729415890e8e0d86ce8 /pkgs/build-support/setup-hooks
parentb727e4cb210367459e4299f8521d9c781fe70fc5 (diff)
Fix venv creation in Python environments
The way we build python environments is subtly broken. A python
environment should be semantically identical to a vanilla Python
installation in, say, /usr/local. The current implementation, however,
differs in two important ways. The first is that it's impossible to use
python packages from the environment in python virtual environments. The
second is that the nix-generated environment appears to be a venv, but
it's not.

This commit changes the way python environments are built:

  * When generating wrappers for python executables, we inherit argv[0]
    from the wrapper. This causes python to initialize its configuration
    in the environment with all the correct paths.
  * We remove the sitecustomize.py file from the base python package.
    This file was used tweak the python configuration after it was
    incorrectly initialized. That's no longer necessary.

The end result is that python environments no longer appear to be venvs,
and behave more like a vanilla python installation. In addition it's
possible to create a venv using an environment and use packages from
both the environment and the venv.
Diffstat (limited to 'pkgs/build-support/setup-hooks')
-rw-r--r--pkgs/build-support/setup-hooks/make-binary-wrapper/make-binary-wrapper.sh46
-rw-r--r--pkgs/build-support/setup-hooks/make-wrapper.sh4
2 files changed, 50 insertions, 0 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 6cd01f6bf6307..3948342a36fc9 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,7 @@ assertExecutable() {
 #                          (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
@@ -87,6 +88,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 resolve_path
     executable=$(escapeStringLiteral "$1")
     params=("$@")
     length=${#params[*]}
@@ -169,6 +171,12 @@ makeCWrapper() {
                 # 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'
             ;;
@@ -176,6 +184,7 @@ 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'
+    [ -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) */"
@@ -183,9 +192,11 @@ makeCWrapper() {
     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' "}"
@@ -338,6 +349,41 @@ void set_env_suffix(char *env, char *sep, char *suffix) {
 "
 }
 
+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.extractCmd
diff --git a/pkgs/build-support/setup-hooks/make-wrapper.sh b/pkgs/build-support/setup-hooks/make-wrapper.sh
index 11b332bfc3eb2..cba158bd31ea9 100644
--- a/pkgs/build-support/setup-hooks/make-wrapper.sh
+++ b/pkgs/build-support/setup-hooks/make-wrapper.sh
@@ -15,6 +15,7 @@ assertExecutable() {
 #                          (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
@@ -177,6 +178,9 @@ makeShellWrapper() {
         elif [[ "$p" == "--inherit-argv0" ]]; then
             # Whichever comes last of --argv0 and --inherit-argv0 wins
             argv0='$0'
+        elif [[ "$p" == "--resolve-argv0" ]]; then
+            # this is noop in shell wrappers, since bash will always resolve $0
+            resolve_argv0=1
         else
             die "makeWrapper doesn't understand the arg $p"
         fi