Skip to content

Commit 8f023a2

Browse files
authored
bpo-40854: Allow overriding sys.platlibdir via PYTHONPLATLIBDIR env-var (GH-20605)
1 parent c6b292c commit 8f023a2

File tree

11 files changed

+82
-24
lines changed

11 files changed

+82
-24
lines changed

Doc/c-api/init_config.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,14 @@ PyConfig
436436
437437
:data:`sys.base_prefix`.
438438
439+
.. c:member:: wchar_t* platlibdir
440+
441+
:data:`sys.platlibdir`: platform library directory name, set at configure time
442+
by ``--with-platlibdir``, overrideable by the ``PYTHONPLATLIBDIR``
443+
environment variable.
444+
445+
.. versionadded:: 3.10
446+
439447
.. c:member:: int buffered_stdio
440448
441449
If equals to 0, enable unbuffered mode, making the stdout and stderr
@@ -884,6 +892,7 @@ Path Configuration
884892
* Path configuration inputs:
885893
886894
* :c:member:`PyConfig.home`
895+
* :c:member:`PyConfig.platlibdir`
887896
* :c:member:`PyConfig.pathconfig_warnings`
888897
* :c:member:`PyConfig.program_name`
889898
* :c:member:`PyConfig.pythonpath_env`

Doc/using/cmdline.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,14 @@ conflict.
538538
within a Python program as the variable :data:`sys.path`.
539539

540540

541+
.. envvar:: PYTHONPLATLIBDIR
542+
543+
If this is set to a non-empty string, it overrides the :data:`sys.platlibdir`
544+
value.
545+
546+
.. versionadded:: 3.10
547+
548+
541549
.. envvar:: PYTHONSTARTUP
542550

543551
If this is the name of a readable file, the Python commands in that file are

