Skip to content

Commit 8bf39b6

Browse files
authored
bpo-38234: Add test_init_setpath_config() to test_embed (GH-16402)
* Add test_embed.test_init_setpath_config(): test Py_SetPath() with PyConfig. * test_init_setpath() and test_init_setpythonhome() no longer call Py_SetProgramName(), but use the default program name. * _PyPathConfig: isolated, site_import and base_executable fields are now only available on Windows. * If executable is set explicitly in the configuration, ignore calculated base_executable: _PyConfig_InitPathConfig() copies executable to base_executable. * Complete path config documentation.
1 parent df69e75 commit 8bf39b6

File tree

6 files changed

+132
-52
lines changed

6 files changed

+132
-52
lines changed

Doc/c-api/init_config.rst

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -864,37 +864,46 @@ Path Configuration
864864
865865
:c:type:`PyConfig` contains multiple fields for the path configuration:
866866
867-
* Path configuration input fields:
867+
* Path configuration inputs:
868868
869869
* :c:member:`PyConfig.home`
870870
* :c:member:`PyConfig.pathconfig_warnings`
871871
* :c:member:`PyConfig.program_name`
872872
* :c:member:`PyConfig.pythonpath_env`
873+
* current working directory: to get absolute paths
874+
* ``PATH`` environment variable to get the program full path
875+
(from :c:member:`PyConfig.program_name`)
876+
* ``__PYVENV_LAUNCHER__`` environment variable
877+
* (Windows only) Application paths in the registry under
878+
"Software\Python\PythonCore\X.Y\PythonPath" of HKEY_CURRENT_USER and
879+
HKEY_LOCAL_MACHINE (where X.Y is the Python version).
873880
874881
* Path configuration output fields:
875882
883+
* :c:member:`PyConfig.base_exec_prefix`
876884
* :c:member:`PyConfig.base_executable`
885+
* :c:member:`PyConfig.base_prefix`
877886
* :c:member:`PyConfig.exec_prefix`
878887
* :c:member:`PyConfig.executable`
879-
* :c:member:`PyConfig.prefix`
880888
* :c:member:`PyConfig.module_search_paths_set`,
881889
:c:member:`PyConfig.module_search_paths`
890+
* :c:member:`PyConfig.prefix`
882891
883-
If at least one "output field" is not set, Python computes the path
892+
If at least one "output field" is not set, Python calculates the path
884893
configuration to fill unset fields. If
885894
:c:member:`~PyConfig.module_search_paths_set` is equal to 0,
886895
:c:member:`~PyConfig.module_search_paths` is overridden and
887896
:c:member:`~PyConfig.module_search_paths_set` is set to 1.
888897
889-
It is possible to completely ignore the function computing the default
898+
It is possible to completely ignore the function calculating the default
890899
path configuration by setting explicitly all path configuration output
891900
fields listed above. A string is considered as set even if it is non-empty.
892901
``module_search_paths`` is considered as set if
893902
``module_search_paths_set`` is set to 1. In this case, path
894903
configuration input fields are ignored as well.
895904
896905
Set :c:member:`~PyConfig.pathconfig_warnings` to 0 to suppress warnings when
897-
computing the path configuration (Unix only, Windows does not log any warning).
906+
calculating the path configuration (Unix only, Windows does not log any warning).
898907
899908
If :c:member:`~PyConfig.base_prefix` or :c:member:`~PyConfig.base_exec_prefix`
900909
fields are not set, they inherit their value from :c:member:`~PyConfig.prefix`

Include/internal/pycore_pathconfig.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,26 @@ typedef struct _PyPathConfig {
1919
wchar_t *program_name;
2020
/* Set by Py_SetPythonHome() or PYTHONHOME environment variable */
2121
wchar_t *home;
22+
#ifdef MS_WINDOWS
2223
/* isolated and site_import are used to set Py_IsolatedFlag and
2324
Py_NoSiteFlag flags on Windows in read_pth_file(). These fields
2425
are ignored when their value are equal to -1 (unset). */
2526
int isolated;
2627
int site_import;
2728
/* Set when a venv is detected */
2829
wchar_t *base_executable;
30+
#endif
2931
} _PyPathConfig;
3032

