Skip to content

bpo-40854: Allow overriding sys.platlibdir via PYTHONPLATLIBDIR env-var #20605

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

Merged
merged 2 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,14 @@ PyConfig

:data:`sys.base_prefix`.

.. c:member:: wchar_t* platlibdir

:data:`sys.platlibdir`: platform library directory name, set at configure time
by ``--with-platlibdir``, overrideable by the ``PYTHONPLATLIBDIR``
environment variable.

.. versionadded:: 3.10

.. c:member:: int buffered_stdio

If equals to 0, enable unbuffered mode, making the stdout and stderr
Expand Down Expand Up @@ -884,6 +892,7 @@ Path Configuration
* Path configuration inputs:

* :c:member:`PyConfig.home`
* :c:member:`PyConfig.platlibdir`
* :c:member:`PyConfig.pathconfig_warnings`
* :c:member:`PyConfig.program_name`
* :c:member:`PyConfig.pythonpath_env`
Expand Down
8 changes: 8 additions & 0 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,14 @@ conflict.
within a Python program as the variable :data:`sys.path`.


.. envvar:: PYTHONPLATLIBDIR

If this is set to a non-empty string, it overrides the :data:`sys.platlibdir`
value.

.. versionadded:: 3.10


.. envvar:: PYTHONSTARTUP

If this is the name of a readable file, the Python commands in that file are
Expand Down
1 change: 1 addition & 0 deletions Include/cpython/initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ typedef struct {
wchar_t *base_prefix; /* sys.base_prefix */
wchar_t *exec_prefix; /* sys.exec_prefix */
wchar_t *base_exec_prefix; /* sys.base_exec_prefix */
wchar_t *platlibdir; /* sys.platlibdir */

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

Expand Down
22 changes: 15 additions & 7 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'exec_prefix': GET_DEFAULT_CONFIG,
'base_exec_prefix': GET_DEFAULT_CONFIG,
'module_search_paths': GET_DEFAULT_CONFIG,
'platlibdir': sys.platlibdir,

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

pythonpath_env = expected['pythonpath_env']
if pythonpath_env is not None:
paths = pythonpath_env.split(os.path.pathsep)
expected['module_search_paths'] = [*paths, *expected['module_search_paths']]
if modify_path_cb is not None:
expected['module_search_paths'] = expected['module_search_paths'].copy()
modify_path_cb(expected['module_search_paths'])
if expected['module_search_paths'] is not self.IGNORE_CONFIG:
pythonpath_env = expected['pythonpath_env']
if pythonpath_env is not None:
paths = pythonpath_env.split(os.path.pathsep)
expected['module_search_paths'] = [*paths, *expected['module_search_paths']]
if modify_path_cb is not None:
expected['module_search_paths'] = expected['module_search_paths'].copy()
modify_path_cb(expected['module_search_paths'])

for key in self.COPY_PRE_CONFIG:
if key not in expected_preconfig:
Expand Down Expand Up @@ -764,6 +766,8 @@ def test_init_from_config(self):
'buffered_stdio': 0,
'user_site_directory': 0,
'faulthandler': 1,
'platlibdir': 'my_platlibdir',
'module_search_paths': self.IGNORE_CONFIG,

'check_hash_pycs_mode': 'always',
'pathconfig_warnings': 0,
Expand Down Expand Up @@ -795,6 +799,8 @@ def test_init_compat_env(self):
'user_site_directory': 0,
'faulthandler': 1,
'warnoptions': ['EnvVar'],
'platlibdir': 'env_platlibdir',
'module_search_paths': self.IGNORE_CONFIG,
'_use_peg_parser': 0,
}
self.check_all_configs("test_init_compat_env", config, preconfig,
Expand Down Expand Up @@ -823,6 +829,8 @@ def test_init_python_env(self):
'user_site_directory': 0,
'faulthandler': 1,
'warnoptions': ['EnvVar'],
'platlibdir': 'env_platlibdir',
'module_search_paths': self.IGNORE_CONFIG,
'_use_peg_parser': 0,
}
self.check_all_configs("test_init_python_env", config, preconfig,
Expand Down
5 changes: 5 additions & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,11 @@ Python/sysmodule.o: $(srcdir)/Python/sysmodule.c Makefile $(srcdir)/Include/pydt
$(MULTIARCH_CPPFLAGS) \
-o $@ $(srcdir)/Python/sysmodule.c

Python/initconfig.o: $(srcdir)/Python/initconfig.c
$(CC) -c $(PY_CORE_CFLAGS) \
-DPLATLIBDIR='"$(PLATLIBDIR)"' \
-o $@ $(srcdir)/Python/initconfig.c

$(IO_OBJS): $(IO_H)

.PHONY: regen-grammar
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow overriding :data:`sys.platlibdir` via a new :envvar:`PYTHONPLATLIBDIR` environment variable.
2 changes: 2 additions & 0 deletions Misc/python.man
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,8 @@ inserted in the path in front of $PYTHONPATH.
The search path can be manipulated from within a Python program as the
variable
.IR sys.path .
.IP PYTHONPLATLIBDIR
Override sys.platlibdir.
.IP PYTHONSTARTUP
If this is the name of a readable file, the Python commands in that
file are executed before the first prompt is displayed in interactive
Expand Down
16 changes: 6 additions & 10 deletions Modules/getpath.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ extern "C" {


#if (!defined(PREFIX) || !defined(EXEC_PREFIX) \
|| !defined(VERSION) || !defined(VPATH) || !defined(PLATLIBDIR))
#error "PREFIX, EXEC_PREFIX, VERSION, VPATH and PLATLIBDIR macros must be defined"
|| !defined(VERSION) || !defined(VPATH))
#error "PREFIX, EXEC_PREFIX, VERSION and VPATH macros must be defined"
#endif

