8000 gh-110722: Add PYTHON_PRESITE to import a module before site.py is ru… · python/cpython@84b7e9e · GitHub
[go: up one dir, main page]

Skip to content

Commit 84b7e9e

Browse files
authored
gh-110722: Add PYTHON_PRESITE to import a module before site.py is run (#110769)
1 parent ab08ff7 commit 84b7e9e

File tree

8 files changed

+178
-7
lines changed

8 files changed

+178
-7
lines changed

Doc/c-api/init_config.rst

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ PyConfig
716716
717717
Set to ``1`` by the :envvar:`PYTHONDUMPREFS` environment variable.
718718
719-
Need a special build of Python with the ``Py_TRACE_REFS`` macro defined:
719+
Needs a special build of Python with the ``Py_TRACE_REFS`` macro defined:
720720
see the :option:`configure --with-trace-refs option <--with-trace-refs>`.
721721
722722
Default: ``0``.
@@ -1048,7 +1048,7 @@ PyConfig
10481048
Incremented by the :option:`-d` command line option. Set to the
10491049
:envvar:`PYTHONDEBUG` environment variable value.
10501050
1051-
Need a :ref:`debug build of Python <debug-build>` (the ``Py_DEBUG`` macro
1051+
Needs a :ref:`debug build of Python <debug-build>` (the ``Py_DEBUG`` macro
10521052
must be defined).
10531053
10541054
Default: ``0``.
@@ -1100,6 +1100,7 @@ PyConfig
11001100
11011101
Set by the :option:`-X pycache_prefix=PATH <-X>` command line option and
11021102
the :envvar:`PYTHONPYCACHEPREFIX` environment variable.
1103+
The command-line option takes precedence.
11031104
11041105
If ``NULL``, :data:`sys.pycache_prefix` is set to ``None``.
11051106
@@ -1143,13 +1144,27 @@ PyConfig
11431144
11441145
Default: ``NULL``.
11451146
1147+
.. c:member:: wchar_t* run_presite
1148+
1149+
``package.module`` path to module that should be imported before
1150+
``site.py`` is run.
1151+
1152+
Set by the :option:`-X presite=package.module <-X>` command-line
1153+
option and the :envvar:`PYTHON_PRESITE` environment variable.
1154+
The command-line option takes precedence.
1155+
1156+
Needs a :ref:`debug build of Python <debug-build>` (the ``Py_DEBUG`` macro
1157+
must be defined).
1158+
1159+
Default: ``NULL``.
1160+
11461161
.. c:member:: int show_ref_count
11471162
11481163
Show total reference count at exit (excluding immortal objects)?
11491164
11501165
Set to ``1`` by :option:`-X showrefcount <-X>` command line option.
11511166
1152-
Need a :ref:`debug build of Python <debug-build>` (the ``Py_REF_DEBUG``
1167+
Needs a :ref:`debug build of Python <debug-build>` (the ``Py_REF_DEBUG``
11531168
macro must be defined).
11541169
11551170
Default: ``0``.

Doc/using/cmdline.rst

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,12 @@ Miscellaneous options
552552
This option may be useful for users who need to limit CPU resources of a
553553
container system. See also :envvar:`PYTHON_CPU_COUNT`.
554554
If *n* is ``default``, nothing is overridden.
555+
* :samp:`-X presite={package.module}` specifies a module that should be
556+
imported before the :mod:`site` module is executed and before the
557+
:mod:`__main__` module exists. Therefore, the imported module isn't
558+
:mod:`__main__`. This can be used to execute code early during Python
559+
initialization. Python needs to be :ref:`built in debug mode <debug-build>`
560+
for this option to exist. See also :envvar:`PYTHON_PRESITE`.
555561

556562
It also allows passing arbitrary values and retrieving them through the
557563
:data:`sys._xoptions` dictionary.
@@ -602,6 +608,9 @@ Miscellaneous options
602608
.. versionadded:: 3.13
603609
The ``-X cpu_count`` option.
604610

611+
.. versionadded:: 3.13
612+
The ``-X presite`` option.
613+
605614

606615
Options you shouldn't use
607616
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1091,13 +1100,33 @@ Debug-mode variables
10911100
If set, Python will dump objects and reference counts still alive after
10921101
shutting down the interpreter.
10931102

1094-
Need Python configured with the :option:`--with-trace-refs` build option.
1103+
Needs Python configured with the :option:`--with-trace-refs` build option.
10951104

1096-
.. envvar:: PYTHONDUMPREFSFILE=FILENAME
1105+
.. envvar:: PYTHONDUMPREFSFILE
10971106

10981107
If set, Python will dump objects and reference counts still alive
1099-
after shutting down the interpreter into a file called *FILENAME*.
1108+
after shutting down the interpreter into a file under the path given
1109+
as the value to this environment variable.
11001110

1101-
Need Python configured with the :option:`--with-trace-refs` build option.
1111+
Needs Python configured with the :option:`--with-trace-refs` build option.
11021112

11031113
.. versionadded:: 3.11
1114+
1115+
.. envvar:: PYTHON_PRESITE
1116+
1117+
If this variable is set to a module, that module will be imported
1118+
early in the interpreter lifecycle, before the :mod:`site` module is
1119+
executed, and before the :mod:`__main__` module is created.
1120+
Therefore, the imported module is not treated as :mod:`__main__`.
1121+
1122+
This can be used to execute code early during Python initialization.
1123+
1124+
To import a submodule, use ``package.module`` as the value, like in
1125+
an import statement.
1126+
1127+
See also the :option:`-X presite <-X>` command-line option,
1128+
which takes precedence over this variable.
1129+
1130+
Needs Python configured with the :option:`--with-pydebug` build option.
1131+
1132+
.. versionadded:: 3.13

Doc/whatsnew/3.13.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,3 +1356,13 @@ removed, although there is currently no date scheduled for their removal.
13561356

13571357
* Remove undocumented ``PY_TIMEOUT_MAX`` constant from the limited C API.
13581358
(Contributed by Victor Stinner in :gh:`110014`.)
1359+
1360+
1361+
Regression Test Changes
1362+
=======================
1363+
1364+
* Python built with :file:`configure` :option:`--with-pydebug` now
1365+
supports a :option:`-X presite=package.module <-X>` command-line
1366+
option. If used, it specifies a module that should be imported early
1367+
in the lifecycle of the interpreter, before ``site.py`` is executed.
1368+
(Contributed by Łukasz Langa in :gh:`110769`.)

Include/cpython/initconfig.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@ typedef struct PyConfig {
225225
// If non-zero, turns on statistics gathering.
226226
int _pystats;
227227
#endif
228+
229+
#ifdef Py_DEBUG
230+
// If not empty, import a non-__main__ module before site.py is executed.
231+
// PYTHON_PRESITE=package.module or -X presite=package.module
232+
wchar_t *run_presite;
233+
#endif
228234
} PyConfig;
229235

230236
PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);

Lib/test/test_embed.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
515515
}
516516
if Py_STATS:
517517
CONFIG_COMPAT['_pystats'] = 0
518+
if support.P 10BC0 y_DEBUG:
519+
CONFIG_COMPAT['run_presite'] = None
518520
if MS_WINDOWS:
519521
CONFIG_COMPAT.update({
520522
'legacy_windows_stdio': 0,
@@ -1818,6 +1820,22 @@ def test_no_memleak(self):
18181820
self.assertEqual(refs, 0, out)
18191821
self.assertEqual(blocks, 0, out)
18201822

1823+
@unittest.skipUnless(support.Py_DEBUG,
1824+
'-X presite requires a Python debug build')
1825+
def test_presite(self):
1826+
cmd = [sys.executable, "-I", "-X", "presite=test.reperf", "-c", "print('cmd')"]
1827+
proc = subprocess.run(
1828+
cmd,
1829+
stdout=subprocess.PIPE,
1830+
stderr=subprocess.STDOUT,
1831+
text=True,
1832+
)
1833+
self.assertEqual(proc.returncode, 0)
1834+
out = proc.stdout.strip()
1835+
self.assertIn("10 times sub", out)
1836+
self.assertIn("CPU seconds", out)
1837+
self.assertIn("cmd", out)
1838+
18211839

18221840
class StdPrinterTests(EmbeddingTestsMixin, unittest.TestCase):
18231841
# Test PyStdPrinter_Type which is used by _PySys_SetPreliminaryStderr():
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :envvar:`PYTHON_PRESITE=package.module` to import a module early in the
2+
interpreter lifecycle before ``site.py`` is executed. Python needs to be
3+
:ref:`built in debug mode <debug-build>` for this option to exist.

Python/initconfig.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ static const PyConfigSpec PYCONFIG_SPEC[] = {
117117
SPEC(_is_python_build, UINT),
118118
#ifdef Py_STATS
119119
SPEC(_pystats, UINT),
120+
#endif
121+
#ifdef Py_DEBUG
122+
SPEC(run_presite, WSTR_OPT),
120123
#endif
121124
{NULL, 0, 0},
122125
};
@@ -241,6 +244,11 @@ The following implementation-specific options are available:\n\
241244
\n\
242245
-X pystats: Enable pystats collection at startup."
243246
#endif
247+
#ifdef Py_DEBUG
248+
"\n\
249+
\n\
250+
-X presite=package.module: import this module before site.py is run."
251+
#endif
244252
;
245253

246254
/* Envvars that don't have equivalent command-line options are listed first */
@@ -297,6 +305,9 @@ static const char usage_envvars[] =
297305
#ifdef Py_STATS
298306
"PYTHONSTATS : turns on statistics gathering\n"
299307
#endif
308+
#ifdef Py_DEBUG
309+
"PYTHON_PRESITE=pkg.mod : import this module before site.py is run\n"
310+
#endif
300311
;
301312

302313
#if defined(MS_WINDOWS)
@@ -790,6 +801,9 @@ PyConfig_Clear(PyConfig *config)
790801
CLEAR(config->run_module);
791802
CLEAR(config->run_filename);
792803
CLEAR(config->check_hash_pycs_mode);
804+
#ifdef Py_DEBUG
805+
CLEAR(config->run_presite);
806+
#endif
793807

794808
_PyWideStringList_Clear(&config->orig_argv);
795809
#undef CLEAR
@@ -1806,6 +1820,36 @@ config_init_pycache_prefix(PyConfig *config)
18061820
}
18071821

18081822

1823+
#ifdef Py_DEBUG
1824+
static PyStatus
1825+
config_init_run_presite(PyConfig *config)
1826+
{
1827+
assert(config->run_presite == NULL);
1828+
1829+
const wchar_t *xoption = config_get_xoption(config, L"presite");
1830+
if (xoption) {
1831+
const wchar_t *sep = wcschr(xoption, L'=');
1832+
if (sep && wcslen(sep) > 1) {
1833+
config->run_presite = _PyMem_RawWcsdup(sep + 1);
1834+
if (config->run_presite == NULL) {
1835+
return _PyStatus_NO_MEMORY();
1836+
}
1837+
}
1838+
else {
1839+
// PYTHON_PRESITE env var ignored
1840+
// if "-X presite=" option is used
1841+
config->run_presite = NULL;
1842+
}
1843+
return _PyStatus_OK();
1844+
}
1845+
1846+
return CONFIG_GET_ENV_DUP(config, &config->run_presite,
1847+
L"PYTHON_PRESITE",
1848+
"PYTHON_PRESITE");
1849+
}
1850+
#endif
1851+
1852+
18091853
static PyStatus
18101854
config_read_complex_options(PyConfig *config)
18111855
{
@@ -1861,6 +1905,16 @@ config_read_complex_options(PyConfig *config)
18611905
return status;
18621906
}
18631907
}
1908+
1909+
#ifdef Py_DEBUG
1910+
if (config->run_presite == NULL) {
1911+
status = config_init_run_presite(config);
1912+
if (_PyStatus_EXCEPTION(status)) {
1913+
return status;
1914+
}
1915+
}
1916+
#endif
1917+
18641918
return _PyStatus_OK();
18651919
}
18661920

