Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-91985: Ensure in-tree builds override platstdlib_dir in every path calculation #93641

Merged
merged 19 commits into from
Jun 16, 2022
42 changes: 42 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,48 @@ def test_init_setpythonhome(self):
self.check_all_configs("test_init_setpythonhome", config,
api=API_COMPAT, env=env)

@unittest.skipUnless(MS_WINDOWS, 'Windows only')
neonene marked this conversation as resolved.
Show resolved Hide resolved
def test_init_is_python_build_win32(self):
# Test _Py_path_config._is_python_build field (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
stdlib = os.path.join(home, "Lib")
expected_paths = [paths[0], stdlib, os.path.join(home, 'DLLs')]
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,
}
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
config['module_search_paths'][-1] = os.path.dirname(self.test_exe)
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']
Expand Down
3 changes: 2 additions & 1 deletion Modules/getpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
41 changes: 41 additions & 0 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -1550,6 +1550,46 @@ static int test_init_setpythonhome(void)
}


static int test_init_is_python_build(void)
{
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);
// Ensure 'home_was_set' is turned on in getpath.py
neonene marked this conversation as resolved.
Show resolved Hide resolved
config_set_string(&config, &config.home, home);
PyMem_RawFree(home);
putenv("TESTHOME=");

config._is_python_build = INT_MAX;
neonene marked this conversation as resolved.
Show resolved Hide resolved
env = getenv("NEGATIVE_ISPYTHONBUILD");
if (env) {
if (strcmp(env, "0") != 0) {
config._is_python_build++;
}
putenv("NEGATIVE_ISPYTHONBUILD=");
neonene marked this conversation as resolved.
Show resolved Hide resolved
}
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 from _Py_path_config
Py_Finalize();
return 0;
}


static int test_init_warnoptions(void)
{
putenv("PYTHONWARNINGS=ignore:::env1,ignore:::env2");
Expand Down Expand Up @@ -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},
Expand Down
23 changes: 22 additions & 1 deletion Python/pathconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down