[Python-checkins] gh-91985: Ensure in-tree builds override platstdlib_dir in every path calculation (GH-93641)

miss-islington webhook-mailer at python.org
Mon Jun 20 14:37:36 EDT 2022


https://github.com/python/cpython/commit/b8fe3bd1d4be5b7d8e9e20b3c36277117e8f6102
commit: b8fe3bd1d4be5b7d8e9e20b3c36277117e8f6102
branch: 3.11
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: miss-islington <31488909+miss-islington at users.noreply.github.com>
date: 2022-06-20T11:37:27-07:00
summary:

gh-91985: Ensure in-tree builds override platstdlib_dir in every path calculation (GH-93641)

(cherry picked from commit 38af903506e9b18c6350c1dadcb489f057713f36)

Co-authored-by: neonene <53406459+neonene at users.noreply.github.com>

files:
M Lib/test/test_embed.py
M Modules/getpath.py
M Programs/_testembed.c
M Python/pathconfig.c

diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index a82e1506f9170..de3354b6b5950 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -1299,6 +1299,66 @@ def test_init_setpythonhome(self):
         self.check_all_configs("test_init_setpythonhome", config,
                                api=API_COMPAT, env=env)
 
+    def test_init_is_python_build_with_home(self):
+        # Test _Py_path_config._is_python_build configuration (gh-91985)
+        config = self._get_expected_config()
+        paths = config['config']['module_search_paths']
+        paths_str = os.path.pathsep.join(paths)
+
+        for path in paths:
+            if not os.path.isdir(path):
+                continue
+            if os.path.exists(os.path.join(path, 'os.py')):
+                home = os.path.dirname(path)
+                break
+        else:
+            self.fail(f"Unable to find home in {paths!r}")
+
+        prefix = exec_prefix = home
+        if MS_WINDOWS:
+            stdlib = os.path.join(home, "Lib")
+            # Because we are specifying 'home', module search paths
+            # are fairly static
+            expected_paths = [paths[0], stdlib, os.path.join(home, 'DLLs')]
+        else:
+            version = f'{sys.version_info.major}.{sys.version_info.minor}'
+            stdlib = os.path.join(home, sys.platlibdir, f'python{version}')
+            expected_paths = self.module_search_paths(prefix=home, exec_prefix=home)
+
+        config = {
+            'home': home,
+            'module_search_paths': expected_paths,
+            'prefix': prefix,
+            'base_prefix': prefix,
+            'exec_prefix': exec_prefix,
+            'base_exec_prefix': exec_prefix,
+            'pythonpath_env': paths_str,
+            'stdlib_dir': stdlib,
+        }
+        # The code above is taken from test_init_setpythonhome()
+        env = {'TESTHOME': home, 'PYTHONPATH': paths_str}
+
+        env['NEGATIVE_ISPYTHONBUILD'] = '1'
+        config['_is_python_build'] = 0
+        self.check_all_configs("test_init_is_python_build", config,
+                               api=API_COMPAT, env=env)
+
+        env['NEGATIVE_ISPYTHONBUILD'] = '0'
+        config['_is_python_build'] = 1
+        exedir = os.path.dirname(sys.executable)
+        with open(os.path.join(exedir, 'pybuilddir.txt'), encoding='utf8') as f:
+            expected_paths[2] = os.path.normpath(
+                os.path.join(exedir, f'{f.read()}\n$'.splitlines()[0]))
+        if not MS_WINDOWS:
+            # PREFIX (default) is set when running in build directory
+            prefix = exec_prefix = sys.prefix
+            # stdlib calculation (/Lib) is not yet supported
+            expected_paths[0] = self.module_search_paths(prefix=prefix)[0]
+            config.update(prefix=prefix, base_prefix=prefix,
+                          exec_prefix=exec_prefix, base_exec_prefix=exec_prefix)
+        self.check_all_configs("test_init_is_python_build", config,
+                               api=API_COMPAT, env=env)
+
     def copy_paths_by_env(self, config):
         all_configs = self._get_expected_config()
         paths = all_configs['config']['module_search_paths']
diff --git a/Modules/getpath.py b/Modules/getpath.py
index 47f075caf5551..dceeed7702c0b 100644
--- a/Modules/getpath.py
+++ b/Modules/getpath.py
@@ -461,7 +461,8 @@ def search_up(prefix, *landmarks, test=isfile):
 
 build_prefix = None
 
-if not home_was_set and real_executable_dir and not py_setpath:
+if ((not home_was_set and real_executable_dir and not py_setpath)
+        or config.get('_is_python_build', 0) > 0):
     # Detect a build marker and use it to infer prefix, exec_prefix,
     # stdlib_dir and the platstdlib_dir directories.
     try:
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 9d3d0cbddf0e5..542e46968ce56 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -1550,6 +1550,46 @@ static int test_init_setpythonhome(void)
 }
 
 