31-
#define _PyPathConfig_INIT \
32-
{.module_search_path = NULL, \
33-
.isolated = -1, \
34-
.site_import = -1}
33+
#ifdef MS_WINDOWS
34+
# define _PyPathConfig_INIT \
35+
{.module_search_path = NULL, \
36+
.isolated = -1, \
37+
.site_import = -1}
38+
#else
39+
# define _PyPathConfig_INIT \
40+
{.module_search_path = NULL}
41+
#endif
3542
/* Note: _PyPathConfig_INIT sets other fields to 0/NULL */
3643

3744
PyAPI_DATA(_PyPathConfig) _Py_path_config;

Lib/test/test_embed.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -635,16 +635,19 @@ def check_global_config(self, configs):
635635
self.assertEqual(configs['global_config'], expected)
636636

637637
def check_all_configs(self, testname, expected_config=None,
638-
expected_preconfig=None, modify_path_cb=None, stderr=None,
639-
*, api, env=None, ignore_stderr=False, cwd=None):
638+
expected_preconfig=None, modify_path_cb=None,
639+
stderr=None, *, api, preconfig_api=None,
640+
env=None, ignore_stderr=False, cwd=None):
640641
new_env = remove_python_envvars()
641642
if env is not None:
642643
new_env.update(env)
643644
env = new_env
644645

645-
if api == API_ISOLATED:
646+
if preconfig_api is None:
647+
preconfig_api = api
648+
if preconfig_api == API_ISOLATED:
646649
default_preconfig = self.PRE_CONFIG_ISOLATED
647-
elif api == API_PYTHON:
650+
elif preconfig_api == API_PYTHON:
648651
default_preconfig = self.PRE_CONFIG_PYTHON
649652
else:
650653
default_preconfig = self.PRE_CONFIG_COMPAT
@@ -1002,8 +1005,21 @@ def test_init_dont_parse_argv(self):
10021005
self.check_all_configs("test_init_dont_parse_argv", config, pre_config,
10031006
api=API_PYTHON)
10041007

1008+
def default_program_name(self, config):
1009+
if MS_WINDOWS:
1010+
program_name = 'python'
1011+
executable = self.test_exe
1012+
else:
1013+
program_name = 'python3'
1014+
executable = shutil.which(program_name) or ''
1015+
config.update({
1016+
'program_name': program_name,
1017+
'base_executable': executable,
1018+
'executable': executable,
1019+
})
1020+
10051021
def test_init_setpath(self):
1006-
# Test Py_SetProgramName() + Py_SetPath()
1022+
# Test Py_SetPath()
10071023
config = self._get_expected_config()
10081024
paths = config['config']['module_search_paths']
10091025

@@ -1014,11 +1030,38 @@ def test_init_setpath(self):
10141030
'exec_prefix': '',
10151031
'base_exec_prefix': '',
10161032
}
1033+
self.default_program_name(config)
10171034
env = {'TESTPATH': os.path.pathsep.join(paths)}
10181035
self.check_all_configs("test_init_setpath", config,
10191036
api=API_COMPAT, env=env,
10201037
ignore_stderr=True)
10211038

1039+
def test_init_setpath_config(self):
1040+
# Test Py_SetPath() with PyConfig
1041+
config = self._get_expected_config()
1042+
paths = config['config']['module_search_paths']
1043+
1044+
config = {
1045+
# set by Py_SetPath()
1046+
'module_search_paths': paths,
1047+
'prefix': '',
1048+
'base_prefix': '',
1049+
'exec_prefix': '',
1050+
'base_exec_prefix': '',
1051+
# overriden by PyConfig
1052+
'program_name': 'conf_program_name',
1053+
'base_executable': 'conf_executable',
1054+
'executable': 'conf_executable',
1055+
}
1056+
env = {'TESTPATH': os.path.pathsep.join(paths)}
1057+
# Py_SetPath() preinitialized Python using the compat API,
1058+
# so we need preconfig_api=API_COMPAT.
1059+
self.check_all_configs("test_init_setpath_config", config,
1060+
api=API_PYTHON,
1061+
preconfig_api=API_COMPAT,
1062+
env=env,
1063+
ignore_stderr=True)
1064+
10221065
def module_search_paths(self, prefix=None, exec_prefix=None):
10231066
config = self._get_expected_config()
10241067
if prefix is None:
@@ -1067,8 +1110,7 @@ def tmpdir_with_python(self):
10671110
yield tmpdir
10681111

