···1919# (if unset or empty, defaults to EXECUTABLE)
2020# --inherit-argv0 : the executable inherits argv0 from the wrapper.
2121# (use instead of --argv0 '$0')
2222+# --resolve-argv0 : if argv0 doesn't include a / character, resolve it against PATH
2223# --set VAR VAL : add VAR with value VAL to the executable's environment
2324# --set-default VAR VAL : like --set, but only adds VAR if not already set in
2425# the environment
···8788makeCWrapper() {
8889 local argv0 inherit_argv0 n params cmd main flagsBefore flagsAfter flags executable length
8990 local uses_prefix uses_suffix uses_assert uses_assert_success uses_stdio uses_asprintf
9191+ local resolve_path
9092 executable=$(escapeStringLiteral "$1")
9193 params=("$@")
9294 length=${#params[*]}
···169171 # Whichever comes last of --argv0 and --inherit-argv0 wins
170172 inherit_argv0=1
171173 ;;
174174+ --resolve-argv0)
175175+ # this gets processed after other argv0 flags
176176+ uses_stdio=1
177177+ uses_string=1
178178+ resolve_argv0=1
179179+ ;;
172180 *) # Using an error macro, we will make sure the compiler gives an understandable error message
173181 main="$main#error makeCWrapper: Unknown argument ${p}"$'\n'
174182 ;;
···176184 done
177185 [[ -z "$flagsBefore" && -z "$flagsAfter" ]] || main="$main"${main:+$'\n'}$(addFlags "$flagsBefore" "$flagsAfter")$'\n'$'\n'
178186 [ -z "$inherit_argv0" ] && main="${main}argv[0] = \"${argv0:-${executable}}\";"$'\n'
187187+ [ -z "$resolve_argv0" ] || main="${main}argv[0] = resolve_argv0(argv[0]);"$'\n'
179188 main="${main}return execv(\"${executable}\", argv);"$'\n'
180189181190 [ -z "$uses_asprintf" ] || printf '%s\n' "#define _GNU_SOURCE /* See feature_test_macros(7) */"
···183192 printf '%s\n' "#include <stdlib.h>"
184193 [ -z "$uses_assert" ] || printf '%s\n' "#include <assert.h>"
185194 [ -z "$uses_stdio" ] || printf '%s\n' "#include <stdio.h>"
195195+ [ -z "$uses_string" ] || printf '%s\n' "#include <string.h>"
186196 [ -z "$uses_assert_success" ] || printf '\n%s\n' "#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0)"
187197 [ -z "$uses_prefix" ] || printf '\n%s\n' "$(setEnvPrefixFn)"
188198 [ -z "$uses_suffix" ] || printf '\n%s\n' "$(setEnvSuffixFn)"
199199+ [ -z "$resolve_argv0" ] || printf '\n%s\n' "$(resolveArgv0Fn)"
189200 printf '\n%s' "int main(int argc, char **argv) {"
190201 printf '\n%s' "$(indent4 "$main")"
191202 printf '\n%s\n' "}"
···334345 } else {
335346 assert_success(setenv(env, suffix, 1));
336347 }
348348+}
349349+"
350350+}
351351+352352+resolveArgv0Fn() {
353353+ printf '%s' "\
354354+char *resolve_argv0(char *argv0) {
355355+ if (strchr(argv0, '/') != NULL) {
356356+ return argv0;
357357+ }
358358+ char *path = getenv(\"PATH\");
359359+ if (path == NULL) {
360360+ return argv0;
361361+ }
362362+ char *path_copy = strdup(path);
363363+ if (path_copy == NULL) {
364364+ return argv0;
365365+ }
366366+ char *dir = strtok(path_copy, \":\");
367367+ while (dir != NULL) {
368368+ char *candidate = malloc(strlen(dir) + strlen(argv0) + 2);
369369+ if (candidate == NULL) {
370370+ free(path_copy);
371371+ return argv0;
372372+ }
373373+ sprintf(candidate, \"%s/%s\", dir, argv0);
374374+ if (access(candidate, X_OK) == 0) {
375375+ free(path_copy);
376376+ return candidate;
377377+ }
378378+ free(candidate);
379379+ dir = strtok(NULL, \":\");
380380+ }
381381+ free(path_copy);
382382+ return argv0;
337383}
338384"
339385}
+4
pkgs/build-support/setup-hooks/make-wrapper.sh
···1515# (if unset or empty, defaults to EXECUTABLE)
1616# --inherit-argv0 : the executable inherits argv0 from the wrapper.
1717# (use instead of --argv0 '$0')
1818+# --resolve-argv0 : if argv0 doesn't include a / character, resolve it against PATH
1819# --set VAR VAL : add VAR with value VAL to the executable's environment
1920# --set-default VAR VAL : like --set, but only adds VAR if not already set in
2021# the environment
···177178 elif [[ "$p" == "--inherit-argv0" ]]; then
178179 # Whichever comes last of --argv0 and --inherit-argv0 wins
179180 argv0='$0'
181181+ elif [[ "$p" == "--resolve-argv0" ]]; then
182182+ # this is noop in shell wrappers, since bash will always resolve $0
183183+ resolve_argv0=1
180184 else
181185 die "makeWrapper doesn't understand the arg $p"
182186 fi
···11-"""
22-This is a Nix-specific module for discovering modules built with Nix.
33-44-The module recursively adds paths that are on `NIX_PYTHONPATH` to `sys.path`. In
55-order to process possible `.pth` files `site.addsitedir` is used.
66-77-The paths listed in `PYTHONPATH` are added to `sys.path` afterwards, but they
88-will be added before the entries we add here and thus take precedence.
99-1010-Note the `NIX_PYTHONPATH` environment variable is unset in order to prevent leakage.
1111-1212-Similarly, this module listens to the environment variable `NIX_PYTHONEXECUTABLE`
1313-and sets `sys.executable` to its value.
1414-"""
1515-import site
1616-import sys
1717-import os
1818-import functools
1919-2020-paths = os.environ.pop('NIX_PYTHONPATH', None)
2121-if paths:
2222- functools.reduce(lambda k, p: site.addsitedir(p, k), paths.split(':'), site._init_pathinfo())
2323-2424-# Check whether we are in a venv or virtualenv.
2525-# For Python 3 we check whether our `base_prefix` is different from our current `prefix`.
2626-# For Python 2 we check whether the non-standard `real_prefix` is set.
2727-# https://stackoverflow.com/questions/1871549/determine-if-python-is-running-inside-virtualenv
2828-in_venv = (sys.version_info.major == 3 and sys.prefix != sys.base_prefix) or (sys.version_info.major == 2 and hasattr(sys, "real_prefix"))
2929-3030-if not in_venv:
3131- executable = os.environ.pop('NIX_PYTHONEXECUTABLE', None)
3232- prefix = os.environ.pop('NIX_PYTHONPREFIX', None)
3333-3434- if 'PYTHONEXECUTABLE' not in os.environ and executable is not None:
3535- sys.executable = executable
3636- if prefix is not None:
3737- # Sysconfig does not like it when sys.prefix is set to None
3838- sys.prefix = sys.exec_prefix = prefix
3939- site.PREFIXES.insert(0, prefix)
+71-18
pkgs/development/interpreters/python/tests.nix
···3939 is_virtualenv = "False";
4040 };
4141 } // lib.optionalAttrs (!python.isPyPy) {
4242- # Use virtualenv from a Nix env.
4343- nixenv-virtualenv = rec {
4444- env = runCommand "${python.name}-virtualenv" {} ''
4545- ${pythonVirtualEnv.interpreter} -m virtualenv venv
4646- mv venv $out
4242+ # Use virtualenv with symlinks from a Nix env.
4343+ nixenv-virtualenv-links = rec {
4444+ env = runCommand "${python.name}-virtualenv-links" {} ''
4545+ ${pythonVirtualEnv.interpreter} -m virtualenv --system-site-packages --symlinks --no-seed $out
4646+ '';
4747+ interpreter = "${env}/bin/${python.executable}";
4848+ is_venv = "False";
4949+ is_nixenv = "True";
5050+ is_virtualenv = "True";
5151+ };
5252+ } // lib.optionalAttrs (!python.isPyPy) {
5353+ # Use virtualenv with copies from a Nix env.
5454+ nixenv-virtualenv-copies = rec {
5555+ env = runCommand "${python.name}-virtualenv-copies" {} ''
5656+ ${pythonVirtualEnv.interpreter} -m virtualenv --system-site-packages --copies --no-seed $out
4757 '';
4858 interpreter = "${env}/bin/${python.executable}";
4959 is_venv = "False";
···5969 is_nixenv = "True";
6070 is_virtualenv = "False";
6171 };
6262- } // lib.optionalAttrs (python.isPy3k && (!python.isPyPy)) {
6363- # Venv built using plain Python
7272+ } // lib.optionalAttrs (python.pythonAtLeast "3.8" && (!python.isPyPy)) {
7373+ # Venv built using links to plain Python
7474+ # Python 2 does not support venv
7575+ # TODO: PyPy executable name is incorrect, it should be pypy-c or pypy-3c instead of pypy and pypy3.
7676+ plain-venv-links = rec {
7777+ env = runCommand "${python.name}-venv-links" {} ''
7878+ ${python.interpreter} -m venv --system-site-packages --symlinks --without-pip $out
7979+ '';
8080+ interpreter = "${env}/bin/${python.executable}";
8181+ is_venv = "True";
8282+ is_nixenv = "False";
8383+ is_virtualenv = "False";
8484+ };
8585+ } // lib.optionalAttrs (python.pythonAtLeast "3.8" && (!python.isPyPy)) {
8686+ # Venv built using copies from plain Python
6487 # Python 2 does not support venv
6588 # TODO: PyPy executable name is incorrect, it should be pypy-c or pypy-3c instead of pypy and pypy3.
6666- plain-venv = rec {
6767- env = runCommand "${python.name}-venv" {} ''
6868- ${python.interpreter} -m venv $out
8989+ plain-venv-copies = rec {
9090+ env = runCommand "${python.name}-venv-copies" {} ''
9191+ ${python.interpreter} -m venv --system-site-packages --copies --without-pip $out
6992 '';
7093 interpreter = "${env}/bin/${python.executable}";
7194 is_venv = "True";
7295 is_nixenv = "False";
7396 is_virtualenv = "False";
7497 };
7575-9898+ } // lib.optionalAttrs (python.pythonAtLeast "3.8") {
9999+ # Venv built using Python Nix environment (python.buildEnv)
100100+ nixenv-venv-links = rec {
101101+ env = runCommand "${python.name}-venv-links" {} ''
102102+ ${pythonEnv.interpreter} -m venv --system-site-packages --symlinks --without-pip $out
103103+ '';
104104+ interpreter = "${env}/bin/${pythonEnv.executable}";
105105+ is_venv = "True";
106106+ is_nixenv = "True";
107107+ is_virtualenv = "False";
108108+ };
76109 } // lib.optionalAttrs (python.pythonAtLeast "3.8") {
77110 # Venv built using Python Nix environment (python.buildEnv)
7878- # TODO: Cannot create venv from a nix env
7979- # Error: Command '['/nix/store/ddc8nqx73pda86ibvhzdmvdsqmwnbjf7-python3-3.7.6-venv/bin/python3.7', '-Im', 'ensurepip', '--upgrade', '--default-pip']' returned non-zero exit status 1.
8080- nixenv-venv = rec {
8181- env = runCommand "${python.name}-venv" {} ''
8282- ${pythonEnv.interpreter} -m venv $out
111111+ nixenv-venv-copies = rec {
112112+ env = runCommand "${python.name}-venv-copies" {} ''
113113+ ${pythonEnv.interpreter} -m venv --system-site-packages --copies --without-pip $out
83114 '';
84115 interpreter = "${env}/bin/${pythonEnv.executable}";
85116 is_venv = "True";
···91122 testfun = name: attrs: runCommand "${python.name}-tests-${name}" ({
92123 inherit (python) pythonVersion;
93124 } // attrs) ''
125125+ mkdir $out
126126+127127+ # set up the test files
94128 cp -r ${./tests/test_environments} tests
95129 chmod -R +w tests
96130 substituteAllInPlace tests/test_python.py
9797- ${attrs.interpreter} -m unittest discover --verbose tests #/test_python.py
9898- mkdir $out
131131+132132+ # run the tests by invoking the interpreter via full path
133133+ echo "absolute path: ${attrs.interpreter}"
134134+ ${attrs.interpreter} -m unittest discover --verbose tests 2>&1 | tee "$out/full.txt"
135135+136136+ # run the tests by invoking the interpreter via $PATH
137137+ export PATH="$(dirname ${attrs.interpreter}):$PATH"
138138+ echo "PATH: $(basename ${attrs.interpreter})"
139139+ "$(basename ${attrs.interpreter})" -m unittest discover --verbose tests 2>&1 | tee "$out/path.txt"
140140+141141+ # make sure we get the right path when invoking through a result link
142142+ ln -s "${attrs.env}" result
143143+ relative="result/bin/$(basename ${attrs.interpreter})"
144144+ expected="$PWD/$relative"
145145+ actual="$(./$relative -c "import sys; print(sys.executable)" | tee "$out/result.txt")"
146146+ if [ "$actual" != "$expected" ]; then
147147+ echo "expected $expected, got $actual"
148148+ exit 1
149149+ fi
150150+151151+ # if we got this far, the tests passed
99152 touch $out/success
100153 '';
101154
···38383939 @unittest.skipIf(IS_PYPY or sys.version_info.major==2, "Python 2 does not have base_prefix")
4040 def test_base_prefix(self):
4141- if IS_VENV or IS_NIXENV or IS_VIRTUALENV:
4141+ if IS_VENV or IS_VIRTUALENV:
4242 self.assertNotEqual(sys.prefix, sys.base_prefix)
4343 else:
4444 self.assertEqual(sys.prefix, sys.base_prefix)
+9-1
pkgs/development/interpreters/python/wrapper.nix
···3535 fi
3636 mkdir -p "$out/bin"
37373838+ rm -f $out/bin/.*-wrapped
3939+3840 for path in ${lib.concatStringsSep " " paths}; do
3941 if [ -d "$path/bin" ]; then
4042 cd "$path/bin"
···4244 if [ -f "$prg" ]; then
4345 rm -f "$out/bin/$prg"
4446 if [ -x "$prg" ]; then
4545- makeWrapper "$path/bin/$prg" "$out/bin/$prg" --set NIX_PYTHONPREFIX "$out" --set NIX_PYTHONEXECUTABLE ${pythonExecutable} --set NIX_PYTHONPATH ${pythonPath} ${lib.optionalString (!permitUserSite) ''--set PYTHONNOUSERSITE "true"''} ${lib.concatStringsSep " " makeWrapperArgs}
4747+ if [ -f ".$prg-wrapped" ]; then
4848+ echo "#!${pythonExecutable}" > "$out/bin/$prg"
4949+ sed -e '1d' -e '3d' ".$prg-wrapped" >> "$out/bin/$prg"
5050+ chmod +x "$out/bin/$prg"
5151+ else
5252+ makeWrapper "$path/bin/$prg" "$out/bin/$prg" --inherit-argv0 --resolve-argv0 ${lib.optionalString (!permitUserSite) ''--set PYTHONNOUSERSITE "true"''} ${lib.concatStringsSep " " makeWrapperArgs}
5353+ fi
4654 fi
4755 fi
4856 done