From 2e6bc39693b47272a203fa3a2234cb2316cd60b9 Mon Sep 17 00:00:00 2001 From: CPython developers <> Date: Mon, 18 Jul 2022 23:55:52 +0900 Subject: [PATCH] update venv from CPython 3.10.5 --- Lib/test/test_venv.py | 74 ++++++++++++++++++++++------ Lib/venv/__init__.py | 33 ++++++++----- Lib/venv/scripts/common/Activate.ps1 | 8 ++- Lib/venv/scripts/common/activate | 3 ++ Lib/venv/scripts/nt/activate.bat | 1 + Lib/venv/scripts/nt/deactivate.bat | 1 + Lib/venv/scripts/posix/activate.csh | 3 +- Lib/venv/scripts/posix/activate.fish | 2 + 8 files changed, 94 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index bf469c77ec..94d626598b 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -5,8 +5,7 @@ Copyright (C) 2011-2012 Vinay Sajip. Licensed to the PSF under a contributor agreement. """ -# pip isn't working yet -# import ensurepip +import ensurepip import os import os.path import re @@ -15,12 +14,12 @@ import struct import subprocess import sys import tempfile -from test.support import captured_stdout, captured_stderr, requires_zlib +from test.support import (captured_stdout, captured_stderr, requires_zlib, + skip_if_broken_multiprocessing_synchronize) from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) -from test.support.import_helper import import_module -import threading import unittest import venv +from unittest.mock import patch try: import ctypes @@ -80,8 +79,8 @@ class BaseTest(unittest.TestCase): def get_env_file(self, *args): return os.path.join(self.env_dir, *args) - def get_text_file_contents(self, *args): - with open(self.get_env_file(*args), 'r') as f: + def get_text_file_contents(self, *args, encoding='utf-8'): + with open(self.get_env_file(*args), 'r', encoding=encoding) as f: result = f.read() return result @@ -139,6 +138,45 @@ class BasicTest(BaseTest): self.assertEqual(context.prompt, '(My prompt) ') self.assertIn("prompt = 'My prompt'\n", data) + rmtree(self.env_dir) + builder = venv.EnvBuilder(prompt='.') + cwd = os.path.basename(os.getcwd()) + self.run_with_capture(builder.create, self.env_dir) + context = builder.ensure_directories(self.env_dir) + data = self.get_text_file_contents('pyvenv.cfg') + self.assertEqual(context.prompt, '(%s) ' % cwd) + self.assertIn("prompt = '%s'\n" % cwd, data) + + def test_upgrade_dependencies(self): + builder = venv.EnvBuilder() + bin_path = 'Scripts' if sys.platform == 'win32' else 'bin' + python_exe = os.path.split(sys.executable)[1] + with tempfile.TemporaryDirectory() as fake_env_dir: + expect_exe = os.path.normcase( + os.path.join(fake_env_dir, bin_path, python_exe) + ) + if sys.platform == 'win32': + expect_exe = os.path.normcase(os.path.realpath(expect_exe)) + + def pip_cmd_checker(cmd): + cmd[0] = os.path.normcase(cmd[0]) + self.assertEqual( + cmd, + [ + expect_exe, + '-m', + 'pip', + 'install', + '--upgrade', + 'pip', + 'setuptools' + ] + ) + + fake_context = builder.ensure_directories(fake_env_dir) + with patch('venv.subprocess.check_call', pip_cmd_checker): + builder.upgrade_dependencies(fake_context) + @requireVenvCreate def test_prefixes(self): """ @@ -325,10 +363,11 @@ class BasicTest(BaseTest): """ Test that the multiprocessing is able to spawn. """ - # Issue bpo-36342: Instanciation of a Pool object imports the + # bpo-36342: Instantiation of a Pool object imports the # multiprocessing.synchronize module. Skip the test if this module # cannot be imported. - import_module('multiprocessing.synchronize') + skip_if_broken_multiprocessing_synchronize() + rmtree(self.env_dir) self.run_with_capture(venv.create, self.env_dir) envpy = os.path.join(os.path.realpath(self.env_dir), @@ -413,7 +452,7 @@ class EnsurePipTest(BaseTest): # pip's cross-version compatibility may trigger deprecation # warnings in current versions of Python. Ensure related # environment settings don't cause venv to fail. - envvars["PYTHONWARNINGS"] = "e" + envvars["PYTHONWARNINGS"] = "ignore" # ensurepip is different enough from a normal pip invocation # that we want to ensure it ignores the normal pip environment # variable settings. We set PIP_NO_INSTALL here specifically @@ -452,7 +491,8 @@ class EnsurePipTest(BaseTest): # Ensure pip is available in the virtual environment envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe) # Ignore DeprecationWarning since pip code is not part of Python - out, err = check_output([envpy, '-W', 'ignore::DeprecationWarning', '-I', + out, err = check_output([envpy, '-W', 'ignore::DeprecationWarning', + '-W', 'ignore::ImportWarning', '-I', '-m', 'pip', '--version']) # We force everything to text, so unittest gives the detailed diff # if we get unexpected results @@ -468,8 +508,12 @@ class EnsurePipTest(BaseTest): # Check the private uninstall command provided for the Windows # installers works (at least in a virtual environment) with EnvironmentVarGuard() as envvars: + # It seems ensurepip._uninstall calls subprocesses which do not + # inherit the interpreter settings. + envvars["PYTHONWARNINGS"] = "ignore" out, err = check_output([envpy, - '-W', 'ignore::DeprecationWarning', '-I', + '-W', 'ignore::DeprecationWarning', + '-W', 'ignore::ImportWarning', '-I', '-m', 'ensurepip._uninstall']) # We force everything to text, so unittest gives the detailed diff # if we get unexpected results @@ -481,7 +525,7 @@ class EnsurePipTest(BaseTest): # executing pip with sudo, you may want sudo's -H flag." # where $HOME is replaced by the HOME environment variable. err = re.sub("^(WARNING: )?The directory .* or its parent directory " - "is not owned by the current user .*$", "", + "is not owned or is not writable by the current user.*$", "", err, flags=re.MULTILINE) self.assertEqual(err.rstrip(), "") # Being fairly specific regarding the expected behaviour for the @@ -497,10 +541,8 @@ class EnsurePipTest(BaseTest): self.assert_pip_not_installed() # Issue #26610: pip/pep425tags.py requires ctypes - # TODO: RUSTPYTHON @unittest.skipUnless(ctypes, 'pip requires ctypes') - @requires_zlib - @unittest.expectedFailure + @requires_zlib() def test_with_pip(self): self.do_test_with_pip(False) self.do_test_with_pip(True) diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index d80463762a..6f1af294ae 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -142,6 +142,20 @@ class EnvBuilder: context.bin_name = binname context.env_exe = os.path.join(binpath, exename) create_if_needed(binpath) + # Assign and update the command to use when launching the newly created + # environment, in case it isn't simply the executable script (e.g. bpo-45337) + context.env_exec_cmd = context.env_exe + if sys.platform == 'win32': + # bpo-45337: Fix up env_exec_cmd to account for file system redirections. + # Some redirects only apply to CreateFile and not CreateProcess + real_env_exe = os.path.realpath(context.env_exe) + if os.path.normcase(real_env_exe) != os.path.normcase(context.env_exe): + logger.warning('Actual environment location may have moved due to ' + 'redirects, links or junctions.\n' + ' Requested location: "%s"\n' + ' Actual location: "%s"', + context.env_exe, real_env_exe) + context.env_exec_cmd = real_env_exe return context def create_configuration(self, context): @@ -267,8 +281,9 @@ class EnvBuilder: os.path.normcase(f).startswith(('python', 'vcruntime')) ] else: - suffixes = ['python.exe', 'python_d.exe', 'pythonw.exe', - 'pythonw_d.exe'] + suffixes = {'python.exe', 'python_d.exe', 'pythonw.exe', 'pythonw_d.exe'} + base_exe = os.path.basename(context.env_exe) + suffixes.add(base_exe) for suffix in suffixes: src = os.path.join(dirname, suffix) @@ -290,15 +305,11 @@ class EnvBuilder: def _setup_pip(self, context): """Installs or upgrades pip in a virtual environment""" - # TODO: RustPython - msg = ("Pip isn't supported yet. To create a virtual environment" - "without pip, call venv with the --without-pip flag.") - raise NotImplementedError(msg) # We run ensurepip in isolated mode to avoid side effects from # environment vars, the current directory and anything else # intended for the global Python environment - cmd = [context.env_exe, '-Im', 'ensurepip', '--upgrade', - '--default-pip'] + cmd = [context.env_exec_cmd, '-Im', 'ensurepip', '--upgrade', + '--default-pip'] subprocess.check_output(cmd, stderr=subprocess.STDOUT) def setup_scripts(self, context): @@ -398,11 +409,7 @@ class EnvBuilder: logger.debug( f'Upgrading {CORE_VENV_DEPS} packages in {context.bin_path}' ) - if sys.platform == 'win32': - python_exe = os.path.join(context.bin_path, 'python.exe') - else: - python_exe = os.path.join(context.bin_path, 'python') - cmd = [python_exe, '-m', 'pip', 'install', '--upgrade'] + cmd = [context.env_exec_cmd, '-m', 'pip', 'install', '--upgrade'] cmd.extend(CORE_VENV_DEPS) subprocess.check_call(cmd) diff --git a/Lib/venv/scripts/common/Activate.ps1 b/Lib/venv/scripts/common/Activate.ps1 index 2fb3852c3c..b49d77ba44 100644 --- a/Lib/venv/scripts/common/Activate.ps1 +++ b/Lib/venv/scripts/common/Activate.ps1 @@ -96,6 +96,11 @@ function global:deactivate ([switch]$NonDestructive) { Remove-Item -Path env:VIRTUAL_ENV } + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force @@ -197,7 +202,7 @@ else { $Prompt = $pyvenvCfg['prompt']; } else { - Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virutal environment)" + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" $Prompt = Split-Path -Path $venvDir -Leaf } @@ -228,6 +233,7 @@ if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " _OLD_VIRTUAL_PROMPT } + $env:VIRTUAL_ENV_PROMPT = $Prompt } # Clear PYTHONHOME diff --git a/Lib/venv/scripts/common/activate b/Lib/venv/scripts/common/activate index 45af3536aa..6fbc2b8801 100644 --- a/Lib/venv/scripts/common/activate +++ b/Lib/venv/scripts/common/activate @@ -28,6 +28,7 @@ deactivate () { fi unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT if [ ! "${1:-}" = "nondestructive" ] ; then # Self destruct! unset -f deactivate @@ -56,6 +57,8 @@ if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then _OLD_VIRTUAL_PS1="${PS1:-}" PS1="__VENV_PROMPT__${PS1:-}" export PS1 + VIRTUAL_ENV_PROMPT="__VENV_PROMPT__" + export VIRTUAL_ENV_PROMPT fi # This should detect bash and zsh, which have a hash command that must diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat index f61413e232..5daa45afc9 100644 --- a/Lib/venv/scripts/nt/activate.bat +++ b/Lib/venv/scripts/nt/activate.bat @@ -25,6 +25,7 @@ if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH% if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH% set PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH% +set VIRTUAL_ENV_PROMPT=__VENV_PROMPT__ :END if defined _OLD_CODEPAGE ( diff --git a/Lib/venv/scripts/nt/deactivate.bat b/Lib/venv/scripts/nt/deactivate.bat index 313c079117..44dae49537 100644 --- a/Lib/venv/scripts/nt/deactivate.bat +++ b/Lib/venv/scripts/nt/deactivate.bat @@ -17,5 +17,6 @@ if defined _OLD_VIRTUAL_PATH ( set _OLD_VIRTUAL_PATH= set VIRTUAL_ENV= +set VIRTUAL_ENV_PROMPT= :END diff --git a/Lib/venv/scripts/posix/activate.csh b/Lib/venv/scripts/posix/activate.csh index 68a0dc74e1..d6f697c55e 100644 --- a/Lib/venv/scripts/posix/activate.csh +++ b/Lib/venv/scripts/posix/activate.csh @@ -3,7 +3,7 @@ # Created by Davide Di Blasi . # Ported to Python 3.3 venv by Andrew Svetlov -alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate' +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' # Unset irrelevant variables. deactivate nondestructive @@ -18,6 +18,7 @@ set _OLD_VIRTUAL_PROMPT="$prompt" if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then set prompt = "__VENV_PROMPT__$prompt" + setenv VIRTUAL_ENV_PROMPT "__VENV_PROMPT__" endif alias pydoc python -m pydoc diff --git a/Lib/venv/scripts/posix/activate.fish b/Lib/venv/scripts/posix/activate.fish index 54b9ea5676..e40a1d7148 100644 --- a/Lib/venv/scripts/posix/activate.fish +++ b/Lib/venv/scripts/posix/activate.fish @@ -20,6 +20,7 @@ function deactivate -d "Exit virtual environment and return to normal shell env end set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT if test "$argv[1]" != "nondestructive" # Self-destruct! functions -e deactivate @@ -61,4 +62,5 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" end set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT "__VENV_PROMPT__" end