Include/cpython/initconfig.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ typedef struct {
385385
wchar_t *base_prefix; /* sys.base_prefix */
386386
wchar_t *exec_prefix; /* sys.exec_prefix */
387387
wchar_t *base_exec_prefix; /* sys.base_exec_prefix */
388+
wchar_t *platlibdir; /* sys.platlibdir */
388389

389390
/* --- Parameter only used by Py_Main() ---------- */
390391

Lib/test/test_embed.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
380380
'exec_prefix': GET_DEFAULT_CONFIG,
381381
'base_exec_prefix': GET_DEFAULT_CONFIG,
382382
'module_search_paths': GET_DEFAULT_CONFIG,
383+
'platlibdir': sys.platlibdir,
383384

384385
'site_import': 1,
385386
'bytes_warning': 0,
@@ -585,13 +586,14 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
585586
if value is self.GET_DEFAULT_CONFIG:
586587
expected[key] = config[key]
587588

588-
pythonpath_env = expected['pythonpath_env']
589-
if pythonpath_env is not None:
590-
paths = pythonpath_env.split(os.path.pathsep)
591-
expected['module_search_paths'] = [*paths, *expected['module_search_paths']]
592-
if modify_path_cb is not None:
593-
expected['module_search_paths'] = expected['module_search_paths'].copy()
594-
modify_path_cb(expected['module_search_paths'])
589+
if expected['module_search_paths'] is not self.IGNORE_CONFIG:
590+
pythonpath_env = expected['pythonpath_env']
591+
if pythonpath_env is not None:
592+
paths = pythonpath_env.split(os.path.pathsep)
593+
expected['module_search_paths'] = [*paths, *expected['module_search_paths']]
594+
if modify_path_cb is not None:
595+
expected['module_search_paths'] = expected['module_search_paths'].copy()
596+
modify_path_cb(expected['module_search_paths'])
595597

596598
for key in self.COPY_PRE_CONFIG:
597599
if key not in expected_preconfig:
@@ -764,6 +766,8 @@ def test_init_from_config(self):
764766
'buffered_stdio': 0,
765767
'user_site_directory': 0,
766768
'faulthandler': 1,
769+
'platlibdir': 'my_platlibdir',
770+
'module_search_paths': self.IGNORE_CONFIG,
767771

768772
'check_hash_pycs_mode': 'always',
769773
'pathconfig_warnings': 0,
@@ -795,6 +799,8 @@ def test_init_compat_env(self):
795799
'user_site_directory': 0,
796800
'faulthandler': 1,
797801
'warnoptions': ['EnvVar'],
802+
'platlibdir': 'env_platlibdir',
803+
'module_search_paths': self.IGNORE_CONFIG,
798804
'_use_peg_parser': 0,
799805
}
800806
self.check_all_configs("test_init_compat_env", config, preconfig,
@@ -823,6 +829,8 @@ def test_init_python_env(self):
823829
'user_site_directory': 0,
824830
'faulthandler': 1,
825831
'warnoptions': ['EnvVar'],
832+
'platlibdir': 'env_platlibdir',
833+
'module_search_paths': self.IGNORE_CONFIG,
826834
'_use_peg_parser': 0,
827835
}
828836
self.check_all_configs("test_init_python_env", config, preconfig,

Makefile.pre.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,11 @@ Python/sysmodule.o: $(srcdir)/Python/sysmodule.c Makefile $(srcdir)/Include/pydt
811811
$(MULTIARCH_CPPFLAGS) \
812812
-o $@ $(srcdir)/Python/sysmodule.c
813813

814+
Python/initconfig.o: $(srcdir)/Python/initconfig.c
815+
$(CC) -c $(PY_CORE_CFLAGS) \
816+
-DPLATLIBDIR='"$(PLATLIBDIR)"' \
817+
-o $@ $(srcdir)/Python/initconfig.c
818+
814819
$(IO_OBJS): $(IO_H)
815820

816821
.PHONY: regen-grammar
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow overriding :data:`sys.platlibdir` via a new :envvar:`PYTHONPLATLIBDIR` environment variable.

Misc/python.man

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ inserted in the path in front of $PYTHONPATH.
413413
The search path can be manipulated from within a Python program as the
414414
variable
415415
.IR sys.path .
416+
.IP PYTHONPLATLIBDIR
417+
Override sys.platlibdir.
416418
.IP PYTHONSTARTUP
417419
If this is the name of a readable file, the Python commands in that
418420
file are executed before the first prompt is displayed in interactive

Modules/getpath.c

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ extern "C" {
105105

106106

107107
#if (!defined(PREFIX) || !defined(EXEC_PREFIX) \
108-
|| !defined(VERSION) || !defined(VPATH) || !defined(PLATLIBDIR))
109-
#error "PREFIX, EXEC_PREFIX, VERSION, VPATH and PLATLIBDIR macros must be defined"
108+
|| !defined(VERSION) || !defined(VPATH))
109+
#error "PREFIX, EXEC_PREFIX, VERSION and VPATH macros must be defined"
110110
#endif
111111