Python/pylifecycle.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,38 @@ pyinit_main_reconfigure(PyThreadState *tstate)
10761076
}
10771077

10781078

1079+
#ifdef Py_DEBUG
1080+
static void
1081+
run_presite(PyThreadState *tstate)
1082+
{
1083+
PyInterpreterState *interp = tstate->interp;
1084+
const PyConfig *config = _PyInterpreterState_GetConfig(interp);
1085+
1086+
if (!config->run_presite) {
1087+
return;
1088+
}
1089+
1090+
PyObject *presite_modname = PyUnicode_FromWideChar(
1091+
config->run_presite,
1092+
wcslen(config->run_presite)
1093+
);
1094+
if (presite_modname == NULL) {
1095+
fprintf(stderr, "Could not convert pre-site module name to unicode\n");
1096+
Py_DECREF(presite_modname);
1097+
}
1098+
else {
1099+
PyObject *presite = PyImport_Import(presite_modname);
1100+
if (presite == NULL) {
1101+
fprintf(stderr, "pre-site import failed:\n");
1102+
_PyErr_Print(tstate);
1103+
}
1104+
Py_XDECREF(presite);
1105+
Py_DECREF(presite_modname);
1106+
}
1107+
}
1108+
#endif
1109+
1110+
10791111
static PyStatus
10801112
init_interp_main(PyThreadState *tstate)
10811113
{
@@ -1157,6 +1189,10 @@ init_interp_main(PyThreadState *tstate)
11571189
return status;
11581190
}
11591191

1192+
#ifdef Py_DEBUG
1193+
run_presite(tstate);
1194+
#endif
1195+
11601196
status = add_main_module(interp);
11611197
if (_PyStatus_EXCEPTION(status)) {
11621198
return status;

0 commit comments

Comments
 (0)
0