From b8fe3bd1d4be5b7d8e9e20b3c36277117e8f6102 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 20 Jun 2022 11:37:27 -0700 Subject: 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@users.noreply.github.com> --- Lib/test/test_embed.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ Modules/getpath.py | 3 ++- Programs/_testembed.c | 41 ++++++++++++++++++++++++++++++++++ Python/pathconfig.c | 23 ++++++++++++++++++- 4 files changed, 125 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index a82e150..de3354b 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1299,6 +1299,66 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 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 47f075c..dceeed7 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -461,7 +461,8 @@ if not py_setpath and not home_was_set: 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 9d3d0cb..542e469 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 4271928..69b7e10 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; -- cgit v0.12