Skip to content

Commit b8fe3bd

Browse files
gh-91985: Ensure in-tree builds override platstdlib_dir in every path calculation (GH-93641)
(cherry picked from commit 38af903) Co-authored-by: neonene <53406459+neonene@users.noreply.github.com>
1 parent 26329e4 commit b8fe3bd

File tree

4 files changed

+125
-2
lines changed

4 files changed

+125
-2
lines changed

Lib/test/test_embed.py

+60
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,66 @@ def test_init_setpythonhome(self):
12991299
self.check_all_configs("test_init_setpythonhome", config,
13001300
api=API_COMPAT, env=env)
13011301

1302+
def test_init_is_python_build_with_home(self):
1303+
# Test _Py_path_config._is_python_build configuration (gh-91985)
1304+
config = self._get_expected_config()
1305+
paths = config['config']['module_search_paths']
1306+
paths_str = os.path.pathsep.join(paths)
1307+
1308+
for path in paths:
1309+
if not os.path.isdir(path):
1310+
continue
1311+
if os.path.exists(os.path.join(path, 'os.py')):
1312+
home = os.path.dirname(path)
1313+
break
1314+
else:
1315+
self.fail(f"Unable to find home in {paths!r}")
1316+
1317+
prefix = exec_prefix = home
1318+
if MS_WINDOWS:
1319+
stdlib = os.path.join(home, "Lib")
1320+
# Because we are specifying 'home', module search paths
1321+
# are fairly static
1322+
expected_paths = [paths[0], stdlib, os.path.join(home, 'DLLs')]
1323+
else:
1324+
version = f'{sys.version_info.major}.{sys.version_info.minor}'
1325+
stdlib = os.path.join(home, sys.platlibdir, f'python{version}')
1326+
expected_paths = self.module_search_paths(prefix=home, exec_prefix=home)
1327+
1328+
config = {
1329+
'home': home,
1330+
'module_search_paths': expected_paths,
1331+
'prefix': prefix,
1332+
'base_prefix': prefix,
1333+
'exec_prefix': exec_prefix,
1334+
'base_exec_prefix': exec_prefix,
1335+
'pythonpath_env': paths_str,
1336+
'stdlib_dir': stdlib,
1337+
}
1338+
# The code above is taken from test_init_setpythonhome()
1339+
env = {'TESTHOME': home, 'PYTHONPATH': paths_str}
1340+
1341+
env['NEGATIVE_ISPYTHONBUILD'] = '1'
1342+
config['_is_python_build'] = 0
1343+
self.check_all_configs("test_init_is_python_build", config,
1344+
api=API_COMPAT, env=env)
1345+
1346+
env['NEGATIVE_ISPYTHONBUILD'] = '0'
1347+
config['_is_python_build'] = 1
1348+
exedir = os.path.dirname(sys.executable)
1349+
with open(os.path.join(exedir, 'pybuilddir.txt'), encoding='utf8') as f:
1350+
expected_paths[2] = os.path.normpath(
1351+
os.path.join(exedir, f'{f.read()}\n$'.splitlines()[0]))
1352+
if not MS_WINDOWS:
1353+
# PREFIX (default) is set when running in build directory
1354+
prefix = exec_prefix = sys.prefix
1355+
# stdlib calculation (/Lib) is not yet supported
1356+
expected_paths[0] = self.module_search_paths(prefix=prefix)[0]
1357+
config.update(prefix=prefix, base_prefix=prefix,
1358+
exec_prefix=exec_prefix, base_exec_prefix=exec_prefix)
1359+
self.check_all_configs("test_init_is_python_build", config,
1360+
api=API_COMPAT, env=env)
1361+
13021362
def copy_paths_by_env(self, config):
13031363
all_configs = self._get_expected_config()
13041364
paths = all_configs['config']['module_search_paths']

Modules/getpath.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,8 @@ def search_up(prefix, *landmarks, test=isfile):
461461

462462
build_prefix = None
463463

464-
if not home_was_set and real_executable_dir and not py_setpath:
464+
if ((not home_was_set and real_executable_dir and not py_setpath)
465+
or config.get('_is_python_build', 0) > 0):
465466
# Detect a build marker and use it to infer prefix, exec_prefix,
466467
# stdlib_dir and the platstdlib_dir directories.
467468
try:

Programs/_testembed.c

+41
Original file line numberDiff line numberDiff line change
@@ -1550,6 +1550,46 @@ static int test_init_setpythonhome(void)
15501550
}
15511551

15521552

