2021-08-30 20:25:11 -03:00
|
|
|
"""Freeze modules and regen related files (e.g. Python/frozen.c).
|
|
|
|
|
|
|
|
See the notes at the top of Python/frozen.c for more info.
|
|
|
|
"""
|
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
from collections import namedtuple
|
|
|
|
import hashlib
|
2021-08-30 20:25:11 -03:00
|
|
|
import os
|
2021-09-15 14:11:12 -03:00
|
|
|
import ntpath
|
|
|
|
import posixpath
|
2021-08-30 20:25:11 -03:00
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import textwrap
|
|
|
|
|
|
|
|
from update_file import updating_file_with_tmpfile
|
|
|
|
|
|
|
|
|
|
|
|
SCRIPTS_DIR = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
TOOLS_DIR = os.path.dirname(SCRIPTS_DIR)
|
|
|
|
ROOT_DIR = os.path.dirname(TOOLS_DIR)
|
|
|
|
|
|
|
|
STDLIB_DIR = os.path.join(ROOT_DIR, 'Lib')
|
2021-09-16 17:20:52 -03:00
|
|
|
# If MODULES_DIR is changed then the .gitattributes and .gitignore files
|
|
|
|
# need to be updated.
|
|
|
|
MODULES_DIR = os.path.join(ROOT_DIR, 'Python', 'frozen_modules')
|
2021-09-15 14:11:12 -03:00
|
|
|
|
|
|
|
if sys.platform != "win32":
|
|
|
|
TOOL = os.path.join(ROOT_DIR, 'Programs', '_freeze_module')
|
|
|
|
else:
|
|
|
|
def find_tool():
|
|
|
|
for arch in ['amd64', 'win32']:
|
|
|
|
for exe in ['_freeze_module.exe', '_freeze_module_d.exe']:
|
|
|
|
tool = os.path.join(ROOT_DIR, 'PCbuild', arch, exe)
|
|
|
|
if os.path.isfile(tool):
|
|
|
|
return tool
|
|
|
|
sys.exit("ERROR: missing _freeze_module.exe; you need to run PCbuild/build.bat")
|
|
|
|
TOOL = find_tool()
|
|
|
|
del find_tool
|
2021-08-30 20:25:11 -03:00
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
MANIFEST = os.path.join(MODULES_DIR, 'MANIFEST')
|
2021-08-30 20:25:11 -03:00
|
|
|
FROZEN_FILE = os.path.join(ROOT_DIR, 'Python', 'frozen.c')
|
|
|
|
MAKEFILE = os.path.join(ROOT_DIR, 'Makefile.pre.in')
|
|
|
|
PCBUILD_PROJECT = os.path.join(ROOT_DIR, 'PCbuild', '_freeze_module.vcxproj')
|
|
|
|
PCBUILD_FILTERS = os.path.join(ROOT_DIR, 'PCbuild', '_freeze_module.vcxproj.filters')
|
2021-09-13 19:18:37 -03:00
|
|
|
TEST_CTYPES = os.path.join(STDLIB_DIR, 'ctypes', 'test', 'test_values.py')
|
2021-08-30 20:25:11 -03:00
|
|
|
|
2021-09-15 13:19:30 -03:00
|
|
|
|
|
|
|
OS_PATH = 'ntpath' if os.name == 'nt' else 'posixpath'
|
|
|
|
|
2021-08-30 20:25:11 -03:00
|
|
|
# These are modules that get frozen.
|
|
|
|
FROZEN = [
|
|
|
|
# See parse_frozen_spec() for the format.
|
|
|
|
# In cases where the frozenid is duplicated, the first one is re-used.
|
2021-09-13 19:18:37 -03:00
|
|
|
('import system', [
|
|
|
|
# These frozen modules are necessary for bootstrapping
|
|
|
|
# the import system.
|
2021-08-30 20:25:11 -03:00
|
|
|
'importlib._bootstrap : _frozen_importlib',
|
|
|
|
'importlib._bootstrap_external : _frozen_importlib_external',
|
2021-09-13 19:18:37 -03:00
|
|
|
# This module is important because some Python builds rely
|
|
|
|
# on a builtin zip file instead of a filesystem.
|
2021-08-30 20:25:11 -03:00
|
|
|
'zipimport',
|
|
|
|
]),
|
2021-09-15 13:19:30 -03:00
|
|
|
('stdlib', [
|
|
|
|
# For the moment we skip codecs, encodings.*, os, and site.
|
|
|
|
# These modules have different generated files depending on
|
|
|
|
# if a debug or non-debug build. (See bpo-45186 and bpo-45188.)
|
|
|
|
# without site (python -S)
|
|
|
|
'abc',
|
|
|
|
#'codecs',
|
|
|
|
# '<encodings.*>',
|
|
|
|
'io',
|
|
|
|
# with site
|
|
|
|
'_collections_abc',
|
|
|
|
'_sitebuiltins',
|
|
|
|
'genericpath',
|
|
|
|
'ntpath',
|
|
|
|
'posixpath',
|
|
|
|
# We must explicitly mark os.path as a frozen module
|
|
|
|
# even though it will never be imported.
|
|
|
|
#f'{OS_PATH} : os.path',
|
|
|
|
#'os',
|
|
|
|
#'site',
|
|
|
|
'stat',
|
|
|
|
]),
|
2021-08-30 20:25:11 -03:00
|
|
|
('Test module', [
|
2021-09-15 17:15:32 -03:00
|
|
|
'__hello__',
|
|
|
|
'__hello__ : <__phello__>',
|
|
|
|
'__hello__ : __phello__.spam',
|
2021-08-30 20:25:11 -03:00
|
|
|
]),
|
|
|
|
]
|
2021-09-13 19:18:37 -03:00
|
|
|
ESSENTIAL = {
|
|
|
|
'importlib._bootstrap',
|
|
|
|
'importlib._bootstrap_external',
|
|
|
|
'zipimport',
|
|
|
|
}
|
2021-08-30 20:25:11 -03:00
|
|
|
|
|
|
|
|
2021-09-15 14:11:12 -03:00
|
|
|
#######################################
|
|
|
|
# platform-specific helpers
|
|
|
|
|
|
|
|
if os.path is posixpath:
|
|
|
|
relpath_for_posix_display = os.path.relpath
|
|
|
|
|
|
|
|
def relpath_for_windows_display(path, base):
|
|
|
|
return ntpath.relpath(
|
|
|
|
ntpath.join(*path.split(os.path.sep)),
|
|
|
|
ntpath.join(*base.split(os.path.sep)),
|
|
|
|
)
|
|
|
|
|
|
|
|
else:
|
|
|
|
relpath_for_windows_display = ntpath.relpath
|
|
|
|
|
|
|
|
def relpath_for_posix_display(path, base):
|
|
|
|
return posixpath.relpath(
|
|
|
|
posixpath.join(*path.split(os.path.sep)),
|
|
|
|
posixpath.join(*base.split(os.path.sep)),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-08-30 20:25:11 -03:00
|
|
|
#######################################
|
|
|
|
# specs
|
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
def parse_frozen_specs(sectionalspecs=FROZEN, destdir=None):
|
|
|
|
seen = {}
|
|
|
|
for section, specs in sectionalspecs:
|
|
|
|
parsed = _parse_specs(specs, section, seen)
|
|
|
|
for frozenid, pyfile, modname, ispkg, section in parsed:
|
|
|
|
try:
|
|
|
|
source = seen[frozenid]
|
|
|
|
except KeyError:
|
|
|
|
source = FrozenSource.from_id(frozenid, pyfile, destdir)
|
|
|
|
seen[frozenid] = source
|
|
|
|
else:
|
|
|
|
assert not pyfile
|
|
|
|
yield FrozenModule(modname, ispkg, section, source)
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_specs(specs, section, seen):
|
|
|
|
for spec in specs:
|
|
|
|
info, subs = _parse_spec(spec, seen, section)
|
|
|
|
yield info
|
|
|
|
for info in subs or ():
|
|
|
|
yield info
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_spec(spec, knownids=None, section=None):
|
|
|
|
"""Yield an info tuple for each module corresponding to the given spec.
|
|
|
|
|
|
|
|
The info consists of: (frozenid, pyfile, modname, ispkg, section).
|
2021-08-30 20:25:11 -03:00
|
|
|
|
|
|
|
Supported formats:
|
|
|
|
|
|
|
|
frozenid
|
|
|
|
frozenid : modname
|
|
|
|
frozenid : modname = pyfile
|
|
|
|
|
|
|
|
"frozenid" and "modname" must be valid module names (dot-separated
|
|
|
|
identifiers). If "modname" is not provided then "frozenid" is used.
|
|
|
|
If "pyfile" is not provided then the filename of the module
|
|
|
|
corresponding to "frozenid" is used.
|
|
|
|
|
|
|
|
Angle brackets around a frozenid (e.g. '<encodings>") indicate
|
|
|
|
it is a package. This also means it must be an actual module
|
|
|
|
(i.e. "pyfile" cannot have been provided). Such values can have
|
|
|
|
patterns to expand submodules:
|
|
|
|
|
|
|
|
<encodings.*> - also freeze all direct submodules
|
|
|
|
<encodings.**.*> - also freeze the full submodule tree
|
|
|
|
|
|
|
|
As with "frozenid", angle brackets around "modname" indicate
|
|
|
|
it is a package. However, in this case "pyfile" should not
|
|
|
|
have been provided and patterns in "modname" are not supported.
|
|
|
|
Also, if "modname" has brackets then "frozenid" should not,
|
|
|
|
and "pyfile" should have been provided..
|
|
|
|
"""
|
2021-09-13 19:18:37 -03:00
|
|
|
frozenid, _, remainder = spec.partition(':')
|
2021-08-30 20:25:11 -03:00
|
|
|
modname, _, pyfile = remainder.partition('=')
|
|
|
|
frozenid = frozenid.strip()
|
|
|
|
modname = modname.strip()
|
|
|
|
pyfile = pyfile.strip()
|
|
|
|
|
|
|
|
submodules = None
|
|
|
|
if modname.startswith('<') and modname.endswith('>'):
|
2021-09-13 19:18:37 -03:00
|
|
|
assert check_modname(frozenid), spec
|
2021-08-30 20:25:11 -03:00
|
|
|
modname = modname[1:-1]
|
2021-09-13 19:18:37 -03:00
|
|
|
assert check_modname(modname), spec
|
2021-08-30 20:25:11 -03:00
|
|
|
if frozenid in knownids:
|
|
|
|
pass
|
|
|
|
elif pyfile:
|
2021-09-13 19:18:37 -03:00
|
|
|
assert not os.path.isdir(pyfile), spec
|
2021-08-30 20:25:11 -03:00
|
|
|
else:
|
|
|
|
pyfile = _resolve_module(frozenid, ispkg=False)
|
|
|
|
ispkg = True
|
|
|
|
elif pyfile:
|
2021-09-13 19:18:37 -03:00
|
|
|
assert check_modname(frozenid), spec
|
|
|
|
assert not knownids or frozenid not in knownids, spec
|
|
|
|
assert check_modname(modname), spec
|
|
|
|
assert not os.path.isdir(pyfile), spec
|
2021-08-30 20:25:11 -03:00
|
|
|
ispkg = False
|
|
|
|
elif knownids and frozenid in knownids:
|
2021-09-13 19:18:37 -03:00
|
|
|
assert check_modname(frozenid), spec
|
|
|
|
assert check_modname(modname), spec
|
2021-08-30 20:25:11 -03:00
|
|
|
ispkg = False
|
|
|
|
else:
|
2021-09-13 19:18:37 -03:00
|
|
|
assert not modname or check_modname(modname), spec
|
2021-08-30 20:25:11 -03:00
|
|
|
resolved = iter(resolve_modules(frozenid))
|
|
|
|
frozenid, pyfile, ispkg = next(resolved)
|
|
|
|
if not modname:
|
|
|
|
modname = frozenid
|
|
|
|
if ispkg:
|
|
|
|
pkgid = frozenid
|
|
|
|
pkgname = modname
|
|
|
|
def iter_subs():
|
|
|
|
for frozenid, pyfile, ispkg in resolved:
|
2021-09-13 19:18:37 -03:00
|
|
|
assert not knownids or frozenid not in knownids, (frozenid, spec)
|
2021-08-30 20:25:11 -03:00
|
|
|
if pkgname:
|
|
|
|
modname = frozenid.replace(pkgid, pkgname, 1)
|
|
|
|
else:
|
|
|
|
modname = frozenid
|
|
|
|
yield frozenid, pyfile, modname, ispkg, section
|
|
|
|
submodules = iter_subs()
|
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
info = (frozenid, pyfile or None, modname, ispkg, section)
|
|
|
|
return info, submodules
|
2021-08-30 20:25:11 -03:00
|
|
|
|
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
#######################################
|
|
|
|
# frozen source files
|
2021-08-30 20:25:11 -03:00
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
class FrozenSource(namedtuple('FrozenSource', 'id pyfile frozenfile')):
|
2021-08-30 20:25:11 -03:00
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
@classmethod
|
|
|
|
def from_id(cls, frozenid, pyfile=None, destdir=MODULES_DIR):
|
|
|
|
if not pyfile:
|
|
|
|
pyfile = os.path.join(STDLIB_DIR, *frozenid.split('.')) + '.py'
|
|
|
|
#assert os.path.exists(pyfile), (frozenid, pyfile)
|
|
|
|
frozenfile = resolve_frozen_file(frozenid, destdir)
|
|
|
|
return cls(frozenid, pyfile, frozenfile)
|
2021-08-30 20:25:11 -03:00
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
@property
|
|
|
|
def frozenid(self):
|
|
|
|
return self.id
|
2021-08-30 20:25:11 -03:00
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
@property
|
|
|
|
def modname(self):
|
|
|
|
if self.pyfile.startswith(STDLIB_DIR):
|
|
|
|
return self.id
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def symbol(self):
|
|
|
|
# This matches what we do in Programs/_freeze_module.c:
|
|
|
|
name = self.frozenid.replace('.', '_')
|
|
|
|
return '_Py_M__' + name
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_frozen_file(frozenid, destdir=MODULES_DIR):
|
|
|
|
"""Return the filename corresponding to the given frozen ID.
|
|
|
|
|
|
|
|
For stdlib modules the ID will always be the full name
|
|
|
|
of the source module.
|
|
|
|
"""
|
|
|
|
if not isinstance(frozenid, str):
|
|
|
|
try:
|
|
|
|
frozenid = frozenid.frozenid
|
|
|
|
except AttributeError:
|
|
|
|
raise ValueError(f'unsupported frozenid {frozenid!r}')
|
2021-08-30 20:25:11 -03:00
|
|
|
# We use a consistent naming convention for all frozen modules.
|
2021-09-13 19:18:37 -03:00
|
|
|
frozenfile = frozenid.replace('.', '_') + '.h'
|
|
|
|
if not destdir:
|
|
|
|
return frozenfile
|
|
|
|
return os.path.join(destdir, frozenfile)
|
2021-08-30 20:25:11 -03:00
|
|
|
|
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
#######################################
|
|
|
|
# frozen modules
|
|
|
|
|
|
|
|
class FrozenModule(namedtuple('FrozenModule', 'name ispkg section source')):
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
return getattr(self.source, name)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def modname(self):
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
def summarize(self):
|
|
|
|
source = self.source.modname
|
|
|
|
if source:
|
|
|
|
source = f'<{source}>'
|
|
|
|
else:
|
2021-09-15 14:11:12 -03:00
|
|
|
source = relpath_for_posix_display(self.pyfile, ROOT_DIR)
|
2021-09-13 19:18:37 -03:00
|
|
|
return {
|
|
|
|
'module': self.name,
|
|
|
|
'ispkg': self.ispkg,
|
|
|
|
'source': source,
|
|
|
|
'frozen': os.path.basename(self.frozenfile),
|
|
|
|
'checksum': _get_checksum(self.frozenfile),
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _iter_sources(modules):
|
|
|
|
seen = set()
|
|
|
|
for mod in modules:
|
|
|
|
if mod.source not in seen:
|
|
|
|
yield mod.source
|
|
|
|
seen.add(mod.source)
|
2021-08-30 20:25:11 -03:00
|
|
|
|
|
|
|
|
|
|
|
#######################################
|
|
|
|
# generic helpers
|
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
def _get_checksum(filename):
|
|
|
|
with open(filename) as infile:
|
|
|
|
text = infile.read()
|
|
|
|
m = hashlib.sha256()
|
|
|
|
m.update(text.encode('utf8'))
|
|
|
|
return m.hexdigest()
|
|
|
|
|
|
|
|
|
2021-08-30 20:25:11 -03:00
|
|
|
def resolve_modules(modname, pyfile=None):
|
|
|
|
if modname.startswith('<') and modname.endswith('>'):
|
|
|
|
if pyfile:
|
|
|
|
assert os.path.isdir(pyfile) or os.path.basename(pyfile) == '__init__.py', pyfile
|
|
|
|
ispkg = True
|
|
|
|
modname = modname[1:-1]
|
|
|
|
rawname = modname
|
|
|
|
# For now, we only expect match patterns at the end of the name.
|
|
|
|
_modname, sep, match = modname.rpartition('.')
|
|
|
|
if sep:
|
|
|
|
if _modname.endswith('.**'):
|
|
|
|
modname = _modname[:-3]
|
|
|
|
match = f'**.{match}'
|
|
|
|
elif match and not match.isidentifier():
|
|
|
|
modname = _modname
|
|
|
|
# Otherwise it's a plain name so we leave it alone.
|
|
|
|
else:
|
|
|
|
match = None
|
|
|
|
else:
|
|
|
|
ispkg = False
|
|
|
|
rawname = modname
|
|
|
|
match = None
|
|
|
|
|
|
|
|
if not check_modname(modname):
|
|
|
|
raise ValueError(f'not a valid module name ({rawname})')
|
|
|
|
|
|
|
|
if not pyfile:
|
|
|
|
pyfile = _resolve_module(modname, ispkg=ispkg)
|
|
|
|
elif os.path.isdir(pyfile):
|
|
|
|
pyfile = _resolve_module(modname, pyfile, ispkg)
|
|
|
|
yield modname, pyfile, ispkg
|
|
|
|
|
|
|
|
if match:
|
|
|
|
pkgdir = os.path.dirname(pyfile)
|
|
|
|
yield from iter_submodules(modname, pkgdir, match)
|
|
|
|
|
|
|
|
|
|
|
|
def check_modname(modname):
|
|
|
|
return all(n.isidentifier() for n in modname.split('.'))
|
|
|
|
|
|
|
|
|
|
|
|
def iter_submodules(pkgname, pkgdir=None, match='*'):
|
|
|
|
if not pkgdir:
|
|
|
|
pkgdir = os.path.join(STDLIB_DIR, *pkgname.split('.'))
|
|
|
|
if not match:
|
|
|
|
match = '**.*'
|
|
|
|
match_modname = _resolve_modname_matcher(match, pkgdir)
|
|
|
|
|
|
|
|
def _iter_submodules(pkgname, pkgdir):
|
|
|
|
for entry in sorted(os.scandir(pkgdir), key=lambda e: e.name):
|
|
|
|
matched, recursive = match_modname(entry.name)
|
|
|
|
if not matched:
|
|
|
|
continue
|
|
|
|
modname = f'{pkgname}.{entry.name}'
|
|
|
|
if modname.endswith('.py'):
|
|
|
|
yield modname[:-3], entry.path, False
|
|
|
|
elif entry.is_dir():
|
|
|
|
pyfile = os.path.join(entry.path, '__init__.py')
|
|
|
|
# We ignore namespace packages.
|
|
|
|
if os.path.exists(pyfile):
|
|
|
|
yield modname, pyfile, True
|
|
|
|
if recursive:
|
|
|
|
yield from _iter_submodules(modname, entry.path)
|
|
|
|
|
|
|
|
return _iter_submodules(pkgname, pkgdir)
|
|
|
|
|
|
|
|
|
|
|
|
def _resolve_modname_matcher(match, rootdir=None):
|
|
|
|
if isinstance(match, str):
|
|
|
|
if match.startswith('**.'):
|
|
|
|
recursive = True
|
|
|
|
pat = match[3:]
|
|
|
|
assert match
|
|
|
|
else:
|
|
|
|
recursive = False
|
|
|
|
pat = match
|
|
|
|
|
|
|
|
if pat == '*':
|
|
|
|
def match_modname(modname):
|
|
|
|
return True, recursive
|
|
|
|
else:
|
|
|
|
raise NotImplementedError(match)
|
|
|
|
elif callable(match):
|
|
|
|
match_modname = match(rootdir)
|
|
|
|
else:
|
|
|
|
raise ValueError(f'unsupported matcher {match!r}')
|
|
|
|
return match_modname
|
|
|
|
|
|
|
|
|
|
|
|
def _resolve_module(modname, pathentry=STDLIB_DIR, ispkg=False):
|
|
|
|
assert pathentry, pathentry
|
|
|
|
pathentry = os.path.normpath(pathentry)
|
|
|
|
assert os.path.isabs(pathentry)
|
|
|
|
if ispkg:
|
|
|
|
return os.path.join(pathentry, *modname.split('.'), '__init__.py')
|
|
|
|
return os.path.join(pathentry, *modname.split('.')) + '.py'
|
|
|
|
|
|
|
|
|
|
|
|
#######################################
|
|
|
|
# regenerating dependent files
|
|
|
|
|
|
|
|
def find_marker(lines, marker, file):
|
|
|
|
for pos, line in enumerate(lines):
|
|
|
|
if marker in line:
|
|
|
|
return pos
|
|
|
|
raise Exception(f"Can't find {marker!r} in file {file}")
|
|
|
|
|
|
|
|
|
|
|
|
def replace_block(lines, start_marker, end_marker, replacements, file):
|
|
|
|
start_pos = find_marker(lines, start_marker, file)
|
|
|
|
end_pos = find_marker(lines, end_marker, file)
|
|
|
|
if end_pos <= start_pos:
|
|
|
|
raise Exception(f"End marker {end_marker!r} "
|
|
|
|
f"occurs before start marker {start_marker!r} "
|
|
|
|
f"in file {file}")
|
2021-09-15 14:11:12 -03:00
|
|
|
replacements = [line.rstrip() + '\n' for line in replacements]
|
2021-08-30 20:25:11 -03:00
|
|
|
return lines[:start_pos + 1] + replacements + lines[end_pos:]
|
|
|
|
|
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
def regen_manifest(modules):
|
|
|
|
header = 'module ispkg source frozen checksum'.split()
|
|
|
|
widths = [5] * len(header)
|
|
|
|
rows = []
|
|
|
|
for mod in modules:
|
|
|
|
info = mod.summarize()
|
|
|
|
row = []
|
|
|
|
for i, col in enumerate(header):
|
|
|
|
value = info[col]
|
|
|
|
if col == 'checksum':
|
|
|
|
value = value[:12]
|
|
|
|
elif col == 'ispkg':
|
|
|
|
value = 'YES' if value else 'no'
|
|
|
|
widths[i] = max(widths[i], len(value))
|
|
|
|
row.append(value or '-')
|
|
|
|
rows.append(row)
|
|
|
|
|
|
|
|
modlines = [
|
|
|
|
'# The list of frozen modules with key information.',
|
|
|
|
'# Note that the "check_generated_files" CI job will identify',
|
|
|
|
'# when source files were changed but regen-frozen wasn\'t run.',
|
|
|
|
'# This file is auto-generated by Tools/scripts/freeze_modules.py.',
|
|
|
|
' '.join(c.center(w) for c, w in zip(header, widths)).rstrip(),
|
|
|
|
' '.join('-' * w for w in widths),
|
|
|
|
]
|
|
|
|
for row in rows:
|
|
|
|
for i, w in enumerate(widths):
|
|
|
|
if header[i] == 'ispkg':
|
|
|
|
row[i] = row[i].center(w)
|
|
|
|
else:
|
|
|
|
row[i] = row[i].ljust(w)
|
|
|
|
modlines.append(' '.join(row).rstrip())
|
2021-08-30 20:25:11 -03:00
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
print(f'# Updating {os.path.relpath(MANIFEST)}')
|
|
|
|
with open(MANIFEST, 'w') as outfile:
|
|
|
|
lines = (l + '\n' for l in modlines)
|
|
|
|
outfile.writelines(lines)
|
|
|
|
|
|
|
|
|
|
|
|
def regen_frozen(modules):
|
2021-08-30 20:25:11 -03:00
|
|
|
headerlines = []
|
|
|
|
parentdir = os.path.dirname(FROZEN_FILE)
|
2021-09-13 19:18:37 -03:00
|
|
|
for src in _iter_sources(modules):
|
2021-08-30 20:25:11 -03:00
|
|
|
# Adding a comment to separate sections here doesn't add much,
|
|
|
|
# so we don't.
|
2021-09-15 14:11:12 -03:00
|
|
|
header = relpath_for_posix_display(src.frozenfile, parentdir)
|
2021-08-30 20:25:11 -03:00
|
|
|
headerlines.append(f'#include "{header}"')
|
|
|
|
|
|
|
|
deflines = []
|
|
|
|
indent = ' '
|
|
|
|
lastsection = None
|
2021-09-13 19:18:37 -03:00
|
|
|
for mod in modules:
|
|
|
|
if mod.section != lastsection:
|
2021-08-30 20:25:11 -03:00
|
|
|
if lastsection is not None:
|
|
|
|
deflines.append('')
|
2021-09-13 19:18:37 -03:00
|
|
|
deflines.append(f'/* {mod.section} */')
|
|
|
|
lastsection = mod.section
|
2021-08-30 20:25:11 -03:00
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
symbol = mod.symbol
|
|
|
|
pkg = '-' if mod.ispkg else ''
|
2021-08-30 20:25:11 -03:00
|
|
|
line = ('{"%s", %s, %s(int)sizeof(%s)},'
|
2021-09-13 19:18:37 -03:00
|
|
|
) % (mod.name, symbol, pkg, symbol)
|
2021-08-30 20:25:11 -03:00
|
|
|
# TODO: Consider not folding lines
|
|
|
|
if len(line) < 80:
|
|
|
|
deflines.append(line)
|
|
|
|
else:
|
|
|
|
line1, _, line2 = line.rpartition(' ')
|
|
|
|
deflines.append(line1)
|
|
|
|
deflines.append(indent + line2)
|
|
|
|
|
|
|
|
if not deflines[0]:
|
|
|
|
del deflines[0]
|
|
|
|
for i, line in enumerate(deflines):
|
|
|
|
if line:
|
|
|
|
deflines[i] = indent + line
|
|
|
|
|
|
|
|
print(f'# Updating {os.path.relpath(FROZEN_FILE)}')
|
|
|
|
with updating_file_with_tmpfile(FROZEN_FILE) as (infile, outfile):
|
|
|
|
lines = infile.readlines()
|
|
|
|
# TODO: Use more obvious markers, e.g.
|
|
|
|
# $START GENERATED FOOBAR$ / $END GENERATED FOOBAR$
|
|
|
|
lines = replace_block(
|
|
|
|
lines,
|
|
|
|
"/* Includes for frozen modules: */",
|
|
|
|
"/* End includes */",
|
|
|
|
headerlines,
|
|
|
|
FROZEN_FILE,
|
|
|
|
)
|
|
|
|
lines = replace_block(
|
|
|
|
lines,
|
|
|
|
"static const struct _frozen _PyImport_FrozenModules[] =",
|
|
|
|
"/* sentinel */",
|
|
|
|
deflines,
|
|
|
|
FROZEN_FILE,
|
|
|
|
)
|
|
|
|
outfile.writelines(lines)
|
|
|
|
|
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
def regen_makefile(modules):
|
2021-08-30 20:25:11 -03:00
|
|
|
frozenfiles = []
|
|
|
|
rules = ['']
|
2021-09-13 19:18:37 -03:00
|
|
|
for src in _iter_sources(modules):
|
2021-09-15 14:11:12 -03:00
|
|
|
header = relpath_for_posix_display(src.frozenfile, ROOT_DIR)
|
2021-09-16 17:20:52 -03:00
|
|
|
frozenfiles.append(f'\t\t{header} \\')
|
2021-08-30 20:25:11 -03:00
|
|
|
|
2021-09-15 14:11:12 -03:00
|
|
|
pyfile = relpath_for_posix_display(src.pyfile, ROOT_DIR)
|
2021-08-30 20:25:11 -03:00
|
|
|
# Note that we freeze the module to the target .h file
|
|
|
|
# instead of going through an intermediate file like we used to.
|
2021-09-13 19:18:37 -03:00
|
|
|
rules.append(f'{header}: Programs/_freeze_module {pyfile}')
|
2021-09-15 13:19:30 -03:00
|
|
|
rules.append(
|
|
|
|
(f'\t$(srcdir)/Programs/_freeze_module {src.frozenid} '
|
|
|
|
f'$(srcdir)/{pyfile} $(srcdir)/{header}'))
|
2021-08-30 20:25:11 -03:00
|
|
|
rules.append('')
|
|
|
|
|
|
|
|
frozenfiles[-1] = frozenfiles[-1].rstrip(" \\")
|
|
|
|
|
|
|
|
print(f'# Updating {os.path.relpath(MAKEFILE)}')
|
|
|
|
with updating_file_with_tmpfile(MAKEFILE) as (infile, outfile):
|
|
|
|
lines = infile.readlines()
|
|
|
|
lines = replace_block(
|
|
|
|
lines,
|
|
|
|
"FROZEN_FILES =",
|
|
|
|
"# End FROZEN_FILES",
|
|
|
|
frozenfiles,
|
|
|
|
MAKEFILE,
|
|
|
|
)
|
|
|
|
lines = replace_block(
|
|
|
|
lines,
|
|
|
|
"# BEGIN: freezing modules",
|
|
|
|
"# END: freezing modules",
|
|
|
|
rules,
|
|
|
|
MAKEFILE,
|
|
|
|
)
|
|
|
|
outfile.writelines(lines)
|
|
|
|
|
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
def regen_pcbuild(modules):
|
2021-08-30 20:25:11 -03:00
|
|
|
projlines = []
|
|
|
|
filterlines = []
|
2021-09-13 19:18:37 -03:00
|
|
|
for src in _iter_sources(modules):
|
2021-09-15 14:11:12 -03:00
|
|
|
pyfile = relpath_for_windows_display(src.pyfile, ROOT_DIR)
|
|
|
|
header = relpath_for_windows_display(src.frozenfile, ROOT_DIR)
|
|
|
|
intfile = ntpath.splitext(ntpath.basename(header))[0] + '.g.h'
|
2021-09-13 19:18:37 -03:00
|
|
|
projlines.append(f' <None Include="..\\{pyfile}">')
|
|
|
|
projlines.append(f' <ModName>{src.frozenid}</ModName>')
|
2021-08-30 20:25:11 -03:00
|
|
|
projlines.append(f' <IntFile>$(IntDir){intfile}</IntFile>')
|
|
|
|
projlines.append(f' <OutFile>$(PySourcePath){header}</OutFile>')
|
|
|
|
projlines.append(f' </None>')
|
|
|
|
|
2021-09-13 19:18:37 -03:00
|
|
|
filterlines.append(f' <None Include="..\\{pyfile}">')
|
2021-08-30 20:25:11 -03:00
|
|
|
filterlines.append(' <Filter>Python Files</Filter>')
|
|
|
|
filterlines.append(' </None>')
|
|
|
|
|
|
|
|
print(f'# Updating {os.path.relpath(PCBUILD_PROJECT)}')
|
|
|
|
with updating_file_with_tmpfile(PCBUILD_PROJECT) as (infile, outfile):
|
|
|
|
lines = infile.readlines()
|
|
|
|
lines = replace_block(
|
|
|
|
lines,
|
|
|
|
'<!-- BEGIN frozen modules -->',
|
|
|
|
'<!-- END frozen modules -->',
|
|
|
|
projlines,
|
|
|
|
PCBUILD_PROJECT,
|
|
|
|
)
|
|
|
|
outfile.writelines(lines)
|
|
|
|
print(f'# Updating {os.path.relpath(PCBUILD_FILTERS)}')
|
|
|
|
with updating_file_with_tmpfile(PCBUILD_FILTERS) as (infile, outfile):
|
|
|
|
lines = infile.readlines()
|
|
|
|
lines = replace_block(
|
|
|
|
lines,
|
|
|
|
'<!-- BEGIN frozen modules -->',
|
|
|
|
'<!-- END frozen modules -->',
|
|
|
|
filterlines,
|
|
|
|
PCBUILD_FILTERS,
|
|
|
|
)
|
|
|
|
outfile.writelines(lines)
|
|
|
|
|
|
|
|
|
|
|
|
#######################################
|
|
|
|
# freezing modules
|
|
|
|
|
|
|
|
def freeze_module(modname, pyfile=None, destdir=MODULES_DIR):
|
|
|
|
"""Generate the frozen module .h file for the given module."""
|
|
|
|
for modname, pyfile, ispkg in resolve_modules(modname, pyfile):
|
2021-09-13 19:18:37 -03:00
|
|
|
frozenfile = resolve_frozen_file(modname, destdir)
|
2021-08-30 20:25:11 -03:00
|
|
|
_freeze_module(modname, pyfile, frozenfile)
|
|
|
|
|
|
|
|
|
|
|
|
def _freeze_module(frozenid, pyfile, frozenfile):
|
|
|
|
tmpfile = frozenfile + '.new'
|
|
|
|
|
|
|
|
argv = [TOOL, frozenid, pyfile, tmpfile]
|
2021-09-13 19:18:37 -03:00
|
|
|
print('#', ' '.join(os.path.relpath(a) for a in argv), flush=True)
|
2021-08-30 20:25:11 -03:00
|
|
|
try:
|
|
|
|
subprocess.run(argv, check=True)
|
2021-09-15 14:11:12 -03:00
|
|
|
except (FileNotFoundError, subprocess.CalledProcessError):
|
2021-08-30 20:25:11 -03:00
|
|
|
if not os.path.exists(TOOL):
|
|
|
|
sys.exit(f'ERROR: missing {TOOL}; you need to run "make regen-frozen"')
|
|
|
|
raise # re-raise
|
|
|
|
|
|
|
|
os.replace(tmpfile, frozenfile)
|
|
|
|
|
|
|
|
|
|
|
|
#######################################
|
|
|
|
# the script
|
|
|
|
|
|
|
|
def main():
|
|
|
|
# Expand the raw specs, preserving order.
|
2021-09-13 19:18:37 -03:00
|
|
|
modules = list(parse_frozen_specs(destdir=MODULES_DIR))
|
2021-08-30 20:25:11 -03:00
|
|
|
|
|
|
|
# Freeze the target modules.
|
2021-09-13 19:18:37 -03:00
|
|
|
for src in _iter_sources(modules):
|
|
|
|
_freeze_module(src.frozenid, src.pyfile, src.frozenfile)
|
|
|
|
|
|
|
|
# Regen build-related files.
|
|
|
|
regen_manifest(modules)
|
|
|
|
regen_frozen(modules)
|
|
|
|
regen_makefile(modules)
|
|
|
|
regen_pcbuild(modules)
|
2021-08-30 20:25:11 -03:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
argv = sys.argv[1:]
|
|
|
|
if argv:
|
|
|
|
sys.exit('ERROR: got unexpected args {argv}')
|
|
|
|
main()
|