diff options
author | Jeremy Kloth <jeremy.kloth@gmail.com> | 2022-07-05 15:08:20 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-05 15:08:20 (GMT) |
commit | 067597522a9002f3b8aff7f46033f10acb2381e4 (patch) | |
tree | 68ae8f1c01ed530c8603c6da3064c6782e65de29 /Lib | |
parent | e6ec6f5b50e8793172e83a9afbb05fe01f236b37 (diff) | |
download | cpython-067597522a9002f3b8aff7f46033f10acb2381e4.zip cpython-067597522a9002f3b8aff7f46033f10acb2381e4.tar.gz cpython-067597522a9002f3b8aff7f46033f10acb2381e4.tar.bz2 |
gh-92897: Ensure `venv --copies` respects source build property of the creating interpreter (GH-92899)
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/distutils/sysconfig.py | 7 | ||||
-rw-r--r-- | Lib/distutils/tests/test_sysconfig.py | 6 | ||||
-rw-r--r-- | Lib/sysconfig.py | 51 | ||||
-rw-r--r-- | Lib/test/test_sysconfig.py | 6 | ||||
-rw-r--r-- | Lib/test/test_venv.py | 46 |
5 files changed, 76 insertions, 40 deletions
diff --git a/Lib/distutils/sysconfig.py b/Lib/distutils/sysconfig.py index 0418775..8eada98 100644 --- a/Lib/distutils/sysconfig.py +++ b/Lib/distutils/sysconfig.py @@ -29,8 +29,6 @@ from sysconfig import ( parse_config_h as sysconfig_parse_config_h, _init_non_posix, - _is_python_source_dir, - _sys_home, _variable_rx, _findvar1_rx, @@ -51,9 +49,6 @@ from sysconfig import ( # 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, @@ -286,7 +281,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) diff --git a/Lib/distutils/tests/test_sysconfig.py b/Lib/distutils/tests/test_sysconfig.py index 270e91a..8f9f72f 100644 --- a/Lib/distutils/tests/test_sysconfig.py +++ b/Lib/distutils/tests/test_sysconfig.py @@ -60,7 +60,11 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase): # 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()), diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py index bf926cf..ebe3711 100644 --- a/Lib/sysconfig.py +++ b/Lib/sysconfig.py @@ -195,37 +195,38 @@ else: # 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') diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 27722fbe..114e144 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -450,7 +450,11 @@ class TestSysConfig(unittest.TestCase): # 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 diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 1545a94..4359a4e 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -15,6 +15,7 @@ import shutil import struct import subprocess import sys +import sysconfig import tempfile from test.support import (captured_stdout, captured_stderr, skip_if_broken_multiprocessing_synchronize, verbose, @@ -254,18 +255,49 @@ class BasicTest(BaseTest): 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 = ( |