[Python-checkins] [3.11] gh-92897: Ensure `venv --copies` respects source build property of the creating interpreter (GH-92899) (GH-94567)

vsajip webhook-mailer at python.org
Tue Jul 5 11:40:26 EDT 2022


https://github.com/python/cpython/commit/49aeda989d9385eb4f0c41f7a7e4854037a7abfb
commit: 49aeda989d9385eb4f0c41f7a7e4854037a7abfb
branch: 3.11
author: Vinay Sajip <vinay_sajip at yahoo.co.uk>
committer: vsajip <vinay_sajip at yahoo.co.uk>
date: 2022-07-05T16:40:17+01:00
summary:

[3.11] gh-92897: Ensure `venv --copies` respects source build property of the creating interpreter (GH-92899) (GH-94567)

(cherry picked from commit 067597522a9002f3b8aff7f46033f10acb2381e4)

Co-authored-by: Jeremy Kloth <jeremy.kloth at gmail.com>

files:
M Lib/distutils/sysconfig.py
M Lib/distutils/tests/test_sysconfig.py
M Lib/sysconfig.py
M Lib/test/test_sysconfig.py
M Lib/test/test_venv.py

diff --git a/Lib/distutils/sysconfig.py b/Lib/distutils/sysconfig.py
index 3414a761e76b99..03b85584190db9 100644
--- a/Lib/distutils/sysconfig.py
+++ b/Lib/distutils/sysconfig.py
@@ -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)
diff --git a/Lib/distutils/tests/test_sysconfig.py b/Lib/distutils/tests/test_sysconfig.py
index 0664acade82cd1..d1c472794c5ae5 100644
--- a/Lib/distutils/tests/test_sysconfig.py
+++ b/Lib/distutils/tests/test_sysconfig.py
@@ -61,7 +61,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()),
diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
index bf926cf76807b7..ebe37118274222 100644
--- a/Lib/sysconfig.py
+++ b/Lib/sysconfig.py
@@ -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')
diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
index 83b492e9219ac1..578ac1db504455 100644
--- a/Lib/test/test_sysconfig.py
+++ b/Lib/test/test_sysconfig.py
@@ -451,7 +451,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
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index 4440fceace2060..74039f59f797e5 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -15,6 +15,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,
@@ -254,18 +255,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 = (



More information about the Python-checkins mailing list