mirror of
https://github.com/python/cpython.git
synced 2025-10-16 03:40:58 +00:00
[3.12] gh-109853: Fix sys.path[0] For Subinterpreters (gh-109994) (gh-110701)
This change makes sure sys.path[0] is set properly for subinterpreters. Before, it wasn't getting set at all.
This change does not address the broader concerns from gh-109853.
(cherry-picked from commit a040a32ea2
)
This commit is contained in:
parent
592a849fdf
commit
313554457e
7 changed files with 26702 additions and 26487 deletions
File diff suppressed because it is too large
Load diff
|
@ -162,6 +162,11 @@ typedef struct pyruntimestate {
|
||||||
/* All the objects that are shared by the runtime's interpreters. */
|
/* All the objects that are shared by the runtime's interpreters. */
|
||||||
struct _Py_static_objects static_objects;
|
struct _Py_static_objects static_objects;
|
||||||
|
|
||||||
|
/* The value to use for sys.path[0] in new subinterpreters.
|
||||||
|
Normally this would be part of the PyConfig struct. However,
|
||||||
|
we cannot add it there in 3.12 since that's an ABI change. */
|
||||||
|
wchar_t *sys_path_0;
|
||||||
|
|
||||||
/* The following fields are here to avoid allocation during init.
|
/* The following fields are here to avoid allocation during init.
|
||||||
The data is exposed through _PyRuntimeState pointer fields.
|
The data is exposed through _PyRuntimeState pointer fields.
|
||||||
These fields should not be accessed directly outside of init.
|
These fields should not be accessed directly outside of init.
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -8,6 +11,7 @@ import time
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import import_helper
|
from test.support import import_helper
|
||||||
from test.support import threading_helper
|
from test.support import threading_helper
|
||||||
|
from test.support import os_helper
|
||||||
_interpreters = import_helper.import_module('_xxsubinterpreters')
|
_interpreters = import_helper.import_module('_xxsubinterpreters')
|
||||||
_channels = import_helper.import_module('_xxinterpchannels')
|
_channels = import_helper.import_module('_xxinterpchannels')
|
||||||
from test.support import interpreters
|
from test.support import interpreters
|
||||||
|
@ -487,6 +491,153 @@ class StressTests(TestBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StartupTests(TestBase):
|
||||||
|
|
||||||
|
# We want to ensure the initial state of subinterpreters
|
||||||
|
# matches expectations.
|
||||||
|
|
||||||
|
_subtest_count = 0
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def subTest(self, *args):
|
||||||
|
with super().subTest(*args) as ctx:
|
||||||
|
self._subtest_count += 1
|
||||||
|
try:
|
||||||
|
yield ctx
|
||||||
|
finally:
|
||||||
|
if self._debugged_in_subtest:
|
||||||
|
if self._subtest_count == 1:
|
||||||
|
# The first subtest adds a leading newline, so we
|
||||||
|
# compensate here by not printing a trailing newline.
|
||||||
|
print('### end subtest debug ###', end='')
|
||||||
|
else:
|
||||||
|
print('### end subtest debug ###')
|
||||||
|
self._debugged_in_subtest = False
|
||||||
|
|
||||||
|
def debug(self, msg, *, header=None):
|
||||||
|
if header:
|
||||||
|
self._debug(f'--- {header} ---')
|
||||||
|
if msg:
|
||||||
|
if msg.endswith(os.linesep):
|
||||||
|
self._debug(msg[:-len(os.linesep)])
|
||||||
|
else:
|
||||||
|
self._debug(msg)
|
||||||
|
self._debug('<no newline>')
|
||||||
|
self._debug('------')
|
||||||
|
else:
|
||||||
|
self._debug(msg)
|
||||||
|
|
||||||
|
_debugged = False
|
||||||
|
_debugged_in_subtest = False
|
||||||
|
def _debug(self, msg):
|
||||||
|
if not self._debugged:
|
||||||
|
print()
|
||||||
|
self._debugged = True
|
||||||
|
if self._subtest is not None:
|
||||||
|
if True:
|
||||||
|
if not self._debugged_in_subtest:
|
||||||
|
self._debugged_in_subtest = True
|
||||||
|
print('### start subtest debug ###')
|
||||||
|
print(msg)
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
def create_temp_dir(self):
|
||||||
|
import tempfile
|
||||||
|
tmp = tempfile.mkdtemp(prefix='test_interpreters_')
|
||||||
|
tmp = os.path.realpath(tmp)
|
||||||
|
self.addCleanup(os_helper.rmtree, tmp)
|
||||||
|
return tmp
|
||||||
|
|
||||||
|
def write_script(self, *path, text):
|
||||||
|
filename = os.path.join(*path)
|
||||||
|
dirname = os.path.dirname(filename)
|
||||||
|
if dirname:
|
||||||
|
os.makedirs(dirname, exist_ok=True)
|
||||||
|
with open(filename, 'w', encoding='utf-8') as outfile:
|
||||||
|
outfile.write(dedent(text))
|
||||||
|
return filename
|
||||||
|
|
||||||
|
@support.requires_subprocess()
|
||||||
|
def run_python(self, argv, *, cwd=None):
|
||||||
|
# This method is inspired by
|
||||||
|
# EmbeddingTestsMixin.run_embedded_interpreter() in test_embed.py.
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
if isinstance(argv, str):
|
||||||
|
argv = shlex.split(argv)
|
||||||
|
argv = [sys.executable, *argv]
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(
|
||||||
|
argv,
|
||||||
|
cwd=cwd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
self.debug(f'# cmd: {shlex.join(argv)}')
|
||||||
|
if isinstance(exc, FileNotFoundError) and not exc.filename:
|
||||||
|
if os.path.exists(argv[0]):
|
||||||
|
exists = 'exists'
|
||||||
|
else:
|
||||||
|
exists = 'does not exist'
|
||||||
|
self.debug(f'{argv[0]} {exists}')
|
||||||
|
raise # re-raise
|
||||||
|
assert proc.stderr == '' or proc.returncode != 0, proc.stderr
|
||||||
|
if proc.returncode != 0 and support.verbose:
|
||||||
|
self.debug(f'# python3 {shlex.join(argv[1:])} failed:')
|
||||||
|
self.debug(proc.stdout, header='stdout')
|
||||||
|
self.debug(proc.stderr, header='stderr')
|
||||||
|
self.assertEqual(proc.returncode, 0)
|
||||||
|
self.assertEqual(proc.stderr, '')
|
||||||
|
return proc.stdout
|
||||||
|
|
||||||
|
def test_sys_path_0(self):
|
||||||
|
# The main interpreter's sys.path[0] should be used by subinterpreters.
|
||||||
|
script = '''
|
||||||
|
import sys
|
||||||
|
from test.support import interpreters
|
||||||
|
|
||||||
|
orig = sys.path[0]
|
||||||
|
|
||||||
|
interp = interpreters.create()
|
||||||
|
interp.run(f"""if True:
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
print(json.dumps({{
|
||||||
|
'main': {orig!r},
|
||||||
|
'sub': sys.path[0],
|
||||||
|
}}, indent=4), flush=True)
|
||||||
|
""")
|
||||||
|
'''
|
||||||
|
# <tmp>/
|
||||||
|
# pkg/
|
||||||
|
# __init__.py
|
||||||
|
# __main__.py
|
||||||
|
# script.py
|
||||||
|
# script.py
|
||||||
|
cwd = self.create_temp_dir()
|
||||||
|
self.write_script(cwd, 'pkg', '__init__.py', text='')
|
||||||
|
self.write_script(cwd, 'pkg', '__main__.py', text=script)
|
||||||
|
self.write_script(cwd, 'pkg', 'script.py', text=script)
|
||||||
|
self.write_script(cwd, 'script.py', text=script)
|
||||||
|
|
||||||
|
cases = [
|
||||||
|
('script.py', cwd),
|
||||||
|
('-m script', cwd),
|
||||||
|
('-m pkg', cwd),
|
||||||
|
('-m pkg.script', cwd),
|
||||||
|
('-c "import script"', ''),
|
||||||
|
]
|
||||||
|
for argv, expected in cases:
|
||||||
|
with self.subTest(f'python3 {argv}'):
|
||||||
|
out = self.run_python(argv, cwd=cwd)
|
||||||
|
data = json.loads(out)
|
||||||
|
sp0_main, sp0_sub = data['main'], data['sub']
|
||||||
|
self.assertEqual(sp0_sub, sp0_main)
|
||||||
|
self.assertEqual(sp0_sub, expected)
|
||||||
|
|
||||||
|
|
||||||
class TestIsShareable(TestBase):
|
class TestIsShareable(TestBase):
|
||||||
|
|
||||||
def test_default_shareables(self):
|
def test_default_shareables(self):
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
``sys.path[0]`` is now set correctly for subinterpreters.
|
|
@ -559,6 +559,8 @@ pymain_run_python(int *exitcode)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(interp->runtime->sys_path_0 == NULL);
|
||||||
|
|
||||||
if (config->run_filename != NULL) {
|
if (config->run_filename != NULL) {
|
||||||
/* If filename is a package (ex: directory or ZIP file) which contains
|
/* If filename is a package (ex: directory or ZIP file) which contains
|
||||||
__main__.py, main_importer_path is set to filename and will be
|
__main__.py, main_importer_path is set to filename and will be
|
||||||
|
@ -574,24 +576,38 @@ pymain_run_python(int *exitcode)
|
||||||
// import readline and rlcompleter before script dir is added to sys.path
|
// import readline and rlcompleter before script dir is added to sys.path
|
||||||
pymain_import_readline(config);
|
pymain_import_readline(config);
|
||||||
|
|
||||||
|
PyObject *path0 = NULL;
|
||||||
if (main_importer_path != NULL) {
|
if (main_importer_path != NULL) {
|
||||||
if (pymain_sys_path_add_path0(interp, main_importer_path) < 0) {
|
path0 = Py_NewRef(main_importer_path);
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (!config->safe_path) {
|
else if (!config->safe_path) {
|
||||||
PyObject *path0 = NULL;
|
|
||||||
int res = _PyPathConfig_ComputeSysPath0(&config->argv, &path0);
|
int res = _PyPathConfig_ComputeSysPath0(&config->argv, &path0);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
else if (res == 0) {
|
||||||
if (res > 0) {
|
Py_CLEAR(path0);
|
||||||
if (pymain_sys_path_add_path0(interp, path0) < 0) {
|
}
|
||||||
|
}
|
||||||
|
if (path0 != NULL) {
|
||||||
|
wchar_t *wstr = PyUnicode_AsWideCharString(path0, NULL);
|
||||||
|
if (wstr == NULL) {
|
||||||
Py_DECREF(path0);
|
Py_DECREF(path0);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
PyMemAllocatorEx old_alloc;
|
||||||
|
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
|
||||||
|
interp->runtime->sys_path_0 = _PyMem_RawWcsdup(wstr);
|
||||||
|
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
|
||||||
|
PyMem_Free(wstr);
|
||||||
|
if (interp->runtime->sys_path_0 == NULL) {
|
||||||
Py_DECREF(path0);
|
Py_DECREF(path0);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
int res = pymain_sys_path_add_path0(interp, path0);
|
||||||
|
Py_DECREF(path0);
|
||||||
|
if (res < 0) {
|
||||||
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1200,6 +1200,32 @@ init_interp_main(PyThreadState *tstate)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!is_main_interp) {
|
||||||
|
// The main interpreter is handled in Py_Main(), for now.
|
||||||
|
wchar_t *sys_path_0 = interp->runtime->sys_path_0;
|
||||||
|
if (sys_path_0 != NULL) {
|
||||||
|
PyObject *path0 = PyUnicode_FromWideChar(sys_path_0, -1);
|
||||||
|
if (path0 == NULL) {
|
||||||
|
return _PyStatus_ERR("can't initialize sys.path[0]");
|
||||||
|
}
|
||||||
|
PyObject *sysdict = interp->sysdict;
|
||||||
|
if (sysdict == NULL) {
|
||||||
|
Py_DECREF(path0);
|
||||||
|
return _PyStatus_ERR("can't initialize sys.path[0]");
|
||||||
|
}
|
||||||
|
PyObject *sys_path = PyDict_GetItemWithError(sysdict, &_Py_ID(path));
|
||||||
|
if (sys_path == NULL) {
|
||||||
|
Py_DECREF(path0);
|
||||||
|
return _PyStatus_ERR("can't initialize sys.path[0]");
|
||||||
|
}
|
||||||
|
int res = PyList_Insert(sys_path, 0, path0);
|
||||||
|
Py_DECREF(path0);
|
||||||
|
if (res) {
|
||||||
|
return _PyStatus_ERR("can't initialize sys.path[0]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assert(!_PyErr_Occurred(tstate));
|
assert(!_PyErr_Occurred(tstate));
|
||||||
|
|
||||||
return _PyStatus_OK();
|
return _PyStatus_OK();
|
||||||
|
|
|
@ -524,6 +524,10 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef FREE_LOCK
|
#undef FREE_LOCK
|
||||||
|
if (runtime->sys_path_0 != NULL) {
|
||||||
|
PyMem_RawFree(runtime->sys_path_0);
|
||||||
|
runtime->sys_path_0 = NULL;
|
||||||
|
}
|
||||||
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
|
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue