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 ESC[4;38;5;81mFrozenSource(ESC[4;38;5;149mnamedtuple('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 ESC[4;38;5;81mFrozenModule(ESC[4;38;5;149mnamedtuple('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 = ["$(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()