Skip to content

Commit a9b6788

Browse files
authored
gh-144278: Enable overriding sys.implementation's name and cache_tag when building sysmodule.c (GH-144293)
Changing the values requires forking and patching, which is intentional. Simply rebuilding from source does not change the implementation enough to justify changing these values - they would still be `cpython` and compatible with existing `.pyc` files. But people who maintain forks are better served by being able to easily override these values in a place that can be forward-ported reliably.
1 parent 30cfe6e commit a9b6788

29 files changed

+305
-189
lines changed

Lib/compileall.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,14 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
165165
stripdir = os.fspath(stripdir) if stripdir is not None else None
166166
name = os.path.basename(fullname)
167167

168+
# Without a cache_tag, we can only create legacy .pyc files. None of our
169+
# callers seem to expect this, so the best we can do is fail without raising
170+
if not legacy and sys.implementation.cache_tag is None:
171+
if not quiet:
172+
print("No cache tag is available to generate .pyc path for",
173+
repr(fullname))
174+
return False
175+
168176
dfile = None
169177

170178
if ddir is not None:

Lib/ensurepip/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
177177
args += ["--user"]
178178
if verbosity:
179179
args += ["-" + "v" * verbosity]
180+
if sys.implementation.cache_tag is None:
181+
args += ["--no-compile"]
180182

181183
return _run_pip([*args, "pip"], [os.fsdecode(tmp_wheel_path)])
182184

Lib/py_compile.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,10 @@ def main():
194194
else:
195195
filenames = args.filenames
196196
for filename in filenames:
197+
cfilename = (None if sys.implementation.cache_tag
198+
else f"{filename.rpartition('.')[0]}.pyc")
197199
try:
198-
compile(filename, doraise=True)
200+
compile(filename, cfilename, doraise=True)
199201
except PyCompileError as error:
200202
if args.quiet:
201203
parser.exit(1)

Lib/test/support/import_helper.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import importlib.machinery
55
import importlib.util
66
import os
7+
import py_compile
78
import shutil
89
import sys
910
import textwrap
@@ -49,20 +50,31 @@ def forget(modname):
4950
# combinations of PEP 3147/488 and legacy pyc files.
5051
unlink(source + 'c')
5152
for opt in ('', 1, 2):
52-
unlink(importlib.util.cache_from_source(source, optimization=opt))
53+
try:
54+
unlink(importlib.util.cache_from_source(source, optimization=opt))
55+
except NotImplementedError:
56+
pass
5357

5458

55-
def make_legacy_pyc(source):
59+
def make_legacy_pyc(source, allow_compile=False):
5660
"""Move a PEP 3147/488 pyc file to its legacy pyc location.
5761
5862
:param source: The file system path to the source file. The source file
59-
does not need to exist, however the PEP 3147/488 pyc file must exist.
63+
does not need to exist, however the PEP 3147/488 pyc file must exist or
64+
allow_compile must be set.
65+
:param allow_compile: If True, uses py_compile to create a .pyc if it does
66+
not exist. This should be passed as True if cache_tag may be None.
6067
:return: The file system path to the legacy pyc file.
6168
"""
62-
pyc_file = importlib.util.cache_from_source(source)
6369
assert source.endswith('.py')
6470
legacy_pyc = source + 'c'
65-
shutil.move(pyc_file, legacy_pyc)
71+
try:
72+
pyc_file = importlib.util.cache_from_source(source)
73+
shutil.move(pyc_file, legacy_pyc)
74+
except (FileNotFoundError, NotImplementedError):
75+
if not allow_compile:
76+
raise
77+
py_compile.compile(source, legacy_pyc, doraise=True)
6678
return legacy_pyc
6779

6880

Lib/test/test_argparse.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import io
88
import operator
99
import os
10-
import py_compile
1110
import shutil
1211
import stat
1312
import sys
@@ -7162,9 +7161,8 @@ def make_script(self, dirname, basename, *, compiled=False):
71627161
script_name = script_helper.make_script(dirname, basename, self.source)
71637162
if not compiled:
71647163
return script_name
7165-
py_compile.compile(script_name, doraise=True)
7164+
pyc_file = import_helper.make_legacy_pyc(script_name, allow_compile=True)
71667165
os.remove(script_name)
7167-
pyc_file = import_helper.make_legacy_pyc(script_name)
71687166
return pyc_file
71697167