#ifndef LANDMARK
Expand All @@ -128,7 +128,6 @@ typedef struct {
wchar_t *pythonpath_macro; /* PYTHONPATH macro */
wchar_t *prefix_macro; /* PREFIX macro */
wchar_t *exec_prefix_macro; /* EXEC_PREFIX macro */
wchar_t *platlibdir_macro; /* PLATLIBDIR macro */
wchar_t *vpath_macro; /* VPATH macro */

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

int warnings;
const wchar_t *pythonpath_env;
const wchar_t *platlibdir;

wchar_t *argv0_path;
wchar_t *zip_path;
Expand Down Expand Up @@ -811,7 +811,7 @@ calculate_exec_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
}

/* <PLATLIBDIR> / "lib-dynload" */
wchar_t *lib_dynload = joinpath2(calculate->platlibdir_macro,
wchar_t *lib_dynload = joinpath2(calculate->platlibdir,
L"lib-dynload");
if (lib_dynload == NULL) {
return _PyStatus_NO_MEMORY();
Expand Down Expand Up @@ -1297,7 +1297,7 @@ calculate_zip_path(PyCalculatePath *calculate)
PyStatus res;

/* Path: <PLATLIBDIR> / "pythonXY.zip" */
wchar_t *path = joinpath2(calculate->platlibdir_macro, L"python" Py_STRINGIFY(PY_MAJOR_VERSION) Py_STRINGIFY(PY_MINOR_VERSION) L".zip");
wchar_t *path = joinpath2(calculate->platlibdir, L"python" Py_STRINGIFY(PY_MAJOR_VERSION) Py_STRINGIFY(PY_MINOR_VERSION) L".zip");
if (path == NULL) {
return _PyStatus_NO_MEMORY();
}
Expand Down Expand Up @@ -1451,10 +1451,6 @@ calculate_init(PyCalculatePath *calculate, const PyConfig *config)
if (!calculate->vpath_macro) {
return DECODE_LOCALE_ERR("VPATH macro", len);
}
calculate->platlibdir_macro = Py_DecodeLocale(PLATLIBDIR, &len);
if (!calculate->platlibdir_macro) {
return DECODE_LOCALE_ERR("PLATLIBDIR macro", len);
}

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

calculate->warnings = config->pathconfig_warnings;
calculate->pythonpath_env = config->pythonpath_env;
calculate->platlibdir = config->platlibdir;

return _PyStatus_OK();
}
Expand All @@ -1475,7 +1472,6 @@ calculate_free(PyCalculatePath *calculate)
PyMem_RawFree(calculate->prefix_macro);
PyMem_RawFree(calculate->exec_prefix_macro);
PyMem_RawFree(calculate->vpath_macro);
PyMem_RawFree(calculate->platlibdir_macro);
PyMem_RawFree(calculate->lib_python);
PyMem_RawFree(calculate->path_env);
PyMem_RawFree(calculate->zip_path);
Expand Down
8 changes: 8 additions & 0 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,13 @@ static int test_init_from_config(void)
/* FIXME: test home */
/* FIXME: test path config: module_search_path .. dll_path */

putenv("PYTHONPLATLIBDIR=env_platlibdir");
status = PyConfig_SetBytesString(&config, &config.platlibdir, "my_platlibdir");
if (PyStatus_Exception(status)) {
PyConfig_Clear(&config);
Py_ExitStatusException(status);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: one empty line is enough :-)

putenv("PYTHONVERBOSE=0");
Py_VerboseFlag = 0;
config.verbose = 1;
Expand Down Expand Up @@ -668,6 +675,7 @@ static void set_most_env_vars(void)
putenv("PYTHONFAULTHANDLER=1");
putenv("PYTHONIOENCODING=iso8859-1:replace");
putenv("PYTHONOLDPARSER=1");
putenv("PYTHONPLATLIBDIR=env_platlibdir");
}