+static int test_init_is_python_build(void)
+{
+    // gh-91985: in-tree builds fail to check for build directory landmarks
+    // under the effect of 'home' or PYTHONHOME environment variable.
+    char *env = getenv("TESTHOME");
+    if (!env) {
+        error("missing TESTHOME env var");
+        return 1;
+    }
+    wchar_t *home = Py_DecodeLocale(env, NULL);
+    if (home == NULL) {
+        error("failed to decode TESTHOME");
+        return 1;
+    }
+
+    PyConfig config;
+    _PyConfig_InitCompatConfig(&config);
+    config_set_program_name(&config);
+    config_set_string(&config, &config.home, home);
+    PyMem_RawFree(home);
+    putenv("TESTHOME=");
+
+    // Use an impossible value so we can detect whether it isn't updated
+    // during initialization.
+    config._is_python_build = INT_MAX;
+    env = getenv("NEGATIVE_ISPYTHONBUILD");
+    if (env && strcmp(env, "0") != 0) {
+        config._is_python_build++;
+    }
+    init_from_config_clear(&config);
+    Py_Finalize();
+    // Second initialization
+    config._is_python_build = -1;
+    init_from_config_clear(&config);
+    dump_config();  // home and _is_python_build are cached in _Py_path_config
+    Py_Finalize();
+    return 0;
+}
+
+
 static int test_init_warnoptions(void)
 {
     putenv("PYTHONWARNINGS=ignore:::env1,ignore:::env2");
@@ -1965,6 +2005,7 @@ static struct TestCase TestCases[] = {
     {"test_init_setpath", test_init_setpath},
     {"test_init_setpath_config", test_init_setpath_config},
     {"test_init_setpythonhome", test_init_setpythonhome},
+    {"test_init_is_python_build", test_init_is_python_build},
     {"test_init_warnoptions", test_init_warnoptions},
     {"test_init_set_config", test_init_set_config},
     {"test_run_main", test_run_main},
diff --git a/Python/pathconfig.c b/Python/pathconfig.c
index 4271928571fa1..69b7e10a3b028 100644
--- a/Python/pathconfig.c
+++ b/Python/pathconfig.c
@@ -36,10 +36,11 @@ typedef struct _PyPathConfig {
     wchar_t *program_name;
     /* Set by Py_SetPythonHome() or PYTHONHOME environment variable */
     wchar_t *home;
+    int _is_python_build;
 } _PyPathConfig;
 
 #  define _PyPathConfig_INIT \
-      {.module_search_path = NULL}
+      {.module_search_path = NULL, ._is_python_build = 0}
 
 
 _PyPathConfig _Py_path_config = _PyPathConfig_INIT;
@@ -72,6 +73,7 @@ _PyPathConfig_ClearGlobal(void)
     CLEAR(calculated_module_search_path);
     CLEAR(program_name);
     CLEAR(home);
+    _Py_path_config._is_python_build = 0;
 
 #undef CLEAR
 
@@ -99,15 +101,25 @@ _PyPathConfig_ReadGlobal(PyConfig *config)
         } \
     } while (0)
 
+#define COPY_INT(ATTR) \
+    do { \
+        assert(_Py_path_config.ATTR >= 0); \
+        if ((_Py_path_config.ATTR >= 0) && (config->ATTR <= 0)) { \
+            config->ATTR = _Py_path_config.ATTR; \
+        } \
+    } while (0)
+
     COPY(prefix);
     COPY(exec_prefix);
     COPY(stdlib_dir);
     COPY(program_name);
     COPY(home);
     COPY2(executable, program_full_path);
+    COPY_INT(_is_python_build);
     // module_search_path must be initialised - not read
 #undef COPY
 #undef COPY2
+#undef COPY_INT
 
 done:
     return status;
@@ -137,14 +149,23 @@ _PyPathConfig_UpdateGlobal(const PyConfig *config)
         } \
     } while (0)
 
+#define COPY_INT(ATTR) \
+    do { \
+        if (config->ATTR > 0) { \
+            _Py_path_config.ATTR = config->ATTR; \
+        } \
+    } while (0)
+
     COPY(prefix);
     COPY(exec_prefix);
     COPY(stdlib_dir);
     COPY(program_name);
     COPY(home);
     COPY2(program_full_path, executable);
+    COPY_INT(_is_python_build);
 #undef COPY
 #undef COPY2
+#undef COPY_INT
 
     PyMem_RawFree(_Py_path_config.module_search_path);
     _Py_path_config.module_search_path = NULL;



More information about the Python-checkins mailing list