10691112
def test_init_setpythonhome(self):
1070-
# Test Py_SetPythonHome(home) + PYTHONPATH env var
1071-
# + Py_SetProgramName()
1113+
# Test Py_SetPythonHome(home) with PYTHONPATH env var
10721114
config = self._get_expected_config()
10731115
paths = config['config']['module_search_paths']
10741116
paths_str = os.path.pathsep.join(paths)
@@ -1095,7 +1137,8 @@ def test_init_setpythonhome(self):
10951137
'base_exec_prefix': exec_prefix,
10961138
'pythonpath_env': paths_str,
10971139
}
1098-
env = {'TESTHOME': home, 'TESTPATH': paths_str}
1140+
self.default_program_name(config)
1141+
env = {'TESTHOME': home, 'PYTHONPATH': paths_str}
10991142
self.check_all_configs("test_init_setpythonhome", config,
11001143
api=API_COMPAT, env=env)
11011144

PC/getpathp.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,11 +1099,11 @@ calculate_free(PyCalculatePath *calculate)
10991099
- __PYVENV_LAUNCHER__ environment variable
11001100
- GetModuleFileNameW(NULL): fully qualified path of the executable file of
11011101
the current process
1102-
- .pth configuration file
1102+
- ._pth configuration file
11031103
- pyvenv.cfg configuration file
11041104
- Registry key "Software\Python\PythonCore\X.Y\PythonPath"
1105-
of HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER where X.Y is the Python
1106-
version (major.minor).
1105+
of HKEY_CURRENT_USER and HKEY_LOCAL_MACHINE where X.Y is the Python
1106+
version.
11071107
11081108
Outputs, 'pathconfig' fields:
11091109

Programs/_testembed.c

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,8 +1425,6 @@ static int test_init_sys_add(void)
14251425

14261426
static int test_init_setpath(void)
14271427
{
1428-
Py_SetProgramName(PROGRAM_NAME);
1429-
14301428
char *env = getenv("TESTPATH");
14311429
if (!env) {
14321430
fprintf(stderr, "missing TESTPATH env var\n");
@@ -1448,23 +1446,35 @@ static int test_init_setpath(void)
14481446
}
14491447

14501448

1451-
static int mysetenv(const char *name, const char *value)
1449+
static int test_init_setpath_config(void)
14521450
{
1453-
size_t len = strlen(name) + 1 + strlen(value) + 1;
1454-
char *env = PyMem_RawMalloc(len);
1455-
if (env == NULL) {
1456-
fprintf(stderr, "out of memory\n");
1457-
return -1;
1451+
char *env = getenv("TESTPATH");
1452+
if (!env) {
1453+
fprintf(stderr, "missing TESTPATH env var\n");
1454+
return 1;
14581455
}
1459-
strcpy(env, name);
1460-
strcat(env, "=");
1461-
strcat(env, value);
1456+
wchar_t *path = Py_DecodeLocale(env, NULL);
1457+
if (path == NULL) {
1458+
fprintf(stderr, "failed to decode TESTPATH\n");
1459+
return 1;
1460+
}
1461+
Py_SetPath(path);
1462+
PyMem_RawFree(path);
1463+
putenv("TESTPATH=");
14621464

1463-
putenv(env);
1465+
PyStatus status;
1466+
PyConfig config;
14641467

1465-
/* Don't call PyMem_RawFree(env), but leak env memory block:
1466-
putenv() does not copy the string. */
1468+
status = PyConfig_InitPythonConfig(&config);
1469+
if (PyStatus_Exception(status)) {
1470+
Py_ExitStatusException(status);
1471+
}
1472+
config_set_string(&config, &config.program_name, L"conf_program_name");
1473+
config_set_string(&config, &config.executable, L"conf_executable");
1474+
init_from_config_clear(&config);
14671475

1476+
dump_config();
1477+
Py_Finalize();
14681478
return 0;
14691479
}
14701480

@@ -1485,19 +1495,6 @@ static int test_init_setpythonhome(void)
14851495
PyMem_RawFree(home);
14861496
putenv("TESTHOME=");
14871497

1488-
char *path = getenv("TESTPATH");
1489-
if (!path) {
1490-
fprintf(stderr, "missing TESTPATH env var\n");
1491-
return 1;
1492-
}
1493-
1494-
if (mysetenv("PYTHONPATH", path) < 0) {
1495-
return 1;
1496-
}
1497-
putenv("TESTPATH=");
1498-
1499-
Py_SetProgramName(PROGRAM_NAME);
1500-
15011498
Py_Initialize();
15021499
dump_config();
15031500
Py_Finalize();
@@ -1642,6 +1639,7 @@ static struct TestCase TestCases[] = {
16421639
{"test_init_main", test_init_main},
16431640
{"test_init_sys_add", test_init_sys_add},
16441641
{"test_init_setpath", test_init_setpath},
1642+
{"test_init_setpath_config", test_init_setpath_config},
16451643
{"test_init_setpythonhome", test_init_setpythonhome},
16461644
{"test_run_main", test_run_main},
16471645

Python/pathconfig.c

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ pathconfig_clear(_PyPathConfig *config)
5858
CLEAR(config->module_search_path);
5959
CLEAR(config->program_name);
6060
CLEAR(config->home);
61+
#ifdef MS_WINDOWS
6162
CLEAR(config->base_executable);
63+
#endif
64+
6265
#undef CLEAR
6366

6467
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
@@ -83,9 +86,11 @@ pathconfig_copy(_PyPathConfig *config, const _PyPathConfig *config2)
8386
COPY_ATTR(module_search_path);
8487
COPY_ATTR(program_name);
8588
COPY_ATTR(home);
89+
#ifdef MS_WINDOWS
8690
config->isolated = config2->isolated;
8791
config->site_import = config2->site_import;
8892
COPY_ATTR(base_executable);
93+
#endif
8994

9095
#undef COPY_ATTR
9196

@@ -189,12 +194,14 @@ pathconfig_set_from_config(_PyPathConfig *pathconfig, const PyConfig *config)
189194
} \
190195
}
191196

192-
COPY_CONFIG(base_executable, base_executable);
193197
COPY_CONFIG(program_full_path, executable);
194198
COPY_CONFIG(prefix, prefix);
195199
COPY_CONFIG(exec_prefix, exec_prefix);
196200
COPY_CONFIG(program_name, program_name);
197201
COPY_CONFIG(home, home);
202+
#ifdef MS_WINDOWS
203+
COPY_CONFIG(base_executable, base_executable);
204+
#endif
198205

199206
#undef COPY_CONFIG
200207

@@ -330,18 +337,32 @@ config_calculate_pathconfig(PyConfig *config)
330337
} \
331338
}
332339