Expand Down
26 changes: 26 additions & 0 deletions Python/initconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
# endif
#endif

#ifndef PLATLIBDIR
# error "PLATLIBDIR macro must be defined"
#endif


/* --- Command line options --------------------------------------- */

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

CLEAR(config->filesystem_encoding);
CLEAR(config->filesystem_errors);
Expand Down Expand Up @@ -824,6 +830,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
COPY_WSTR_ATTR(base_prefix);
COPY_WSTR_ATTR(exec_prefix);
COPY_WSTR_ATTR(base_exec_prefix);
COPY_WSTR_ATTR(platlibdir);

COPY_ATTR(site_import);
COPY_ATTR(bytes_warning);
Expand Down Expand Up @@ -926,6 +933,7 @@ config_as_dict(const PyConfig *config)
SET_ITEM_WSTR(base_prefix);
SET_ITEM_WSTR(exec_prefix);
SET_ITEM_WSTR(base_exec_prefix);
SET_ITEM_WSTR(platlibdir);
SET_ITEM_INT(site_import);
SET_ITEM_INT(bytes_warning);
SET_ITEM_INT(inspect);
Expand Down Expand Up @@ -1336,6 +1344,14 @@ config_read_env_vars(PyConfig *config)
}
}

if(config->platlibdir == NULL) {
status = CONFIG_GET_ENV_DUP(config, &config->platlibdir,
L"PYTHONPLATLIBDIR", "PYTHONPLATLIBDIR");
if (_PyStatus_EXCEPTION(status)) {
return status;
}
}

if (config->use_hash_seed < 0) {
status = config_init_hash_seed(config);
if (_PyStatus_EXCEPTION(status)) {
Expand Down Expand Up @@ -1731,6 +1747,14 @@ config_read(PyConfig *config)
}
}

if(config->platlibdir == NULL) {
status = CONFIG_SET_BYTES_STR(config, &config->platlibdir, PLATLIBDIR,
"PLATLIBDIR macro");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't notice the Makefile change to define PLATLIBDIR macro. Please add an explicit check to ensure that it's defined. See Modules/getpath.c. I sugges to add at the top of this file, after includes (on Windows, it's defined by PC/pyconfig.h which is included by Python.h):

#ifndef PLATLIBDIR
#  error "PLATLIBDIR macro must be defined"
#endif

if (_PyStatus_EXCEPTION(status)) {
return status;
}
}

if (config->_install_importlib) {
status = _PyConfig_InitPathConfig(config);
if (_PyStatus_EXCEPTION(status)) {
Expand Down Expand Up @@ -2554,6 +2578,7 @@ PyConfig_Read(PyConfig *config)
assert(config->exec_prefix != NULL);
assert(config->base_exec_prefix != NULL);
}
assert(config->platlibdir != NULL);
assert(config->filesystem_encoding != NULL);
assert(config->filesystem_errors != NULL);
assert(config->stdio_encoding != NULL);
Expand Down Expand Up @@ -2704,6 +2729,7 @@ _Py_DumpPathConfig(PyThreadState *tstate)
DUMP_SYS(_base_executable);
DUMP_SYS(base_prefix);
DUMP_SYS(base_exec_prefix);
DUMP_SYS(platlibdir);
DUMP_SYS(executable);
DUMP_SYS(prefix);
DUMP_SYS(exec_prefix);
Expand Down
8 changes: 1 addition & 7 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2922,13 +2922,7 @@ _PySys_InitMain(PyThreadState *tstate)
SET_SYS_FROM_WSTR("base_prefix", config->base_prefix);
SET_SYS_FROM_WSTR("exec_prefix", config->exec_prefix);
SET_SYS_FROM_WSTR("base_exec_prefix", config->base_exec_prefix);
{
PyObject *str = PyUnicode_FromString(PLATLIBDIR);
if (str == NULL) {
return -1;
}
SET_SYS_FROM_STRING("platlibdir", str);
}
SET_SYS_FROM_WSTR("platlibdir", config->platlibdir);

if (config->pycache_prefix != NULL) {
SET_SYS_FROM_WSTR("pycache_prefix", config->pycache_prefix);
Expand Down