mirror of
https://github.com/python/cpython.git
synced 2025-08-02 08:02:56 +00:00
[3.11] gh-92897: Ensure venv --copies
respects source build property of the creating interpreter (GH-92899) (GH-94567)
(cherry picked from commit 067597522a
)
Co-authored-by: Jeremy Kloth <jeremy.kloth@gmail.com>
This commit is contained in:
parent
7a3dae06eb
commit
49aeda989d
5 changed files with 76 additions and 40 deletions
|
@ -30,8 +30,6 @@ from sysconfig import (
|
||||||
parse_config_h as sysconfig_parse_config_h,
|
parse_config_h as sysconfig_parse_config_h,
|
||||||
|
|
||||||
_init_non_posix,
|
_init_non_posix,
|
||||||
_is_python_source_dir,
|
|
||||||
_sys_home,
|
|
||||||
|
|
||||||
_variable_rx,
|
_variable_rx,
|
||||||
_findvar1_rx,
|
_findvar1_rx,
|
||||||
|
@ -52,9 +50,6 @@ from sysconfig import (
|
||||||
# which might not be true in the time of import.
|
# which might not be true in the time of import.
|
||||||
_config_vars = get_config_vars()
|
_config_vars = get_config_vars()
|
||||||
|
|
||||||
if os.name == "nt":
|
|
||||||
from sysconfig import _fix_pcbuild
|
|
||||||
|
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'The distutils.sysconfig module is deprecated, use sysconfig instead',
|
'The distutils.sysconfig module is deprecated, use sysconfig instead',
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
|
@ -287,7 +282,7 @@ def get_python_inc(plat_specific=0, prefix=None):
|
||||||
# must use "srcdir" from the makefile to find the "Include"
|
# must use "srcdir" from the makefile to find the "Include"
|
||||||
# directory.
|
# directory.
|
||||||
if plat_specific:
|
if plat_specific:
|
||||||
return _sys_home or project_base
|
return project_base
|
||||||
else:
|
else:
|
||||||
incdir = os.path.join(get_config_var('srcdir'), 'Include')
|
incdir = os.path.join(get_config_var('srcdir'), 'Include')
|
||||||
return os.path.normpath(incdir)
|
return os.path.normpath(incdir)
|
||||||
|
|
|
@ -61,7 +61,11 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase):
|
||||||
# should be a full source checkout.
|
# should be a full source checkout.
|
||||||
Python_h = os.path.join(srcdir, 'Include', 'Python.h')
|
Python_h = os.path.join(srcdir, 'Include', 'Python.h')
|
||||||
self.assertTrue(os.path.exists(Python_h), 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':
|
elif os.name == 'posix':
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
os.path.dirname(sysconfig.get_makefile_filename()),
|
os.path.dirname(sysconfig.get_makefile_filename()),
|
||||||
|
|
|
@ -195,37 +195,38 @@ else:
|
||||||
# unable to retrieve the real program name
|
# unable to retrieve the real program name
|
||||||
_PROJECT_BASE = _safe_realpath(os.getcwd())
|
_PROJECT_BASE = _safe_realpath(os.getcwd())
|
||||||
|
|
||||||
if (os.name == 'nt' and
|
# In a virtual environment, `sys._home` gives us the target directory
|
||||||
_PROJECT_BASE.lower().endswith(('\\pcbuild\\win32', '\\pcbuild\\amd64'))):
|
# `_PROJECT_BASE` for the executable that created it when the virtual
|
||||||
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
|
# 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
|
# set for cross builds
|
||||||
if "_PYTHON_PROJECT_BASE" in os.environ:
|
if "_PYTHON_PROJECT_BASE" in os.environ:
|
||||||
_PROJECT_BASE = _safe_realpath(os.environ["_PYTHON_PROJECT_BASE"])
|
_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"):
|
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 True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
_sys_home = getattr(sys, '_home', None)
|
_PYTHON_BUILD = is_python_build()
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if _PYTHON_BUILD:
|
if _PYTHON_BUILD:
|
||||||
for scheme in ('posix_prefix', 'posix_home'):
|
for scheme in ('posix_prefix', 'posix_home'):
|
||||||
|
@ -442,7 +443,7 @@ def _parse_makefile(filename, vars=None, keep_unresolved=True):
|
||||||
def get_makefile_filename():
|
def get_makefile_filename():
|
||||||
"""Return the path of the Makefile."""
|
"""Return the path of the Makefile."""
|
||||||
if _PYTHON_BUILD:
|
if _PYTHON_BUILD:
|
||||||
return os.path.join(_sys_home or _PROJECT_BASE, "Makefile")
|
return os.path.join(_PROJECT_BASE, "Makefile")
|
||||||
if hasattr(sys, 'abiflags'):
|
if hasattr(sys, 'abiflags'):
|
||||||
config_dir_name = f'config-{_PY_VERSION_SHORT}{sys.abiflags}'
|
config_dir_name = f'config-{_PY_VERSION_SHORT}{sys.abiflags}'
|
||||||
else:
|
else:
|
||||||
|
@ -587,9 +588,9 @@ def get_config_h_filename():
|
||||||
"""Return the path of pyconfig.h."""
|
"""Return the path of pyconfig.h."""
|
||||||
if _PYTHON_BUILD:
|
if _PYTHON_BUILD:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
inc_dir = os.path.join(_sys_home or _PROJECT_BASE, "PC")
|
inc_dir = os.path.join(_PROJECT_BASE, "PC")
|
||||||
else:
|
else:
|
||||||
inc_dir = _sys_home or _PROJECT_BASE
|
inc_dir = _PROJECT_BASE
|
||||||
else:
|
else:
|
||||||
inc_dir = get_path('platinclude')
|
inc_dir = get_path('platinclude')
|
||||||
return os.path.join(inc_dir, 'pyconfig.h')
|
return os.path.join(inc_dir, 'pyconfig.h')
|
||||||
|
|
|
@ -451,7 +451,11 @@ class TestSysConfig(unittest.TestCase):
|
||||||
# should be a full source checkout.
|
# should be a full source checkout.
|
||||||
Python_h = os.path.join(srcdir, 'Include', 'Python.h')
|
Python_h = os.path.join(srcdir, 'Include', 'Python.h')
|
||||||
self.assertTrue(os.path.exists(Python_h), 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':
|
elif os.name == 'posix':
|
||||||
makefile_dir = os.path.dirname(sysconfig.get_makefile_filename())
|
makefile_dir = os.path.dirname(sysconfig.get_makefile_filename())
|
||||||
# Issue #19340: srcdir has been realpath'ed already
|
# Issue #19340: srcdir has been realpath'ed already
|
||||||
|
|
|
@ -15,6 +15,7 @@ import shutil
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import sysconfig
|
||||||
import tempfile
|
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, verbose,
|
skip_if_broken_multiprocessing_synchronize, verbose,
|
||||||
|
@ -254,18 +255,49 @@ class BasicTest(BaseTest):
|
||||||
self.assertEqual(out.strip(), expected.encode(), prefix)
|
self.assertEqual(out.strip(), expected.encode(), prefix)
|
||||||
|
|
||||||
@requireVenvCreate
|
@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)
|
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)
|
envpy = os.path.join(self.env_dir, self.bindir, self.exe)
|
||||||
cmd = [envpy, '-c', None]
|
cmd = [envpy, '-c', None]
|
||||||
for call in ('get_preferred_scheme("prefix")', 'get_default_scheme()'):
|
for call, expected in (
|
||||||
cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
|
# installation scheme
|
||||||
out, err = check_output(cmd)
|
('get_preferred_scheme("prefix")', 'venv'),
|
||||||
self.assertEqual(out.strip(), b'venv', err)
|
('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':
|
if sys.platform == 'win32':
|
||||||
ENV_SUBDIRS = (
|
ENV_SUBDIRS = (
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue