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