Skip to content

gh-92897: Ensure venv --copies respects source build property of the creating interpreter (GH-92899) #92899

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 5, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions Lib/distutils/sysconfig.py
Original file line number Diff line number Diff line change
@@ -30,8 +30,6 @@
parse_config_h as sysconfig_parse_config_h,

_init_non_posix,
_is_python_source_dir,
_sys_home,

_variable_rx,
_findvar1_rx,
@@ -52,9 +50,6 @@
# which might not be true in the time of import.
_config_vars = get_config_vars()

if os.name == "nt":
from sysconfig import _fix_pcbuild

warnings.warn(
'The distutils.sysconfig module is deprecated, use sysconfig instead',
DeprecationWarning,
@@ -287,7 +282,7 @@ def get_python_inc(plat_specific=0, prefix=None):
# must use "srcdir" from the makefile to find the "Include"
# directory.
if plat_specific:
return _sys_home or project_base
return project_base
else:
incdir = os.path.join(get_config_var('srcdir'), 'Include')
return os.path.normpath(incdir)
6 changes: 5 additions & 1 deletion Lib/distutils/tests/test_sysconfig.py
Original file line number Diff line number Diff line change
@@ -60,7 +60,11 @@ def test_srcdir(self):
# should be a full source checkout.
Python_h = os.path.join(srcdir, 'Include', 'Python.h')
self.assertTrue(os.path.exists(Python_h), Python_h)
self.assertTrue(sysconfig._is_python_source_dir(srcdir))
# <srcdir>/PC/pyconfig.h always exists even if unused on POSIX.
pyconfig_h = os.path.join(srcdir, 'PC', 'pyconfig.h')
self.assertTrue(os.path.exists(pyconfig_h), pyconfig_h)
pyconfig_h_in = os.path.join(srcdir, 'pyconfig.h.in')
self.assertTrue(os.path.exists(pyconfig_h_in), pyconfig_h_in)
elif os.name == 'posix':
self.assertEqual(
os.path.dirname(sysconfig.get_makefile_filename()),
51 changes: 26 additions & 25 deletions Lib/sysconfig.py
Original file line number Diff line number Diff line change
@@ -195,37 +195,38 @@ def _safe_realpath(path):
# unable to retrieve the real program name
_PROJECT_BASE = _safe_realpath(os.getcwd())

if (os.name == 'nt' and
_PROJECT_BASE.lower().endswith(('\\pcbuild\\win32', '\\pcbuild\\amd64'))):
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
# In a virtual environment, `sys._home` gives us the target directory
# `_PROJECT_BASE` for the executable that created it when the virtual
# python is an actual executable ('venv --copies' or Windows).
_sys_home = getattr(sys, '_home', None)
if _sys_home:
_PROJECT_BASE = _sys_home

if os.name == 'nt':
# In a source build, the executable is in a subdirectory of the root
# that we want (<root>\PCbuild\<platname>).
# `_BASE_PREFIX` is used as the base installation is where the source
# will be. The realpath is needed to prevent mount point confusion
# that can occur with just string comparisons.
if _safe_realpath(_PROJECT_BASE).startswith(
_safe_realpath(f'{_BASE_PREFIX}\\PCbuild')):
_PROJECT_BASE = _BASE_PREFIX

# set for cross builds
if "_PYTHON_PROJECT_BASE" in os.environ:
_PROJECT_BASE = _safe_realpath(os.environ["_PYTHON_PROJECT_BASE"])

def _is_python_source_dir(d):
def is_python_build(check_home=None):
if check_home is not None:
import warnings
warnings.warn("check_home argument is deprecated and ignored.",
DeprecationWarning, stacklevel=2)
for fn in ("Setup", "Setup.local"):
if os.path.isfile(os.path.join(d, "Modules", fn)):
if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
return True
return False

_sys_home = getattr(sys, '_home', None)

if os.name == 'nt':
def _fix_pcbuild(d):
if d and os.path.normcase(d).startswith(
os.path.normcase(os.path.join(_PREFIX, "PCbuild"))):
return _PREFIX
return d
_PROJECT_BASE = _fix_pcbuild(_PROJECT_BASE)
_sys_home = _fix_pcbuild(_sys_home)

def is_python_build(check_home=False):
if check_home and _sys_home:
return _is_python_source_dir(_sys_home)
return _is_python_source_dir(_PROJECT_BASE)

_PYTHON_BUILD = is_python_build(True)
_PYTHON_BUILD = is_python_build()

if _PYTHON_BUILD:
for scheme in ('posix_prefix', 'posix_home'):
@@ -442,7 +443,7 @@ def _parse_makefile(filename, vars=None, keep_unresolved=True):
def get_makefile_filename():
"""Return the path of the Makefile."""
if _PYTHON_BUILD:
return os.path.join(_sys_home or _PROJECT_BASE, "Makefile")
return os.path.join(_PROJECT_BASE, "Makefile")
if hasattr(sys, 'abiflags'):
config_dir_name = f'config-{_PY_VERSION_SHORT}{sys.abiflags}'
else:
@@ -587,9 +588,9 @@ def get_config_h_filename():
"""Return the path of pyconfig.h."""
if _PYTHON_BUILD:
if os.name == "nt":
inc_dir = os.path.join(_sys_home or _PROJECT_BASE, "PC")
inc_dir = os.path.join(_PROJECT_BASE, "PC")
else:
inc_dir = _sys_home or _PROJECT_BASE
inc_dir = _PROJECT_BASE
else:
inc_dir = get_path('platinclude')
return os.path.join(inc_dir, 'pyconfig.h')
6 changes: 5 additions & 1 deletion Lib/test/test_sysconfig.py
Original file line number Diff line number Diff line change
@@ -448,7 +448,11 @@ def test_srcdir(self):
# should be a full source checkout.
Python_h = os.path.join(srcdir, 'Include', 'Python.h')
self.assertTrue(os.path.exists(Python_h), Python_h)
self.assertTrue(sysconfig._is_python_source_dir(srcdir))
# <srcdir>/PC/pyconfig.h always exists even if unused on POSIX.
pyconfig_h = os.path.join(srcdir, 'PC', 'pyconfig.h')
self.assertTrue(os.path.exists(pyconfig_h), pyconfig_h)
pyconfig_h_in = os.path.join(srcdir, 'pyconfig.h.in')
self.assertTrue(os.path.exists(pyconfig_h_in), pyconfig_h_in)
elif os.name == 'posix':
makefile_dir = os.path.dirname(sysconfig.get_makefile_filename())
# Issue #19340: srcdir has been realpath'ed already
46 changes: 39 additions & 7 deletions Lib/test/test_venv.py
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
import struct
import subprocess
import sys
import sysconfig
import tempfile
from test.support import (captured_stdout, captured_stderr, requires_zlib,
skip_if_broken_multiprocessing_synchronize, verbose,
@@ -240,18 +241,49 @@ def test_prefixes(self):
self.assertEqual(out.strip(), expected.encode(), prefix)

@requireVenvCreate
def test_sysconfig_preferred_and_default_scheme(self):
def test_sysconfig(self):
"""
Test that the sysconfig preferred(prefix) and default scheme is venv.
Test that the sysconfig functions work in a virtual environment.
"""
rmtree(self.env_dir)
self.run_with_capture(venv.create, self.env_dir)
self.run_with_capture(venv.create, self.env_dir, symlinks=False)
envpy = os.path.join(self.env_dir, self.bindir, self.exe)
cmd = [envpy, '-c', None]
for call in ('get_preferred_scheme("prefix")', 'get_default_scheme()'):
cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
out, err = check_output(cmd)
self.assertEqual(out.strip(), b'venv', err)
for call, expected in (
# installation scheme
('get_preferred_scheme("prefix")', 'venv'),
('get_default_scheme()', 'venv'),
# build environment
('is_python_build()', str(sysconfig.is_python_build())),
('get_makefile_filename()', sysconfig.get_makefile_filename()),
('get_config_h_filename()', sysconfig.get_config_h_filename())):
with self.subTest(call):
cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
out, err = check_output(cmd)
self.assertEqual(out.strip(), expected.encode(), err)

@requireVenvCreate
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
def test_sysconfig_symlinks(self):
"""
Test that the sysconfig functions work in a virtual environment.
"""
rmtree(self.env_dir)
self.run_with_capture(venv.create, self.env_dir, symlinks=True)
envpy = os.path.join(self.env_dir, self.bindir, self.exe)
cmd = [envpy, '-c', None]
for call, expected in (
# installation scheme
('get_preferred_scheme("prefix")', 'venv'),
('get_default_scheme()', 'venv'),
# build environment
('is_python_build()', str(sysconfig.is_python_build())),
('get_makefile_filename()', sysconfig.get_makefile_filename()),
('get_config_h_filename()', sysconfig.get_config_h_filename())):
with self.subTest(call):
cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
out, err = check_output(cmd)
self.assertEqual(out.strip(), expected.encode(), err)

if sys.platform == 'win32':
ENV_SUBDIRS = (