112112
#ifndef LANDMARK
@@ -128,7 +128,6 @@ typedef struct {
128128
wchar_t *pythonpath_macro; /* PYTHONPATH macro */
129129
wchar_t *prefix_macro; /* PREFIX macro */
130130
wchar_t *exec_prefix_macro; /* EXEC_PREFIX macro */
131-
wchar_t *platlibdir_macro; /* PLATLIBDIR macro */
132131
wchar_t *vpath_macro; /* VPATH macro */
133132

134133
wchar_t *lib_python; /* "lib/pythonX.Y" */
@@ -138,6 +137,7 @@ typedef struct {
138137

139138
int warnings;
140139
const wchar_t *pythonpath_env;
140+
const wchar_t *platlibdir;
141141

142142
wchar_t *argv0_path;
143143
wchar_t *zip_path;
@@ -811,7 +811,7 @@ calculate_exec_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
811811
}
812812

813813
/* <PLATLIBDIR> / "lib-dynload" */
814-
wchar_t *lib_dynload = joinpath2(calculate->platlibdir_macro,
814+
wchar_t *lib_dynload = joinpath2(calculate->platlibdir,
815815
L"lib-dynload");
816816
if (lib_dynload == NULL) {
817817
return _PyStatus_NO_MEMORY();
@@ -1297,7 +1297,7 @@ calculate_zip_path(PyCalculatePath *calculate)
12971297
PyStatus res;
12981298

12991299
/* Path: <PLATLIBDIR> / "pythonXY.zip" */
1300-
wchar_t *path = joinpath2(calculate->platlibdir_macro, L"python" Py_STRINGIFY(PY_MAJOR_VERSION) Py_STRINGIFY(PY_MINOR_VERSION) L".zip");
1300+
wchar_t *path = joinpath2(calculate->platlibdir, L"python" Py_STRINGIFY(PY_MAJOR_VERSION) Py_STRINGIFY(PY_MINOR_VERSION) L".zip");
13011301
if (path == NULL) {
13021302
return _PyStatus_NO_MEMORY();
13031303
}
@@ -1451,10 +1451,6 @@ calculate_init(PyCalculatePath *calculate, const PyConfig *config)
14511451
if (!calculate->vpath_macro) {
14521452
return DECODE_LOCALE_ERR("VPATH macro", len);
14531453
}
1454-
calculate->platlibdir_macro = Py_DecodeLocale(PLATLIBDIR, &len);
1455-
if (!calculate->platlibdir_macro) {
1456-
return DECODE_LOCALE_ERR("PLATLIBDIR macro", len);
1457-
}
14581454

14591455
calculate->lib_python = Py_DecodeLocale(PLATLIBDIR "/python" VERSION, &len);
14601456
if (!calculate->lib_python) {
@@ -1463,6 +1459,7 @@ calculate_init(PyCalculatePath *calculate, const PyConfig *config)
14631459

14641460
calculate->warnings = config->pathconfig_warnings;
14651461
calculate->pythonpath_env = config->pythonpath_env;
1462+
calculate->platlibdir = config->platlibdir;
14661463

14671464
return _PyStatus_OK();
14681465
}
@@ -1475,7 +1472,6 @@ calculate_free(PyCalculatePath *calculate)
14751472
PyMem_RawFree(calculate->prefix_macro);
14761473
PyMem_RawFree(calculate->exec_prefix_macro);
14771474
PyMem_RawFree(calculate->vpath_macro);
1478-
PyMem_RawFree(calculate->platlibdir_macro);
14791475
PyMem_RawFree(calculate->lib_python);
14801476
PyMem_RawFree(calculate->path_env);
14811477
PyMem_RawFree(calculate->zip_path);

Programs/_testembed.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,13 @@ static int test_init_from_config(void)
548548
/* FIXME: test home */
549549
/* FIXME: test path config: module_search_path .. dll_path */
550550

551+
putenv("PYTHONPLATLIBDIR=env_platlibdir");
552+
status = PyConfig_SetBytesString(&config, &config.platlibdir, "my_platlibdir");
553+
if (PyStatus_Exception(status)) {
554+
PyConfig_Clear(&config);
555+
Py_ExitStatusException(status);
556+
}
557+
551558
putenv("PYTHONVERBOSE=0");
552559
Py_VerboseFlag = 0;
553560
config.verbose = 1;
@@ -668,6 +675,7 @@ static void set_most_env_vars(void)
668675
putenv("PYTHONFAULTHANDLER=1");
669676
putenv("PYTHONIOENCODING=iso8859-1:replace");
670677
putenv("PYTHONOLDPARSER=1");
678+
putenv("PYTHONPLATLIBDIR=env_platlibdir");
671679
}
672680

673681

Python/initconfig.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
# endif
2525
#endif
2626

27+
#ifndef PLATLIBDIR
28+
# error "PLATLIBDIR macro must be defined"
29+
#endif
30+
2731

2832
/* --- Command line options --------------------------------------- */
2933

@@ -110,6 +114,7 @@ PYTHONPATH : '%lc'-separated list of directories prefixed to the\n\
110114
static const char usage_5[] =
111115
"PYTHONHOME : alternate <prefix> directory (or <prefix>%lc<exec_prefix>).\n"
112116
" The default module search path uses %s.\n"
117+
"PYTHONPLATLIBDIR : override sys.platlibdir.\n"
113118
"PYTHONCASEOK : ignore case in 'import' statements (Windows).\n"
114119
"PYTHONUTF8: if set to 1, enable the UTF-8 mode.\n"
115120
"PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n"
@@ -588,6 +593,7 @@ PyConfig_Clear(PyConfig *config)
588593
CLEAR(config->base_prefix);
589594
CLEAR(config->exec_prefix);
590595
CLEAR(config->base_exec_prefix);
596+
CLEAR(config->platlibdir);
591597

592598
CLEAR(config->filesystem_encoding);
593599
CLEAR(config->filesystem_errors);
@@ -824,6 +830,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
824830
COPY_WSTR_ATTR(base_prefix);
825831
COPY_WSTR_ATTR(exec_prefix);
826832
COPY_WSTR_ATTR(base_exec_prefix);
833+
COPY_WSTR_ATTR(platlibdir);
827834

828835
COPY_ATTR(site_import);
829836
COPY_ATTR(bytes_warning);
@@ -926,6 +933,7 @@ config_as_dict(const PyConfig *config)
926933
SET_ITEM_WSTR(base_prefix);
927934
SET_ITEM_WSTR(exec_prefix);
928935
SET_ITEM_WSTR(base_exec_prefix);
936+
SET_ITEM_WSTR(platlibdir);
929937
SET_ITEM_INT(site_import);
930938
SET_ITEM_INT(bytes_warning);
931939
SET_ITEM_INT(inspect);
@@ -1336,6 +1344,14 @@ config_read_env_vars(PyConfig *config)
13361344
}
13371345
}
13381346

