Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Tools/build/freeze_modules.py
12 views
1
"""Freeze modules and regen related files (e.g. Python/frozen.c).
2
3
See the notes at the top of Python/frozen.c for more info.
4
"""
5
6
from collections import namedtuple
7
import hashlib
8
import os
9
import ntpath
10
import posixpath
11
import argparse
12
from update_file import updating_file_with_tmpfile
13
14
15
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
16
ROOT_DIR = os.path.abspath(ROOT_DIR)
17
FROZEN_ONLY = os.path.join(ROOT_DIR, 'Tools', 'freeze', 'flag.py')
18
19
STDLIB_DIR = os.path.join(ROOT_DIR, 'Lib')
20
# If FROZEN_MODULES_DIR or DEEPFROZEN_MODULES_DIR is changed then the
21
# .gitattributes and .gitignore files needs to be updated.
22
FROZEN_MODULES_DIR = os.path.join(ROOT_DIR, 'Python', 'frozen_modules')
23
DEEPFROZEN_MODULES_DIR = os.path.join(ROOT_DIR, 'Python', 'deepfreeze')
24
25
FROZEN_FILE = os.path.join(ROOT_DIR, 'Python', 'frozen.c')
26
MAKEFILE = os.path.join(ROOT_DIR, 'Makefile.pre.in')
27
PCBUILD_PROJECT = os.path.join(ROOT_DIR, 'PCbuild', '_freeze_module.vcxproj')
28
PCBUILD_FILTERS = os.path.join(ROOT_DIR, 'PCbuild', '_freeze_module.vcxproj.filters')
29
PCBUILD_PYTHONCORE = os.path.join(ROOT_DIR, 'PCbuild', 'pythoncore.vcxproj')
30
31
32
OS_PATH = 'ntpath' if os.name == 'nt' else 'posixpath'
33
34
# These are modules that get frozen.
35
# If you're debugging new bytecode instructions,
36
# you can delete all sections except 'import system'.
37
# This also speeds up building somewhat.
38
TESTS_SECTION = 'Test module'
39
FROZEN = [
40
# See parse_frozen_spec() for the format.
41
# In cases where the frozenid is duplicated, the first one is re-used.
42
('import system', [
43
# These frozen modules are necessary for bootstrapping
44
# the import system.
45
'importlib._bootstrap : _frozen_importlib',
46
'importlib._bootstrap_external : _frozen_importlib_external',
47
# This module is important because some Python builds rely
48
# on a builtin zip file instead of a filesystem.
49
'zipimport',
50
]),
51
# (You can delete entries from here down to the end of the list.)
52
('stdlib - startup, without site (python -S)', [
53
'abc',
54
'codecs',
55
# For now we do not freeze the encodings, due # to the noise all
56
# those extra modules add to the text printed during the build.
57
# (See https://github.com/python/cpython/pull/28398#pullrequestreview-756856469.)
58
#'<encodings.*>',
59
'io',
60
]),
61
('stdlib - startup, with site', [
62
'_collections_abc',
63
'_sitebuiltins',
64
'genericpath',
65
'ntpath',
66
'posixpath',
67
# We must explicitly mark os.path as a frozen module
68
# even though it will never be imported.
69
f'{OS_PATH} : os.path',
70
'os',
71
'site',
72
'stat',
73
]),
74
('runpy - run module with -m', [
75
"importlib.util",
76
"importlib.machinery",
77
"runpy",
78
]),
79
(TESTS_SECTION, [
80
'__hello__',
81
'__hello__ : __hello_alias__',
82
'__hello__ : <__phello_alias__>',
83
'__hello__ : __phello_alias__.spam',
84
'<__phello__.**.*>',
85
f'frozen_only : __hello_only__ = {FROZEN_ONLY}',
86
]),
87
# (End of stuff you could delete.)
88
]
89
BOOTSTRAP = {
90
'importlib._bootstrap',
91
'importlib._bootstrap_external',
92
'zipimport',
93
}
94
95
96
#######################################
97
# platform-specific helpers
98
99
if os.path is posixpath:
100
relpath_for_posix_display = os.path.relpath
101
102
def relpath_for_windows_display(path, base):
103
return ntpath.relpath(
104
ntpath.join(*path.split(os.path.sep)),
105
ntpath.join(*base.split(os.path.sep)),
106
)
107
108
else:
109
relpath_for_windows_display = ntpath.relpath
110
111
def relpath_for_posix_display(path, base):
112
return posixpath.relpath(
113
posixpath.join(*path.split(os.path.sep)),
114
posixpath.join(*base.split(os.path.sep)),
115
)
116
117
118
#######################################
119
# specs
120
121
def parse_frozen_specs():
122
seen = {}
123
for section, specs in FROZEN:
124
parsed = _parse_specs(specs, section, seen)
125
for item in parsed:
126
frozenid, pyfile, modname, ispkg, section = item
127
try:
128
source = seen[frozenid]
129
except KeyError:
130
source = FrozenSource.from_id(frozenid, pyfile)
131
seen[frozenid] = source
132
else:
133
assert not pyfile or pyfile == source.pyfile, item
134
yield FrozenModule(modname, ispkg, section, source)
135
136
137
def _parse_specs(specs, section, seen):
138
for spec in specs:
139
info, subs = _parse_spec(spec, seen, section)
140
yield info
141
for info in subs or ():
142
yield info
143
144
145
def _parse_spec(spec, knownids=None, section=None):
146
"""Yield an info tuple for each module corresponding to the given spec.
147
148
The info consists of: (frozenid, pyfile, modname, ispkg, section).
149
150
Supported formats:
151
152
frozenid
153
frozenid : modname
154
frozenid : modname = pyfile
155
156
"frozenid" and "modname" must be valid module names (dot-separated
157
identifiers). If "modname" is not provided then "frozenid" is used.
158
If "pyfile" is not provided then the filename of the module
159
corresponding to "frozenid" is used.
160
161
Angle brackets around a frozenid (e.g. '<encodings>") indicate
162
it is a package. This also means it must be an actual module
163
(i.e. "pyfile" cannot have been provided). Such values can have
164
patterns to expand submodules:
165
166
<encodings.*> - also freeze all direct submodules
167
<encodings.**.*> - also freeze the full submodule tree
168
169
As with "frozenid", angle brackets around "modname" indicate
170
it is a package. However, in this case "pyfile" should not
171
have been provided and patterns in "modname" are not supported.
172
Also, if "modname" has brackets then "frozenid" should not,
173
and "pyfile" should have been provided..
174
"""
175
frozenid, _, remainder = spec.partition(':')
176
modname, _, pyfile = remainder.partition('=')
177
frozenid = frozenid.strip()
178
modname = modname.strip()
179
pyfile = pyfile.strip()
180
181
submodules = None
182
if modname.startswith('<') and modname.endswith('>'):
183
assert check_modname(frozenid), spec
184
modname = modname[1:-1]
185
assert check_modname(modname), spec
186
if frozenid in knownids:
187
pass
188
elif pyfile:
189
assert not os.path.isdir(pyfile), spec
190
else:
191
pyfile = _resolve_module(frozenid, ispkg=False)
192
ispkg = True
193
elif pyfile:
194
assert check_modname(frozenid), spec
195
assert not knownids or frozenid not in knownids, spec
196
assert check_modname(modname), spec
197
assert not os.path.isdir(pyfile), spec
198
ispkg = False
199
elif knownids and frozenid in knownids:
200
assert check_modname(frozenid), spec
201
assert check_modname(modname), spec
202
ispkg = False
203
else:
204
assert not modname or check_modname(modname), spec
205
resolved = iter(resolve_modules(frozenid))
206
frozenid, pyfile, ispkg = next(resolved)
207
if not modname:
208
modname = frozenid
209
if ispkg:
210
pkgid = frozenid
211
pkgname = modname
212
pkgfiles = {pyfile: pkgid}
213
def iter_subs():
214
for frozenid, pyfile, ispkg in resolved:
215
if pkgname:
216
modname = frozenid.replace(pkgid, pkgname, 1)
217
else:
218
modname = frozenid
219
if pyfile:
220
if pyfile in pkgfiles:
221
frozenid = pkgfiles[pyfile]
222
pyfile = None
223
elif ispkg:
224
pkgfiles[pyfile] = frozenid
225
yield frozenid, pyfile, modname, ispkg, section
226
submodules = iter_subs()
227
228
info = (frozenid, pyfile or None, modname, ispkg, section)
229
return info, submodules
230
231
232
#######################################
233
# frozen source files
234
235
class FrozenSource(namedtuple('FrozenSource', 'id pyfile frozenfile deepfreezefile')):
236
237
@classmethod
238
def from_id(cls, frozenid, pyfile=None):
239
if not pyfile:
240
pyfile = os.path.join(STDLIB_DIR, *frozenid.split('.')) + '.py'
241
#assert os.path.exists(pyfile), (frozenid, pyfile)
242
frozenfile = resolve_frozen_file(frozenid, FROZEN_MODULES_DIR)
243
deepfreezefile = resolve_frozen_file(frozenid, DEEPFROZEN_MODULES_DIR)
244
return cls(frozenid, pyfile, frozenfile, deepfreezefile)
245
246
@property
247
def frozenid(self):
248
return self.id
249
250
@property
251
def modname(self):
252
if self.pyfile.startswith(STDLIB_DIR):
253
return self.id
254
return None
255
256
@property
257
def symbol(self):
258
# This matches what we do in Programs/_freeze_module.c:
259
name = self.frozenid.replace('.', '_')
260
return '_Py_M__' + name
261
262
@property
263
def ispkg(self):
264
if not self.pyfile:
265
return False
266
elif self.frozenid.endswith('.__init__'):
267
return False
268
else:
269
return os.path.basename(self.pyfile) == '__init__.py'
270
271
@property
272
def isbootstrap(self):
273
return self.id in BOOTSTRAP
274
275
276
def resolve_frozen_file(frozenid, destdir):
277
"""Return the filename corresponding to the given frozen ID.
278
279
For stdlib modules the ID will always be the full name
280
of the source module.
281
"""
282
if not isinstance(frozenid, str):
283
try:
284
frozenid = frozenid.frozenid
285
except AttributeError:
286
raise ValueError(f'unsupported frozenid {frozenid!r}')
287
# We use a consistent naming convention for all frozen modules.
288
frozenfile = f'{frozenid}.h'
289
if not destdir:
290
return frozenfile
291
return os.path.join(destdir, frozenfile)
292
293
294
#######################################
295
# frozen modules
296
297
class FrozenModule(namedtuple('FrozenModule', 'name ispkg section source')):
298
299
def __getattr__(self, name):
300
return getattr(self.source, name)
301
302
@property
303
def modname(self):
304
return self.name
305
306
@property
307
def orig(self):
308
return self.source.modname
309
310
@property
311
def isalias(self):
312
orig = self.source.modname
313
if not orig:
314
return True
315
return self.name != orig
316
317
def summarize(self):
318
source = self.source.modname
319
if source:
320
source = f'<{source}>'
321
else:
322
source = relpath_for_posix_display(self.pyfile, ROOT_DIR)
323
return {
324
'module': self.name,
325
'ispkg': self.ispkg,
326
'source': source,
327
'frozen': os.path.basename(self.frozenfile),
328
'checksum': _get_checksum(self.frozenfile),
329
}
330
331
332
def _iter_sources(modules):
333
seen = set()
334
for mod in modules:
335
if mod.source not in seen:
336
yield mod.source
337
seen.add(mod.source)
338
339
340
#######################################
341
# generic helpers
342
343
def _get_checksum(filename):
344
with open(filename, "rb") as infile:
345
contents = infile.read()
346
m = hashlib.sha256()
347
m.update(contents)
348
return m.hexdigest()
349
350
351
def resolve_modules(modname, pyfile=None):
352
if modname.startswith('<') and modname.endswith('>'):
353
if pyfile:
354
assert os.path.isdir(pyfile) or os.path.basename(pyfile) == '__init__.py', pyfile
355
ispkg = True
356
modname = modname[1:-1]
357
rawname = modname
358
# For now, we only expect match patterns at the end of the name.
359
_modname, sep, match = modname.rpartition('.')
360
if sep:
361
if _modname.endswith('.**'):
362
modname = _modname[:-3]
363
match = f'**.{match}'
364
elif match and not match.isidentifier():
365
modname = _modname
366
# Otherwise it's a plain name so we leave it alone.
367
else:
368
match = None
369
else:
370
ispkg = False
371
rawname = modname
372
match = None
373
374
if not check_modname(modname):
375
raise ValueError(f'not a valid module name ({rawname})')
376
377
if not pyfile:
378
pyfile = _resolve_module(modname, ispkg=ispkg)
379
elif os.path.isdir(pyfile):
380
pyfile = _resolve_module(modname, pyfile, ispkg)
381
yield modname, pyfile, ispkg
382
383
if match:
384
pkgdir = os.path.dirname(pyfile)
385
yield from iter_submodules(modname, pkgdir, match)
386
387
388
def check_modname(modname):
389
return all(n.isidentifier() for n in modname.split('.'))
390
391
392
def iter_submodules(pkgname, pkgdir=None, match='*'):
393
if not pkgdir:
394
pkgdir = os.path.join(STDLIB_DIR, *pkgname.split('.'))
395
if not match:
396
match = '**.*'
397
match_modname = _resolve_modname_matcher(match, pkgdir)
398
399
def _iter_submodules(pkgname, pkgdir):
400
for entry in sorted(os.scandir(pkgdir), key=lambda e: e.name):
401
matched, recursive = match_modname(entry.name)
402
if not matched:
403
continue
404
modname = f'{pkgname}.{entry.name}'
405
if modname.endswith('.py'):
406
yield modname[:-3], entry.path, False
407
elif entry.is_dir():
408
pyfile = os.path.join(entry.path, '__init__.py')
409
# We ignore namespace packages.
410
if os.path.exists(pyfile):
411
yield modname, pyfile, True
412
if recursive:
413
yield from _iter_submodules(modname, entry.path)
414
415
return _iter_submodules(pkgname, pkgdir)
416
417
418
def _resolve_modname_matcher(match, rootdir=None):
419
if isinstance(match, str):
420
if match.startswith('**.'):
421
recursive = True
422
pat = match[3:]
423
assert match
424
else:
425
recursive = False
426
pat = match
427
428
if pat == '*':
429
def match_modname(modname):
430
return True, recursive
431
else:
432
raise NotImplementedError(match)
433
elif callable(match):
434
match_modname = match(rootdir)
435
else:
436
raise ValueError(f'unsupported matcher {match!r}')
437
return match_modname
438
439
440
def _resolve_module(modname, pathentry=STDLIB_DIR, ispkg=False):
441
assert pathentry, pathentry
442
pathentry = os.path.normpath(pathentry)
443
assert os.path.isabs(pathentry)
444
if ispkg:
445
return os.path.join(pathentry, *modname.split('.'), '__init__.py')
446
return os.path.join(pathentry, *modname.split('.')) + '.py'
447
448
449
#######################################
450
# regenerating dependent files
451
452
def find_marker(lines, marker, file):
453
for pos, line in enumerate(lines):
454
if marker in line:
455
return pos
456
raise Exception(f"Can't find {marker!r} in file {file}")
457
458
459
def replace_block(lines, start_marker, end_marker, replacements, file):
460
start_pos = find_marker(lines, start_marker, file)
461
end_pos = find_marker(lines, end_marker, file)
462
if end_pos <= start_pos:
463
raise Exception(f"End marker {end_marker!r} "
464
f"occurs before start marker {start_marker!r} "
465
f"in file {file}")
466
replacements = [line.rstrip() + '\n' for line in replacements]
467
return lines[:start_pos + 1] + replacements + lines[end_pos:]
468
469
470
def regen_frozen(modules, frozen_modules: bool):
471
headerlines = []
472
parentdir = os.path.dirname(FROZEN_FILE)
473
if frozen_modules:
474
for src in _iter_sources(modules):
475
# Adding a comment to separate sections here doesn't add much,
476
# so we don't.
477
header = relpath_for_posix_display(src.frozenfile, parentdir)
478
headerlines.append(f'#include "{header}"')
479
480
externlines = []
481
bootstraplines = []
482
stdliblines = []
483
testlines = []
484
aliaslines = []
485
indent = ' '
486
lastsection = None
487
for mod in modules:
488
if mod.isbootstrap:
489
lines = bootstraplines
490
elif mod.section == TESTS_SECTION:
491
lines = testlines
492
else:
493
lines = stdliblines
494
if mod.section != lastsection:
495
if lastsection is not None:
496
lines.append('')
497
lines.append(f'/* {mod.section} */')
498
lastsection = mod.section
499
500
# Also add a extern declaration for the corresponding
501
# deepfreeze-generated function.
502
orig_name = mod.source.id
503
code_name = orig_name.replace(".", "_")
504
get_code_name = "_Py_get_%s_toplevel" % code_name
505
externlines.append("extern PyObject *%s(void);" % get_code_name)
506
507
symbol = mod.symbol
508
pkg = 'true' if mod.ispkg else 'false'
509
if not frozen_modules:
510
line = ('{"%s", NULL, 0, %s, GET_CODE(%s)},'
511
) % (mod.name, pkg, code_name)
512
else:
513
line = ('{"%s", %s, (int)sizeof(%s), %s, GET_CODE(%s)},'
514
) % (mod.name, symbol, symbol, pkg, code_name)
515
lines.append(line)
516
517
if mod.isalias:
518
if not mod.orig:
519
entry = '{"%s", NULL},' % (mod.name,)
520
elif mod.source.ispkg:
521
entry = '{"%s", "<%s"},' % (mod.name, mod.orig)
522
else:
523
entry = '{"%s", "%s"},' % (mod.name, mod.orig)
524
aliaslines.append(indent + entry)
525
526
for lines in (bootstraplines, stdliblines, testlines):
527
# TODO: Is this necessary any more?
528
if lines and not lines[0]:
529
del lines[0]
530
for i, line in enumerate(lines):
531
if line:
532
lines[i] = indent + line
533
534
print(f'# Updating {os.path.relpath(FROZEN_FILE)}')
535
with updating_file_with_tmpfile(FROZEN_FILE) as (infile, outfile):
536
lines = infile.readlines()
537
# TODO: Use more obvious markers, e.g.
538
# $START GENERATED FOOBAR$ / $END GENERATED FOOBAR$
539
lines = replace_block(
540
lines,
541
"/* Includes for frozen modules: */",
542
"/* End includes */",
543
headerlines,
544
FROZEN_FILE,
545
)
546
lines = replace_block(
547
lines,
548
"/* Start extern declarations */",
549
"/* End extern declarations */",
550
externlines,
551
FROZEN_FILE,
552
)
553
lines = replace_block(
554
lines,
555
"static const struct _frozen bootstrap_modules[] =",
556
"/* bootstrap sentinel */",
557
bootstraplines,
558
FROZEN_FILE,
559
)
560
lines = replace_block(
561
lines,
562
"static const struct _frozen stdlib_modules[] =",
563
"/* stdlib sentinel */",
564
stdliblines,
565
FROZEN_FILE,
566
)
567
lines = replace_block(
568
lines,
569
"static const struct _frozen test_modules[] =",
570
"/* test sentinel */",
571
testlines,
572
FROZEN_FILE,
573
)
574
lines = replace_block(
575
lines,
576
"const struct _module_alias aliases[] =",
577
"/* aliases sentinel */",
578
aliaslines,
579
FROZEN_FILE,
580
)
581
outfile.writelines(lines)
582
583
584
def regen_makefile(modules):
585
pyfiles = []
586
frozenfiles = []
587
rules = ['']
588
deepfreezerules = ["Python/deepfreeze/deepfreeze.c: $(DEEPFREEZE_DEPS)",
589
"\t$(PYTHON_FOR_FREEZE) $(srcdir)/Tools/build/deepfreeze.py \\"]
590
for src in _iter_sources(modules):
591
frozen_header = relpath_for_posix_display(src.frozenfile, ROOT_DIR)
592
frozenfiles.append(f'\t\t{frozen_header} \\')
593
594
pyfile = relpath_for_posix_display(src.pyfile, ROOT_DIR)
595
pyfiles.append(f'\t\t{pyfile} \\')
596
597
if src.isbootstrap:
598
freezecmd = '$(FREEZE_MODULE_BOOTSTRAP)'
599
freezedep = '$(FREEZE_MODULE_BOOTSTRAP_DEPS)'
600
else:
601
freezecmd = '$(FREEZE_MODULE)'
602
freezedep = '$(FREEZE_MODULE_DEPS)'
603
604
freeze = (f'{freezecmd} {src.frozenid} '
605
f'$(srcdir)/{pyfile} {frozen_header}')
606
rules.extend([
607
f'{frozen_header}: {pyfile} {freezedep}',
608
f'\t{freeze}',
609
'',
610
])
611
deepfreezerules.append(f"\t{frozen_header}:{src.frozenid} \\")
612
deepfreezerules.append('\t-o Python/deepfreeze/deepfreeze.c')
613
pyfiles[-1] = pyfiles[-1].rstrip(" \\")
614
frozenfiles[-1] = frozenfiles[-1].rstrip(" \\")
615
616
print(f'# Updating {os.path.relpath(MAKEFILE)}')
617
with updating_file_with_tmpfile(MAKEFILE) as (infile, outfile):
618
lines = infile.readlines()
619
lines = replace_block(
620
lines,
621
"FROZEN_FILES_IN =",
622
"# End FROZEN_FILES_IN",
623
pyfiles,
624
MAKEFILE,
625
)
626
lines = replace_block(
627
lines,
628
"FROZEN_FILES_OUT =",
629
"# End FROZEN_FILES_OUT",
630
frozenfiles,
631
MAKEFILE,
632
)
633
lines = replace_block(
634
lines,
635
"# BEGIN: freezing modules",
636
"# END: freezing modules",
637
rules,
638
MAKEFILE,
639
)
640
lines = replace_block(
641
lines,
642
"# BEGIN: deepfreeze modules",
643
"# END: deepfreeze modules",
644
deepfreezerules,
645
MAKEFILE,
646
)
647
outfile.writelines(lines)
648
649
650
def regen_pcbuild(modules):
651
projlines = []
652
filterlines = []
653
corelines = []
654
deepfreezerules = ['\t<Exec Command=\'$(PythonForBuild) "$(PySourcePath)Tools\\build\\deepfreeze.py" ^']
655
for src in _iter_sources(modules):
656
pyfile = relpath_for_windows_display(src.pyfile, ROOT_DIR)
657
header = relpath_for_windows_display(src.frozenfile, ROOT_DIR)
658
intfile = ntpath.splitext(ntpath.basename(header))[0] + '.g.h'
659
projlines.append(f' <None Include="..\\{pyfile}">')
660
projlines.append(f' <ModName>{src.frozenid}</ModName>')
661
projlines.append(f' <IntFile>$(IntDir){intfile}</IntFile>')
662
projlines.append(f' <OutFile>$(PySourcePath){header}</OutFile>')
663
projlines.append(f' </None>')
664
665
filterlines.append(f' <None Include="..\\{pyfile}">')
666
filterlines.append(' <Filter>Python Files</Filter>')
667
filterlines.append(' </None>')
668
deepfreezerules.append(f'\t\t "$(PySourcePath){header}:{src.frozenid}" ^')
669
deepfreezerules.append('\t\t "-o" "$(PySourcePath)Python\\deepfreeze\\deepfreeze.c"\'/>' )
670
671
corelines.append(f' <ClCompile Include="..\\Python\\deepfreeze\\deepfreeze.c" />')
672
673
print(f'# Updating {os.path.relpath(PCBUILD_PROJECT)}')
674
with updating_file_with_tmpfile(PCBUILD_PROJECT) as (infile, outfile):
675
lines = infile.readlines()
676
lines = replace_block(
677
lines,
678
'<!-- BEGIN frozen modules -->',
679
'<!-- END frozen modules -->',
680
projlines,
681
PCBUILD_PROJECT,
682
)
683
outfile.writelines(lines)
684
with updating_file_with_tmpfile(PCBUILD_PROJECT) as (infile, outfile):
685
lines = infile.readlines()
686
lines = replace_block(
687
lines,
688
'<!-- BEGIN deepfreeze rule -->',
689
'<!-- END deepfreeze rule -->',
690
deepfreezerules,
691
PCBUILD_PROJECT,
692
)
693
outfile.writelines(lines)
694
print(f'# Updating {os.path.relpath(PCBUILD_FILTERS)}')
695
with updating_file_with_tmpfile(PCBUILD_FILTERS) as (infile, outfile):
696
lines = infile.readlines()
697
lines = replace_block(
698
lines,
699
'<!-- BEGIN frozen modules -->',
700
'<!-- END frozen modules -->',
701
filterlines,
702
PCBUILD_FILTERS,
703
)
704
outfile.writelines(lines)
705
print(f'# Updating {os.path.relpath(PCBUILD_PYTHONCORE)}')
706
with updating_file_with_tmpfile(PCBUILD_PYTHONCORE) as (infile, outfile):
707
lines = infile.readlines()
708
lines = replace_block(
709
lines,
710
'<!-- BEGIN deepfreeze -->',
711
'<!-- END deepfreeze -->',
712
corelines,
713
PCBUILD_FILTERS,
714
)
715
outfile.writelines(lines)
716
717
718
#######################################
719
# the script
720
721
parser = argparse.ArgumentParser()
722
parser.add_argument("--frozen-modules", action="store_true",
723
help="Use both frozen and deepfrozen modules. (default: uses only deepfrozen modules)")
724
725
def main():
726
args = parser.parse_args()
727
frozen_modules: bool = args.frozen_modules
728
# Expand the raw specs, preserving order.
729
modules = list(parse_frozen_specs())
730
731
# Regen build-related files.
732
regen_makefile(modules)
733
regen_pcbuild(modules)
734
regen_frozen(modules, frozen_modules)
735
736
737
if __name__ == '__main__':
738
main()
739
740