71707168
def make_zip_script(self, script_name, name_in_zip=None):

Lib/test/test_capi/test_import.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,10 @@ def check_executecode_pathnames(self, execute_code_func, object=False):
289289
self.check_executecodemodule(execute_code_func, NULL, pathname)
290290

291291
# Test NULL pathname and non-NULL cpathname
292-
pyc_filename = importlib.util.cache_from_source(__file__)
292+
try:
293+
pyc_filename = importlib.util.cache_from_source(__file__)
294+
except NotImplementedError:
295+
return
293296
py_filename = importlib.util.source_from_cache(pyc_filename)
294297
origin = self.check_executecodemodule(execute_code_func, NULL, pyc_filename)
295298
if not object:

Lib/test/test_cmd_line_script.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,8 @@ def test_script_abspath(self):
240240
def test_script_compiled(self):
241241
with os_helper.temp_dir() as script_dir:
242242
script_name = _make_test_script(script_dir, 'script')
243-
py_compile.compile(script_name, doraise=True)
243+
pyc_file = import_helper.make_legacy_pyc(script_name, allow_compile=True)
244244
os.remove(script_name)
245-
pyc_file = import_helper.make_legacy_pyc(script_name)
246245
self._check_script(pyc_file, pyc_file,
247246
pyc_file, script_dir, None,
248247
importlib.machinery.SourcelessFileLoader)
@@ -257,9 +256,8 @@ def test_directory(self):
257256
def test_directory_compiled(self):
258257
with os_helper.temp_dir() as script_dir:
259258
script_name = _make_test_script(script_dir, '__main__')
260-
py_compile.compile(script_name, doraise=True)
259+
pyc_file = import_helper.make_legacy_pyc(script_name, allow_compile=True)
261260
os.remove(script_name)
262-
pyc_file = import_helper.make_legacy_pyc(script_name)
263261
self._check_script(script_dir, pyc_file, script_dir,
264262
script_dir, '',
265263
importlib.machinery.SourcelessFileLoader)
@@ -279,8 +277,8 @@ def test_zipfile(self):
279277
def test_zipfile_compiled_timestamp(self):
280278
with os_helper.temp_dir() as script_dir:
281279
script_name = _make_test_script(script_dir, '__main__')
282-
compiled_name = py_compile.compile(
283-
script_name, doraise=True,
280+
compiled_name = script_name + 'c'
281+
py_compile.compile(script_name, compiled_name, doraise=True,
284282
invalidation_mode=py_compile.PycInvalidationMode.TIMESTAMP)
285283
zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name)
286284
self._check_script(zip_name, run_name, zip_name, zip_name, '',
@@ -289,8 +287,8 @@ def test_zipfile_compiled_timestamp(self):
289287
def test_zipfile_compiled_checked_hash(self):
290288
with os_helper.temp_dir() as script_dir:
291289
script_name = _make_test_script(script_dir, '__main__')
292-
compiled_name = py_compile.compile(
293-
script_name, doraise=True,
290+
compiled_name = script_name + 'c'
291+
py_compile.compile(script_name, compiled_name, doraise=True,
294292
invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH)
295293
zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name)
296294
self._check_script(zip_name, run_name, zip_name, zip_name, '',
@@ -299,8 +297,8 @@ def test_zipfile_compiled_checked_hash(self):
299297
def test_zipfile_compiled_unchecked_hash(self):
300298
with os_helper.temp_dir() as script_dir:
301299
script_name = _make_test_script(script_dir, '__main__')
302-
compiled_name = py_compile.compile(
303-
script_name, doraise=True,
300+
compiled_name = script_name + 'c'
301+
py_compile.compile(script_name, compiled_name, doraise=True,
304302
invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH)
305303
zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name)
306304
self._check_script(zip_name, run_name, zip_name, zip_name, '',
@@ -353,9 +351,8 @@ def test_package_compiled(self):
353351
pkg_dir = os.path.join(script_dir, 'test_pkg')
354352
make_pkg(pkg_dir)
355353
script_name = _make_test_script(pkg_dir, '__main__')
356-
compiled_name = py_compile.compile(script_name, doraise=True)
354+
pyc_file = import_helper.make_legacy_pyc(script_name, allow_compile=True)
357355
os.remove(script_name)
358-
pyc_file = import_helper.make_legacy_pyc(script_name)
359356
self._check_script(["-m", "test_pkg"], pyc_file,
360357
pyc_file, script_dir, 'test_pkg',
361358
importlib.machinery.SourcelessFileLoader,

Lib/test/test_compileall.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
from test.support.os_helper import FakePath
3434

3535

36+
if sys.implementation.cache_tag is None:
37+
raise unittest.SkipTest('requires sys.implementation.cache_tag is not None')
38+
39+
3640
def get_pyc(script, opt):
3741
if not opt:
3842
# Replace None and 0 with ''

Lib/test/test_ensurepip.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
import ensurepip._uninstall
1313

1414

15+
if sys.implementation.cache_tag is None:
16+
COMPILE_OPT = ["--no-compile"]
17+
else:
18+
COMPILE_OPT = []
19+
20+
1521
class TestPackages(unittest.TestCase):
1622
def touch(self, directory, filename):
1723
fullname = os.path.join(directory, filename)
@@ -85,7 +91,7 @@ def test_basic_bootstrapping(self):
8591
self.run_pip.assert_called_once_with(
8692
[
8793
"install", "--no-cache-dir", "--no-index", "--find-links",
88-
unittest.mock.ANY, "pip",
94+
unittest.mock.ANY, *COMPILE_OPT, "pip",
8995
],
9096
unittest.mock.ANY,
9197
)
@@ -99,7 +105,7 @@ def test_bootstrapping_with_root(self):
99105
self.run_pip.assert_called_once_with(
100106
[
101107
"install", "--no-cache-dir", "--no-index", "--find-links",
102-
unittest.mock.ANY, "--root", "/foo/bar/",
108+
unittest.mock.ANY, "--root", "/foo/bar/", *COMPILE_OPT,
103109
"pip",
104110
],
105111
unittest.mock.ANY,
@@ -111,7 +117,7 @@ def test_bootstrapping_with_user(self):
111117
self.run_pip.assert_called_once_with(
112118
[
113119
"install", "--no-cache-dir", "--no-index", "--find-links",
114-
unittest.mock.ANY, "--user", "pip",
120+
unittest.mock.ANY, "--user", *COMPILE_OPT, "pip",
115121
],
116122
unittest.mock.ANY,
117123
)
@@ -122,7 +128,7 @@ def test_bootstrapping_with_upgrade(self):
122128
self.run_pip.assert_called_once_with(
123129
[
124130
"install", "--no-cache-dir", "--no-index", "--find-links",
125-
unittest.mock.ANY, "--upgrade", "pip",
131+
unittest.mock.ANY, "--upgrade", *COMPILE_OPT, "pip",
126132
],
127133
unittest.mock.ANY,
128134
)
@@ -133,7 +139,7 @@ def test_bootstrapping_with_verbosity_1(self):
133139
self.run_pip.assert_called_once_with(
134140
[
135141
"install", "--no-cache-dir", "--no-index", "--find-links",
136-
unittest.mock.ANY, "-v", "pip",
142+
unittest.mock.ANY, "-v", *COMPILE_OPT, "pip",
137143
],
138144
unittest.mock.ANY,
139145
)
@@ -144,7 +150,7 @@ def test_bootstrapping_with_verbosity_2(self):
144150
self.run_pip.assert_called_once_with(
145151
[
146152
"install", "--no-cache-dir", "--no-index", "--find-links",
147-
unittest.mock.ANY, "-vv", "pip",
153+
unittest.mock.ANY, "-vv", *COMPILE_OPT, "pip",
148154
],
149155
unittest.mock.ANY,
150156
)
@@ -155,7 +161,7 @@ def test_bootstrapping_with_verbosity_3(self):
155161
self.run_pip.assert_called_once_with(
156162
[
157163
"install", "--no-cache-dir", "--no-index", "--find-links",
158-
unittest.mock.ANY, "-vvv", "pip",
164+
unittest.mock.ANY, "-vvv", *COMPILE_OPT, "pip",
159165
],
160166
unittest.mock.ANY,
161167
)
@@ -312,7 +318,7 @@ def test_basic_bootstrapping(self):
312318
self.run_pip.assert_called_once_with(
313319
[
314320
"install", "--no-cache-dir", "--no-index", "--find-links",
315-
unittest.mock.ANY, "pip",
321+
unittest.mock.ANY, *COMPILE_OPT, "pip",
316322
],
317323
unittest.mock.ANY,
318324
)

Lib/test/test_import/__init__.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777

7878

7979
skip_if_dont_write_bytecode = unittest.skipIf(
80-
sys.dont_write_bytecode,
80+
sys.dont_write_bytecode or sys.implementation.cache_tag is None,
8181
"test meaningful only when writing bytecode")
8282

8383

@@ -505,7 +505,7 @@ def test_module_with_large_stack(self, module='longlist'):
505505
try:
506506
# Compile & remove .py file; we only need .pyc.
507507
# Bytecode must be relocated from the PEP 3147 bytecode-only location.
508-
py_compile.compile(filename)
508+
make_legacy_pyc(filename, allow_compile=True)
509509
finally:
510510
unlink(filename)
511511

@@ -515,7 +515,6 @@ def test_module_with_large_stack(self, module='longlist'):
515515

516516
namespace = {}
517517
try:
518-
make_legacy_pyc(filename)
519518
# This used to crash.
520519
exec('import ' + module, None, namespace)
521520
finally:
@@ -1400,7 +1399,10 @@ def func():
14001399
"""
14011400
dir_name = os.path.abspath(TESTFN)
14021401
file_name = os.path.join(dir_name, module_name) + os.extsep + "py"
1403-
compiled_name = importlib.util.cache_from_source(file_name)
1402+
try:
1403+
compiled_name = importlib.util.cache_from_source(file_name)
1404+
except NotImplementedError:
1405+
compiled_name = None
14041406

14051407
def setUp(self):
14061408
self.sys_path = sys.path[:]
@@ -1418,7 +1420,8 @@ def tearDown(self):
14181420
else:
14191421
unload(self.module_name)
14201422
unlink(self.file_name)
1421-
unlink(self.compiled_name)
1423+
if self.compiled_name:
1424+
unlink(self.compiled_name)
14221425
rmtree(self.dir_name)
14231426

14241427
def import_module(self):
@@ -1437,6 +1440,8 @@ def test_basics(self):
14371440
self.assertEqual(mod.code_filename, self.file_name)
14381441
self.assertEqual(mod.func_filename, self.file_name)
14391442

1443+
@unittest.skipIf(sys.implementation.cache_tag is None,
1444+
'requires sys.implementation.cache_tag is not None')
14401445
def test_incorrect_code_name(self):
14411446
py_compile.compile(self.file_name, dfile="another_module.py")
14421447
mod = self.import_module()
@@ -1446,28 +1451,31 @@ def test_incorrect_code_name(self):
14461451

14471452
def test_module_without_source(self):
14481453
target = "another_module.py"
1449-
py_compile.compile(self.file_name, dfile=target)
1454+
pyc_file = self.file_name + 'c'
1455+
py_compile.compile(self.file_name, pyc_file, dfile=target)
14501456
os.remove(self.file_name)
1451-
pyc_file = make_legacy_pyc(self.file_name)
14521457
importlib.invalidate_caches()
14531458
mod = self.import_module()
14541459
self.assertEqual(mod.module_filename, pyc_file)
14551460
self.assertEqual(mod.code_filename, target)
14561461
self.assertEqual(mod.func_filename, target)
14571462

14581463
def test_foreign_code(self):
1459-
py_compile.compile(self.file_name)
1460-
with open(self.compiled_name, "rb") as f:
1464+
compiled_name = self.compiled_name or (self.file_name + 'c')
1465+
py_compile.compile(self.file_name, compiled_name)
1466+
with open(compiled_name, "rb") as f:
14611467
header = f.read(16)
14621468
code = marshal.load(f)
14631469
constants = list(code.co_consts)
14641470
foreign_code = importlib.import_module.__code__
14651471
pos = constants.index(1000)
14661472
constants[pos] = foreign_code
14671473
code = code.replace(co_consts=tuple(constants))
1468-
with open(self.compiled_name, "wb") as f:
1474+
with open(compiled_name, "wb") as f:
14691475
f.write(header)
14701476
marshal.dump(code, f)
1477+
if not self.compiled_name:
1478+
os.remove(self.file_name)
14711479
mod = self.import_module()
14721480
self.assertEqual(mod.constant.co_filename, foreign_code.co_filename)
14731481

0 commit comments

Comments
 (0)