Skip to content

Commit 5be98e5

Browse files
bpo-45873: Get rid of bootstrap_python (#29717)
Instead we use $(PYTHON_FOR_REGEN) .../deepfreeze.py with the frozen .h file as input, as we did for Windows in bpo-45850. We also get rid of the code that generates the .h files when make regen-frozen is run (i.e., .../make_frozen.py), and the MANIFEST file. Restore Python 3.8 and 3.9 as Windows host Python again Co-authored-by: Kumar Aditya <[email protected]>
1 parent ae1965c commit 5be98e5

File tree

8 files changed

+259
-431
lines changed

8 files changed

+259
-431
lines changed

Makefile.pre.in

+83-164
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Get rid of the ``_bootstrap_python`` build step. The deepfreeze.py script is now run using ``$(PYTHON_FOR_REGEN)`` which can be Python 3.7 or newer (on Windows, 3.8 or newer).

PCbuild/find_python.bat

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@
3131
@if "%_Py_EXTERNALS_DIR%"=="" (set _Py_EXTERNALS_DIR=%~dp0\..\externals)
3232

3333
@rem If we have Python in externals, use that one
34-
@if exist "%_Py_EXTERNALS_DIR%\pythonx86\tools\python.exe" ("%_Py_EXTERNALS_DIR%\pythonx86\tools\python.exe" -Ec "import sys; assert sys.version_info[:2] >= (3, 10)" >nul 2>nul) && (set PYTHON="%_Py_EXTERNALS_DIR%\pythonx86\tools\python.exe") && (set _Py_Python_Source=found in externals directory) && goto :found || rmdir /Q /S "%_Py_EXTERNALS_DIR%\pythonx86"
34+
@if exist "%_Py_EXTERNALS_DIR%\pythonx86\tools\python.exe" ("%_Py_EXTERNALS_DIR%\pythonx86\tools\python.exe" -Ec "import sys; assert sys.version_info[:2] >= (3, 8)" >nul 2>nul) && (set PYTHON="%_Py_EXTERNALS_DIR%\pythonx86\tools\python.exe") && (set _Py_Python_Source=found in externals directory) && goto :found || rmdir /Q /S "%_Py_EXTERNALS_DIR%\pythonx86"
3535

3636
@rem If HOST_PYTHON is recent enough, use that
37-
@if NOT "%HOST_PYTHON%"=="" @%HOST_PYTHON% -Ec "import sys; assert sys.version_info[:2] >= (3, 10)" >nul 2>nul && (set PYTHON="%HOST_PYTHON%") && (set _Py_Python_Source=found as HOST_PYTHON) && goto :found
37+
@if NOT "%HOST_PYTHON%"=="" @%HOST_PYTHON% -Ec "import sys; assert sys.version_info[:2] >= (3, 8)" >nul 2>nul && (set PYTHON="%HOST_PYTHON%") && (set _Py_Python_Source=found as HOST_PYTHON) && goto :found
3838

3939
@rem If py.exe finds a recent enough version, use that one
40-
@for %%p in (3.10) do @py -%%p -EV >nul 2>&1 && (set PYTHON=py -%%p) && (set _Py_Python_Source=found %%p with py.exe) && goto :found
40+
@for %%p in (3.10 3.9 3.8) do @py -%%p -EV >nul 2>&1 && (set PYTHON=py -%%p) && (set _Py_Python_Source=found %%p with py.exe) && goto :found
4141

4242
@if NOT exist "%_Py_EXTERNALS_DIR%" mkdir "%_Py_EXTERNALS_DIR%"
4343
@set _Py_NUGET=%NUGET%

Tools/scripts/deepfreeze.py

+46-41
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import re
88
import time
99
import types
10-
import typing
10+
from typing import Dict, FrozenSet, Tuple, TextIO
1111

1212
import umarshal
1313

@@ -42,13 +42,14 @@ def get_localsplus(code: types.CodeType):
4242

4343

4444
def get_localsplus_counts(code: types.CodeType,
45-
names: tuple[str, ...],
46-
kinds: bytes) -> tuple[int, int, int, int]:
45+
names: Tuple[str, ...],
46+
kinds: bytes) -> Tuple[int, int, int, int]:
4747
nlocals = 0
4848
nplaincellvars = 0
4949
ncellvars = 0
5050
nfreevars = 0
51-
for name, kind in zip(names, kinds, strict=True):
51+
assert len(names) == len(kinds)
52+
for name, kind in zip(names, kinds):
5253
if kind & CO_FAST_LOCAL:
5354
nlocals += 1
5455
if kind & CO_FAST_CELL:
@@ -71,7 +72,7 @@ def get_localsplus_counts(code: types.CodeType,
7172
PyUnicode_4BYTE_KIND = 4
7273

7374

74-
def analyze_character_width(s: str) -> tuple[int, bool]:
75+
def analyze_character_width(s: str) -> Tuple[int, bool]:
7576
maxchar = ' '
7677
for c in s:
7778
maxchar = max(maxchar, c)
@@ -86,12 +87,17 @@ def analyze_character_width(s: str) -> tuple[int, bool]:
8687
return kind, ascii
8788

8889

90+
def removesuffix(base: str, suffix: str) -> str:
91+
if base.endswith(suffix):
92+
return base[:len(base) - len(suffix)]
93+
return base
94+
8995
class Printer:
9096

91-
def __init__(self, file: typing.TextIO):
97+
def __init__(self, file: TextIO):
9298
self.level = 0
9399
self.file = file
94-
self.cache: dict[tuple[type, object], str] = {}
100+
self.cache: Dict[Tuple[type, object], str] = {}
95101
self.hits, self.misses = 0, 0
96102
self.patchups: list[str] = []
97103
self.write('#include "Python.h"')
@@ -231,7 +237,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
231237
# otherwise MSVC doesn't like it.
232238
self.write(f".co_consts = {co_consts},")
233239
self.write(f".co_names = {co_names},")
234-
self.write(f".co_firstinstr = (_Py_CODEUNIT *) {co_code.removesuffix('.ob_base.ob_base')}.ob_sval,")
240+
self.write(f".co_firstinstr = (_Py_CODEUNIT *) {removesuffix(co_code, '.ob_base.ob_base')}.ob_sval,")
235241
self.write(f".co_exceptiontable = {co_exceptiontable},")
236242
self.field(code, "co_flags")
237243
self.write(".co_warmup = QUICKENING_INITIAL_WARMUP_VALUE,")
@@ -259,7 +265,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
259265
self.write(f".co_freevars = {co_freevars},")
260266
return f"& {name}.ob_base"
261267

262-
def generate_tuple(self, name: str, t: tuple[object, ...]) -> str:
268+
def generate_tuple(self, name: str, t: Tuple[object, ...]) -> str:
263269
items = [self.generate(f"{name}_{i}", it) for i, it in enumerate(t)]
264270
self.write("static")
265271
with self.indent():
@@ -323,7 +329,7 @@ def generate_complex(self, name: str, z: complex) -> str:
323329
self.write(f".cval = {{ {z.real}, {z.imag} }},")
324330
return f"&{name}.ob_base"
325331

326-
def generate_frozenset(self, name: str, fs: frozenset[object]) -> str:
332+
def generate_frozenset(self, name: str, fs: FrozenSet[object]) -> str:
327333
ret = self.generate_tuple(name, tuple(sorted(fs)))
328334
self.write("// TODO: The above tuple should be a frozenset")
329335
return ret
@@ -336,34 +342,33 @@ def generate(self, name: str, obj: object) -> str:
336342
# print(f"Cache hit {key!r:.40}: {self.cache[key]!r:.40}")
337343
return self.cache[key]
338344
self.misses += 1
339-
match obj:
340-
case types.CodeType() | umarshal.Code() as code:
341-
val = self.generate_code(name, code)
342-
case tuple(t):
343-
val = self.generate_tuple(name, t)
344-
case str(s):
345-
val = self.generate_unicode(name, s)
346-
case bytes(b):
347-
val = self.generate_bytes(name, b)
348-
case True:
349-
return "Py_True"
350-
case False:
351-
return "Py_False"
352-
case int(i):
353-
val = self.generate_int(name, i)
354-
case float(x):
355-
val = self.generate_float(name, x)
356-
case complex() as z:
357-
val = self.generate_complex(name, z)
358-
case frozenset(fs):
359-
val = self.generate_frozenset(name, fs)
360-
case builtins.Ellipsis:
361-
return "Py_Ellipsis"
362-
case None:
363-
return "Py_None"
364-
case _:
365-
raise TypeError(
366-
f"Cannot generate code for {type(obj).__name__} object")
345+
if isinstance(obj, types.CodeType) or isinstance(obj, umarshal.Code):
346+
val = self.generate_code(name, obj)
347+
elif isinstance(obj, tuple):
348+
val = self.generate_tuple(name, obj)
349+
elif isinstance(obj, str):
350+
val = self.generate_unicode(name, obj)
351+
elif isinstance(obj, bytes):
352+
val = self.generate_bytes(name, obj)
353+
elif obj is True:
354+
return "Py_True"
355+
elif obj is False:
356+
return "Py_False"
357+
elif isinstance(obj, int):
358+
val = self.generate_int(name, obj)
359+
elif isinstance(obj, float):
360+
val = self.generate_float(name, obj)
361+
elif isinstance(obj, complex):
362+
val = self.generate_complex(name, obj)
363+
elif isinstance(obj, frozenset):
364+
val = self.generate_frozenset(name, obj)
365+
elif obj is builtins.Ellipsis:
366+
return "Py_Ellipsis"
367+
elif obj is None:
368+
return "Py_None"
369+
else:
370+
raise TypeError(
371+
f"Cannot generate code for {type(obj).__name__} object")
367372
# print(f"Cache store {key!r:.40}: {val!r:.40}")
368373
self.cache[key] = val
369374
return val
@@ -393,12 +398,12 @@ def decode_frozen_data(source: str) -> types.CodeType:
393398
del lines[0]
394399
while lines and re.match(FROZEN_DATA_LINE, lines[-1]) is None:
395400
del lines[-1]
396-
values: tuple[int, ...] = ast.literal_eval("".join(lines))
401+
values: Tuple[int, ...] = ast.literal_eval("".join(lines).strip())
397402
data = bytes(values)
398403
return umarshal.loads(data)
399404

400405

401-
def generate(source: str, filename: str, modname: str, file: typing.TextIO) -> None:
406+
def generate(source: str, filename: str, modname: str, file: TextIO) -> None:
402407
if is_frozen_header(source):
403408
code = decode_frozen_data(source)
404409
else:
@@ -439,7 +444,7 @@ def main() -> None:
439444
verbose = args.verbose
440445
with open(args.file, encoding="utf-8") as f:
441446
source = f.read()
442-
modname = args.module or os.path.basename(args.file).removesuffix(".py")
447+
modname = args.module or removesuffix(os.path.basename(args.file), ".py")
443448
output = args.output or modname + ".c"
444449
with open(output, "w", encoding="utf-8") as file:
445450
with report_time("generate"):

Tools/scripts/freeze_modules.py

+6-104
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,6 @@
2525
# need to be updated.
2626
MODULES_DIR = os.path.join(ROOT_DIR, 'Python', 'frozen_modules')
2727

28-
if sys.platform != "win32":
29-
TOOL = os.path.join(ROOT_DIR, 'Programs', '_freeze_module')
30-
if not os.path.isfile(TOOL):
31-
# When building out of the source tree, get the tool from directory
32-
# of the Python executable
33-
TOOL = os.path.dirname(sys.executable)
34-
TOOL = os.path.join(TOOL, 'Programs', '_freeze_module')
35-
TOOL = os.path.abspath(TOOL)
36-
if not os.path.isfile(TOOL):
37-
sys.exit("ERROR: missing _freeze_module")
38-
else:
39-
def find_tool():
40-
archs = ['amd64', 'win32']
41-
if platform.machine() == "ARM64":
42-
archs.append('arm64')
43-
for arch in archs:
44-
for exe in ['_freeze_module.exe', '_freeze_module_d.exe']:
45-
tool = os.path.join(ROOT_DIR, 'PCbuild', arch, exe)
46-
if os.path.isfile(tool):
47-
return tool
48-
sys.exit("ERROR: missing _freeze_module.exe; you need to run PCbuild/build.bat")
49-
TOOL = find_tool()
50-
del find_tool
51-
52-
MANIFEST = os.path.join(MODULES_DIR, 'MANIFEST')
5328
FROZEN_FILE = os.path.join(ROOT_DIR, 'Python', 'frozen.c')
5429
MAKEFILE = os.path.join(ROOT_DIR, 'Makefile.pre.in')
5530
PCBUILD_PROJECT = os.path.join(ROOT_DIR, 'PCbuild', '_freeze_module.vcxproj')
@@ -480,45 +455,6 @@ def replace_block(lines, start_marker, end_marker, replacements, file):
480455
return lines[:start_pos + 1] + replacements + lines[end_pos:]
481456

482457

483-
def regen_manifest(modules):
484-
header = 'module ispkg source frozen checksum'.split()
485-
widths = [5] * len(header)
486-
rows = []
487-
for mod in modules:
488-
info = mod.summarize()
489-
row = []
490-
for i, col in enumerate(header):
491-
value = info[col]
492-
if col == 'checksum':
493-
value = value[:12]
494-
elif col == 'ispkg':
495-
value = 'YES' if value else 'no'
496-
widths[i] = max(widths[i], len(value))
497-
row.append(value or '-')
498-
rows.append(row)
499-
500-
modlines = [
501-
'# The list of frozen modules with key information.',
502-
'# Note that the "check_generated_files" CI job will identify',
503-
'# when source files were changed but regen-frozen wasn\'t run.',
504-
'# This file is auto-generated by Tools/scripts/freeze_modules.py.',
505-
' '.join(c.center(w) for c, w in zip(header, widths)).rstrip(),
506-
' '.join('-' * w for w in widths),
507-
]
508-
for row in rows:
509-
for i, w in enumerate(widths):
510-
if header[i] == 'ispkg':
511-
row[i] = row[i].center(w)
512-
else:
513-
row[i] = row[i].ljust(w)
514-
modlines.append(' '.join(row).rstrip())
515-
516-
print(f'# Updating {os.path.relpath(MANIFEST)}')
517-
with open(MANIFEST, 'w', encoding="utf-8") as outfile:
518-
lines = (l + '\n' for l in modlines)
519-
outfile.writelines(lines)
520-
521-
522458
def regen_frozen(modules):
523459
headerlines = []
524460
parentdir = os.path.dirname(FROZEN_FILE)
@@ -648,11 +584,11 @@ def regen_makefile(modules):
648584
deepfreezefiles.append(f"\t\t{ofile} \\")
649585

650586
# Also add a deepfreeze rule.
651-
deepfreezerules.append(f'{cfile}: $(srcdir)/{_pyfile} $(DEEPFREEZE_DEPS)')
652-
deepfreezerules.append(f'\t@echo "Deepfreezing {cfile} from {_pyfile}"')
653-
deepfreezerules.append(f"\t@./$(BOOTSTRAP) \\")
654-
deepfreezerules.append(f"\t\t$(srcdir)/Tools/scripts/deepfreeze.py \\")
655-
deepfreezerules.append(f"\t\t$(srcdir)/{_pyfile} -m {src.frozenid} -o {cfile}")
587+
deepfreezerules.append(f'{cfile}: {header} $(DEEPFREEZE_DEPS)')
588+
deepfreezerules.append(
589+
f"\t$(PYTHON_FOR_REGEN) "
590+
f"$(srcdir)/Tools/scripts/deepfreeze.py "
591+
f"{header} -m {src.frozenid} -o {cfile}")
656592
deepfreezerules.append('')
657593

658594
for src in _iter_sources(modules):
@@ -663,7 +599,7 @@ def regen_makefile(modules):
663599
pyfiles.append(f'\t\t{pyfile} \\')
664600

665601
freeze = (f'Programs/_freeze_module {src.frozenid} '
666-
f'$(srcdir)/{pyfile} $(srcdir)/{header}')
602+
f'$(srcdir)/{pyfile} {header}')
667603
rules.extend([
668604
f'{header}: Programs/_freeze_module {pyfile}',
669605
f'\t{freeze}',
@@ -774,32 +710,6 @@ def regen_pcbuild(modules):
774710
outfile.writelines(lines)
775711

776712

777-
#######################################
778-
# freezing modules
779-
780-
def freeze_module(modname, pyfile=None, destdir=MODULES_DIR):
781-
"""Generate the frozen module .h file for the given module."""
782-
tmpsuffix = f'.{int(time.time())}'
783-
for modname, pyfile, ispkg in resolve_modules(modname, pyfile):
784-
frozenfile = resolve_frozen_file(modname, destdir)
785-
_freeze_module(modname, pyfile, frozenfile, tmpsuffix)
786-
787-
788-
def _freeze_module(frozenid, pyfile, frozenfile, tmpsuffix):
789-
tmpfile = f'{frozenfile}.{int(time.time())}'
790-
791-
argv = [TOOL, frozenid, pyfile, tmpfile]
792-
print('#', ' '.join(os.path.relpath(a) for a in argv), flush=True)
793-
try:
794-
subprocess.run(argv, check=True)
795-
except (FileNotFoundError, subprocess.CalledProcessError):
796-
if not os.path.exists(TOOL):
797-
sys.exit(f'ERROR: missing {TOOL}; you need to run "make regen-frozen"')
798-
raise # re-raise
799-
800-
update_file_with_tmpfile(frozenfile, tmpfile, create=True)
801-
802-
803713
#######################################
804714
# the script
805715

@@ -810,15 +720,7 @@ def main():
810720
# Regen build-related files.
811721
regen_makefile(modules)
812722
regen_pcbuild(modules)
813-
814-
# Freeze the target modules.
815-
tmpsuffix = f'.{int(time.time())}'
816-
for src in _iter_sources(modules):
817-
_freeze_module(src.frozenid, src.pyfile, src.frozenfile, tmpsuffix)
818-
819-
# Regen files dependent of frozen file details.
820723
regen_frozen(modules)
821-
regen_manifest(modules)
822724

823725

824726
if __name__ == '__main__':

0 commit comments

Comments
 (0)