1347+
if(config->platlibdir == NULL) {
1348+
status = CONFIG_GET_ENV_DUP(config, &config->platlibdir,
1349+
L"PYTHONPLATLIBDIR", "PYTHONPLATLIBDIR");
1350+
if (_PyStatus_EXCEPTION(status)) {
1351+
return status;
1352+
}
1353+
}
1354+
13391355
if (config->use_hash_seed < 0) {
13401356
status = config_init_hash_seed(config);
13411357
if (_PyStatus_EXCEPTION(status)) {
@@ -1731,6 +1747,14 @@ config_read(PyConfig *config)
17311747
}
17321748
}
17331749

1750+
if(config->platlibdir == NULL) {
1751+
status = CONFIG_SET_BYTES_STR(config, &config->platlibdir, PLATLIBDIR,
1752+
"PLATLIBDIR macro");
1753+
if (_PyStatus_EXCEPTION(status)) {
1754+
return status;
1755+
}
1756+
}
1757+
17341758
if (config->_install_importlib) {
17351759
status = _PyConfig_InitPathConfig(config);
17361760
if (_PyStatus_EXCEPTION(status)) {
@@ -2554,6 +2578,7 @@ PyConfig_Read(PyConfig *config)
25542578
assert(config->exec_prefix != NULL);
25552579
assert(config->base_exec_prefix != NULL);
25562580
}
2581+
assert(config->platlibdir != NULL);
25572582
assert(config->filesystem_encoding != NULL);
25582583
assert(config->filesystem_errors != NULL);
25592584
assert(config->stdio_encoding != NULL);
@@ -2704,6 +2729,7 @@ _Py_DumpPathConfig(PyThreadState *tstate)
27042729
DUMP_SYS(_base_executable);
27052730
DUMP_SYS(base_prefix);
27062731
DUMP_SYS(base_exec_prefix);
2732+
DUMP_SYS(platlibdir);
27072733
DUMP_SYS(executable);
27082734
DUMP_SYS(prefix);
27092735
DUMP_SYS(exec_prefix);

Python/sysmodule.c

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2922,13 +2922,7 @@ _PySys_InitMain(PyThreadState *tstate)
29222922
SET_SYS_FROM_WSTR("base_prefix", config->base_prefix);
29232923
SET_SYS_FROM_WSTR("exec_prefix", config->exec_prefix);
29242924
SET_SYS_FROM_WSTR("base_exec_prefix", config->base_exec_prefix);
2925-
{
2926-
PyObject *str = PyUnicode_FromString(PLATLIBDIR);
2927-
if (str == NULL) {
2928-
return -1;
2929-
}
2930-
SET_SYS_FROM_STRING("platlibdir", str);
2931-
}
2925+
SET_SYS_FROM_WSTR("platlibdir", config->platlibdir);
29322926

29332927
if (config->pycache_prefix != NULL) {
29342928
SET_SYS_FROM_WSTR("pycache_prefix", config->pycache_prefix);

0 commit comments

Comments
 (0)