summaryrefslogtreecommitdiffstats
path: root/Lib/venv
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2024-01-17 21:52:23 (GMT)
committerGitHub <noreply@github.com>2024-01-17 21:52:23 (GMT)
commitf56d132deb9fff861439ed56ed7414d22e4e4bb9 (patch)
tree69e70221cd40f134de9abf1292f228ec7d5c9c11 /Lib/venv
parent78fcde039a33d8463e34356d5462fecee0f2831a (diff)
downloadcpython-f56d132deb9fff861439ed56ed7414d22e4e4bb9.zip
cpython-f56d132deb9fff861439ed56ed7414d22e4e4bb9.tar.gz
cpython-f56d132deb9fff861439ed56ed7414d22e4e4bb9.tar.bz2
gh-112984 Update Windows build and installer for free-threaded builds (GH-113129)
Diffstat (limited to 'Lib/venv')
-rw-r--r--Lib/venv/__init__.py258
1 files changed, 155 insertions, 103 deletions
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
index d960bf3..f04ca8f 100644
--- a/Lib/venv/__init__.py
+++ b/Lib/venv/__init__.py
@@ -139,6 +139,11 @@ class EnvBuilder:
'check that your PATH environment variable is '
'correctly set.')
dirname, exename = os.path.split(os.path.abspath(executable))
+ if sys.platform == 'win32':
+ # Always create the simplest name in the venv. It will either be a
+ # link back to executable, or a copy of the appropriate launcher
+ _d = '_d' if os.path.splitext(exename)[0].endswith('_d') else ''
+ exename = f'python{_d}.exe'
context.executable = executable
context.python_dir = dirname
context.python_exe = exename
@@ -222,67 +227,26 @@ class EnvBuilder:
args = ' '.join(args)
f.write(f'command = {sys.executable} -m venv {args}\n')
- if os.name != 'nt':
- def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
- """
- Try symlinking a file, and if that fails, fall back to copying.
- """
- force_copy = not self.symlinks
- if not force_copy:
- try:
- if not os.path.islink(dst): # can't link to itself!
- if relative_symlinks_ok:
- assert os.path.dirname(src) == os.path.dirname(dst)
- os.symlink(os.path.basename(src), dst)
- else:
- os.symlink(src, dst)
- except Exception: # may need to use a more specific exception
- logger.warning('Unable to symlink %r to %r', src, dst)
- force_copy = True
- if force_copy:
- shutil.copyfile(src, dst)
- else:
- def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
- """
- Try symlinking a file, and if that fails, fall back to copying.
- """
- bad_src = os.path.lexists(src) and not os.path.exists(src)
- if self.symlinks and not bad_src and not os.path.islink(dst):
- try:
+ def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
+ """
+ Try symlinking a file, and if that fails, fall back to copying.
+ (Unused on Windows, because we can't just copy a failed symlink file: we
+ switch to a different set of files instead.)
+ """
+ assert os.name != 'nt'
+ force_copy = not self.symlinks
+ if not force_copy:
+ try:
+ if not os.path.islink(dst): # can't link to itself!
if relative_symlinks_ok:
assert os.path.dirname(src) == os.path.dirname(dst)
os.symlink(os.path.basename(src), dst)
else:
os.symlink(src, dst)
- return
- except Exception: # may need to use a more specific exception
- logger.warning('Unable to symlink %r to %r', src, dst)
-
- # On Windows, we rewrite symlinks to our base python.exe into
- # copies of venvlauncher.exe
- basename, ext = os.path.splitext(os.path.basename(src))
- srcfn = os.path.join(os.path.dirname(__file__),
- "scripts",
- "nt",
- basename + ext)
- # Builds or venv's from builds need to remap source file
- # locations, as we do not put them into Lib/venv/scripts
- if sysconfig.is_python_build() or not os.path.isfile(srcfn):
- if basename.endswith('_d'):
- ext = '_d' + ext
- basename = basename[:-2]
- if basename == 'python':
- basename = 'venvlauncher'
- elif basename == 'pythonw':
- basename = 'venvwlauncher'
- src = os.path.join(os.path.dirname(src), basename + ext)
- else:
- src = srcfn
- if not os.path.exists(src):
- if not bad_src:
- logger.warning('Unable to copy %r', src)
- return
-
+ except Exception: # may need to use a more specific exception
+ logger.warning('Unable to symlink %r to %r', src, dst)
+ force_copy = True
+ if force_copy:
shutil.copyfile(src, dst)
def create_git_ignore_file(self, context):
@@ -298,22 +262,23 @@ class EnvBuilder:
'see https://docs.python.org/3/library/venv.html\n')
file.write('*\n')
- def setup_python(self, context):
- """
- Set up a Python executable in the environment.
+ if os.name != 'nt':
+ def setup_python(self, context):
+ """
+ Set up a Python executable in the environment.
- :param context: The information for the environment creation request
- being processed.
- """
- binpath = context.bin_path
- path = context.env_exe
- copier = self.symlink_or_copy
- dirname = context.python_dir
- if os.name != 'nt':
+ :param context: The information for the environment creation request
+ being processed.
+ """
+ binpath = context.bin_path
+ path = context.env_exe
+ copier = self.symlink_or_copy
+ dirname = context.python_dir
copier(context.executable, path)
if not os.path.islink(path):
os.chmod(path, 0o755)
- for suffix in ('python', 'python3', f'python3.{sys.version_info[1]}'):
+ for suffix in ('python', 'python3',
+ f'python3.{sys.version_info[1]}'):
path = os.path.join(binpath, suffix)
if not os.path.exists(path):
# Issue 18807: make copies if
@@ -321,30 +286,105 @@ class EnvBuilder:
copier(context.env_exe, path, relative_symlinks_ok=True)
if not os.path.islink(path):
os.chmod(path, 0o755)
- else:
- if self.symlinks:
- # For symlinking, we need a complete copy of the root directory
- # If symlinks fail, you'll get unnecessary copies of files, but
- # we assume that if you've opted into symlinks on Windows then
- # you know what you're doing.
- suffixes = [
- f for f in os.listdir(dirname) if
- os.path.normcase(os.path.splitext(f)[1]) in ('.exe', '.dll')
- ]
- if sysconfig.is_python_build():
- suffixes = [
- f for f in suffixes if
- os.path.normcase(f).startswith(('python', 'vcruntime'))
- ]
+
+ else:
+ def setup_python(self, context):
+ """
+ Set up a Python executable in the environment.
+
+ :param context: The information for the environment creation request
+ being processed.
+ """
+ binpath = context.bin_path
+ dirname = context.python_dir
+ exename = os.path.basename(context.env_exe)
+ exe_stem = os.path.splitext(exename)[0]
+ exe_d = '_d' if os.path.normcase(exe_stem).endswith('_d') else ''
+ if sysconfig.is_python_build():
+ scripts = dirname
+ else:
+ scripts = os.path.join(os.path.dirname(__file__),
+ 'scripts', 'nt')
+ if not sysconfig.get_config_var("Py_GIL_DISABLED"):
+ python_exe = os.path.join(dirname, f'python{exe_d}.exe')
+ pythonw_exe = os.path.join(dirname, f'pythonw{exe_d}.exe')
+ link_sources = {
+ 'python.exe': python_exe,
+ f'python{exe_d}.exe': python_exe,
+ 'pythonw.exe': pythonw_exe,
+ f'pythonw{exe_d}.exe': pythonw_exe,
+ }
+ python_exe = os.path.join(scripts, f'venvlauncher{exe_d}.exe')
+ pythonw_exe = os.path.join(scripts, f'venvwlauncher{exe_d}.exe')
+ copy_sources = {
+ 'python.exe': python_exe,
+ f'python{exe_d}.exe': python_exe,
+ 'pythonw.exe': pythonw_exe,
+ f'pythonw{exe_d}.exe': pythonw_exe,
+ }
else:
- suffixes = {'python.exe', 'python_d.exe', 'pythonw.exe', 'pythonw_d.exe'}
- base_exe = os.path.basename(context.env_exe)
- suffixes.add(base_exe)
+ exe_t = f'3.{sys.version_info[1]}t'
+ python_exe = os.path.join(dirname, f'python{exe_t}{exe_d}.exe')
+ pythonw_exe = os.path.join(dirname, f'pythonw{exe_t}{exe_d}.exe')
+ link_sources = {
+ 'python.exe': python_exe,
+ f'python{exe_d}.exe': python_exe,
+ f'python{exe_t}.exe': python_exe,
+ f'python{exe_t}{exe_d}.exe': python_exe,
+ 'pythonw.exe': pythonw_exe,
+ f'pythonw{exe_d}.exe': pythonw_exe,
+ f'pythonw{exe_t}.exe': pythonw_exe,
+ f'pythonw{exe_t}{exe_d}.exe': pythonw_exe,
+ }
+ python_exe = os.path.join(scripts, f'venvlaunchert{exe_d}.exe')
+ pythonw_exe = os.path.join(scripts, f'venvwlaunchert{exe_d}.exe')
+ copy_sources = {
+ 'python.exe': python_exe,
+ f'python{exe_d}.exe': python_exe,
+ f'python{exe_t}.exe': python_exe,
+ f'python{exe_t}{exe_d}.exe': python_exe,
+ 'pythonw.exe': pythonw_exe,
+ f'pythonw{exe_d}.exe': pythonw_exe,
+ f'pythonw{exe_t}.exe': pythonw_exe,
+ f'pythonw{exe_t}{exe_d}.exe': pythonw_exe,
+ }
+
+ do_copies = True
+ if self.symlinks:
+ do_copies = False
+ # For symlinking, we need all the DLLs to be available alongside
+ # the executables.
+ link_sources.update({
+ f: os.path.join(dirname, f) for f in os.listdir(dirname)
+ if os.path.normcase(f).startswith(('python', 'vcruntime'))
+ and os.path.normcase(os.path.splitext(f)[1]) == '.dll'
+ })
+
+ to_unlink = []
+ for dest, src in link_sources.items():
+ dest = os.path.join(binpath, dest)
+ try:
+ os.symlink(src, dest)
+ to_unlink.append(dest)
+ except OSError:
+ logger.warning('Unable to symlink %r to %r', src, dst)
+ do_copies = True
+ for f in to_unlink:
+ try:
+ os.unlink(f)
+ except OSError:
+ logger.warning('Failed to clean up symlink %r',
+ f)
+ logger.warning('Retrying with copies')
+ break
- for suffix in suffixes:
- src = os.path.join(dirname, suffix)
- if os.path.lexists(src):
- copier(src, os.path.join(binpath, suffix))
+ if do_copies:
+ for dest, src in copy_sources.items():
+ dest = os.path.join(binpath, dest)
+ try:
+ shutil.copy2(src, dest)
+ except OSError:
+ logger.warning('Unable to copy %r to %r', src, dest)
if sysconfig.is_python_build():
# copy init.tcl
@@ -437,6 +477,14 @@ class EnvBuilder:
"""
binpath = context.bin_path
plen = len(path)
+ if os.name == 'nt':
+ def skip_file(f):
+ f = os.path.normcase(f)
+ return (f.startswith(('python', 'venv'))
+ and f.endswith(('.exe', '.pdb')))
+ else:
+ def skip_file(f):
+ return False
for root, dirs, files in os.walk(path):
if root == path: # at top-level, remove irrelevant dirs
for d in dirs[:]:
@@ -444,8 +492,7 @@ class EnvBuilder:
dirs.remove(d)
continue # ignore files in top level
for f in files:
- if (os.name == 'nt' and f.startswith('python')
- and f.endswith(('.exe', '.pdb'))):
+ if skip_file(f):
continue
srcfile = os.path.join(root, f)
suffix = root[plen:].split(os.sep)[2:]
@@ -456,20 +503,25 @@ class EnvBuilder:
if not os.path.exists(dstdir):
os.makedirs(dstdir)
dstfile = os.path.join(dstdir, f)
+ if os.name == 'nt' and srcfile.endswith(('.exe', '.pdb')):
+ shutil.copy2(srcfile, dstfile)
+ continue
with open(srcfile, 'rb') as f:
data = f.read()
- if not srcfile.endswith(('.exe', '.pdb')):
- try:
- data = data.decode('utf-8')
- data = self.replace_variables(data, context)
- data = data.encode('utf-8')
- except UnicodeError as e:
- data = None
- logger.warning('unable to copy script %r, '
- 'may be binary: %s', srcfile, e)
- if data is not None:
+ try:
+ new_data = (
+ self.replace_variables(data.decode('utf-8'), context)
+ .encode('utf-8')
+ )
+ except UnicodeError as e:
+ logger.warning('unable to copy script %r, '
+ 'may be binary: %s', srcfile, e)
+ continue
+ if new_data == data:
+ shutil.copy2(srcfile, dstfile)
+ else:
with open(dstfile, 'wb') as f:
- f.write(data)
+ f.write(new_data)
shutil.copymode(srcfile, dstfile)
def upgrade_dependencies(self, context):