1553+
static int test_init_is_python_build(void)
1554+
{
1555+
// gh-91985: in-tree builds fail to check for build directory landmarks
1556+
// under the effect of 'home' or PYTHONHOME environment variable.
1557+
char *env = getenv("TESTHOME");
1558+
if (!env) {
1559+
error("missing TESTHOME env var");
1560+
return 1;
1561+
}
1562+
wchar_t *home = Py_DecodeLocale(env, NULL);
1563+
if (home == NULL) {
1564+
error("failed to decode TESTHOME");
1565+
return 1;
1566+
}
1567+
1568+
PyConfig config;
1569+
_PyConfig_InitCompatConfig(&config);
1570+
config_set_program_name(&config);
1571+
config_set_string(&config, &config.home, home);
1572+
PyMem_RawFree(home);
1573+
putenv("TESTHOME=");
1574+
1575+
// Use an impossible value so we can detect whether it isn't updated
1576+
// during initialization.
1577+
config._is_python_build = INT_MAX;
1578+
env = getenv("NEGATIVE_ISPYTHONBUILD");
1579+
if (env && strcmp(env, "0") != 0) {
1580+
config._is_python_build++;
1581+
}
1582+
init_from_config_clear(&config);
1583+
Py_Finalize();
1584+
// Second initialization
1585+
config._is_python_build = -1;
1586+
init_from_config_clear(&config);
1587+
dump_config(); // home and _is_python_build are cached in _Py_path_config
1588+
Py_Finalize();
1589+
return 0;
1590+
}
1591+
1592+
15531593
static int test_init_warnoptions(void)
15541594
{
15551595
putenv("PYTHONWARNINGS=ignore:::env1,ignore:::env2");
@@ -1965,6 +2005,7 @@ static struct TestCase TestCases[] = {
19652005
{"test_init_setpath", test_init_setpath},
19662006
{"test_init_setpath_config", test_init_setpath_config},
19672007
{"test_init_setpythonhome", test_init_setpythonhome},
2008+
{"test_init_is_python_build", test_init_is_python_build},
19682009
{"test_init_warnoptions", test_init_warnoptions},
19692010
{"test_init_set_config", test_init_set_config},
19702011
{"test_run_main", test_run_main},

Python/pathconfig.c

+22-1
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ typedef struct _PyPathConfig {
3636
wchar_t *program_name;
3737
/* Set by Py_SetPythonHome() or PYTHONHOME environment variable */
3838
wchar_t *home;
39+
int _is_python_build;
3940
} _PyPathConfig;
4041

4142
# define _PyPathConfig_INIT \
42-
{.module_search_path = NULL}
43+
{.module_search_path = NULL, ._is_python_build = 0}
4344

4445

4546
_PyPathConfig _Py_path_config = _PyPathConfig_INIT;
@@ -72,6 +73,7 @@ _PyPathConfig_ClearGlobal(void)
7273
CLEAR(calculated_module_search_path);
7374
CLEAR(program_name);
7475
CLEAR(home);
76+
_Py_path_config._is_python_build = 0;
7577

7678
#undef CLEAR
7779

@@ -99,15 +101,25 @@ _PyPathConfig_ReadGlobal(PyConfig *config)
99101
} \
100102
} while (0)
101103

104+
#define COPY_INT(ATTR) \
105+
do { \
106+
assert(_Py_path_config.ATTR >= 0); \
107+
if ((_Py_path_config.ATTR >= 0) && (config->ATTR <= 0)) { \
108+
config->ATTR = _Py_path_config.ATTR; \
109+
} \
110+
} while (0)
111+
102112
COPY(prefix);
103113
COPY(exec_prefix);
104114
COPY(stdlib_dir);
105115
COPY(program_name);
106116
COPY(home);
107117
COPY2(executable, program_full_path);
118+
COPY_INT(_is_python_build);
108119
// module_search_path must be initialised - not read
109120
#undef COPY
110121
#undef COPY2
122+
#undef COPY_INT
111123

112124
done:
113125
return status;
@@ -137,14 +149,23 @@ _PyPathConfig_UpdateGlobal(const PyConfig *config)
137149
} \
138150
} while (0)
139151

152+
#define COPY_INT(ATTR) \
153+
do { \
154+
if (config->ATTR > 0) { \
155+
_Py_path_config.ATTR = config->ATTR; \
156+
} \
157+
} while (0)
158+
140159
COPY(prefix);
141160
COPY(exec_prefix);
142161
COPY(stdlib_dir);
143162
COPY(program_name);
144163
COPY(home);
145164
COPY2(program_full_path, executable);
165+
COPY_INT(_is_python_build);
146166
#undef COPY
147167
#undef COPY2
168+
#undef COPY_INT
148169

149170
PyMem_RawFree(_Py_path_config.module_search_path);
150171
_Py_path_config.module_search_path = NULL;

0 commit comments

Comments
 (0)