summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorJeremy Kloth <jeremy.kloth@gmail.com>2022-07-05 15:08:20 (GMT)
committerGitHub <noreply@github.com>2022-07-05 15:08:20 (GMT)
commit067597522a9002f3b8aff7f46033f10acb2381e4 (patch)
tree68ae8f1c01ed530c8603c6da3064c6782e65de29 /Lib
parente6ec6f5b50e8793172e83a9afbb05fe01f236b37 (diff)
downloadcpython-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.py7
-rw-r--r--Lib/distutils/tests/test_sysconfig.py6
-rw-r--r--Lib/sysconfig.py51
-rw-r--r--Lib/test/test_sysconfig.py6
-rw-r--r--Lib/test/test_venv.py46
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 = (