340+
#ifdef MS_WINDOWS
341+
if (config->executable != NULL && config->base_executable == NULL) {
342+
/* If executable is set explicitly in the configuration,
343+
ignore calculated base_executable: _PyConfig_InitPathConfig()
344+
will copy executable to base_executable */
345+
}
346+
else {
347+
COPY_ATTR(base_executable, base_executable);
348+
}
349+
#endif
350+
333351
COPY_ATTR(program_full_path, executable);
334352
COPY_ATTR(prefix, prefix);
335353
COPY_ATTR(exec_prefix, exec_prefix);
336-
COPY_ATTR(base_executable, base_executable);
354+
337355
#undef COPY_ATTR
338356

357+
#ifdef MS_WINDOWS
358+
/* If a ._pth file is found: isolated and site_import are overriden */
339359
if (pathconfig.isolated != -1) {
340360
config->isolated = pathconfig.isolated;
341361
}
342362
if (pathconfig.site_import != -1) {
343363
config->site_import = pathconfig.site_import;
344364
}
365+
#endif
345366

346367
status = _PyStatus_OK();
347368
goto done;
@@ -360,9 +381,9 @@ _PyConfig_InitPathConfig(PyConfig *config)
360381
{
361382
/* Do we need to calculate the path? */
362383
if (!config->module_search_paths_set
363-
|| (config->executable == NULL)
364-
|| (config->prefix == NULL)
365-
|| (config->exec_prefix == NULL))
384+
|| config->executable == NULL
385+
|| config->prefix == NULL
386+
|| config->exec_prefix == NULL)
366387
{
367388
PyStatus status = config_calculate_pathconfig(config);
368389
if (_PyStatus_EXCEPTION(status)) {
@@ -442,7 +463,9 @@ pathconfig_global_init(void)
442463
assert(_Py_path_config.module_search_path != NULL);
443464
assert(_Py_path_config.program_name != NULL);
444465
/* home can be NULL */
466+
#ifdef MS_WINDOWS
445467
assert(_Py_path_config.base_executable != NULL);
468+
#endif
446469
}
447470

448471

0 commit comments

Comments
 (0)