summaryrefslogtreecommitdiffstats
path: root/Tools/wasm/wasm_assets.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/wasm/wasm_assets.py')
-rwxr-xr-xTools/wasm/wasm_assets.py164
1 files changed, 115 insertions, 49 deletions
diff --git a/Tools/wasm/wasm_assets.py b/Tools/wasm/wasm_assets.py
index bb1983a..fba70b9 100755
--- a/Tools/wasm/wasm_assets.py
+++ b/Tools/wasm/wasm_assets.py
@@ -20,7 +20,11 @@ SRCDIR = pathlib.Path(__file__).parent.parent.parent.absolute()
SRCDIR_LIB = SRCDIR / "Lib"
# sysconfig data relative to build dir.
-SYSCONFIGDATA_GLOB = "build/lib.*/_sysconfigdata_*.py"
+SYSCONFIGDATA = pathlib.PurePath(
+ "build",
+ f"lib.emscripten-wasm32-{sys.version_info.major}.{sys.version_info.minor}",
+ "_sysconfigdata__emscripten_wasm32-emscripten.py",
+)
# Library directory relative to $(prefix).
WASM_LIB = pathlib.PurePath("lib")
@@ -38,33 +42,44 @@ WASM_DYNLOAD = WASM_STDLIB / "lib-dynload"
OMIT_FILES = (
# regression tests
"test/",
- # user interfaces: TK, curses
- "curses/",
- "idlelib/",
- "tkinter/",
- "turtle.py",
- "turtledemo/",
# package management
"ensurepip/",
"venv/",
# build system
"distutils/",
"lib2to3/",
- # concurrency
- "concurrent/",
- "multiprocessing/",
# deprecated
"asyncore.py",
"asynchat.py",
- # Synchronous network I/O and protocols are not supported; for example,
- # socket.create_connection() raises an exception:
- # "BlockingIOError: [Errno 26] Operation in progress".
+ "uu.py",
+ "xdrlib.py",
+ # other platforms
+ "_aix_support.py",
+ "_bootsubprocess.py",
+ "_osx_support.py",
+ # webbrowser
+ "antigravity.py",
+ "webbrowser.py",
+ # Pure Python implementations of C extensions
+ "_pydecimal.py",
+ "_pyio.py",
+ # Misc unused or large files
+ "pydoc_data/",
+ "msilib/",
+)
+
+# Synchronous network I/O and protocols are not supported; for example,
+# socket.create_connection() raises an exception:
+# "BlockingIOError: [Errno 26] Operation in progress".
+OMIT_NETWORKING_FILES = (
"cgi.py",
"cgitb.py",
"email/",
"ftplib.py",
"http/",
"imaplib.py",
+ "mailbox.py",
+ "mailcap.py",
"nntplib.py",
"poplib.py",
"smtpd.py",
@@ -77,26 +92,28 @@ OMIT_FILES = (
"urllib/response.py",
"urllib/robotparser.py",
"wsgiref/",
- "xmlrpc/",
- # dbm / gdbm
- "dbm/",
- # other platforms
- "_aix_support.py",
- "_bootsubprocess.py",
- "_osx_support.py",
- # webbrowser
- "antigravity.py",
- "webbrowser.py",
- # ctypes
- "ctypes/",
- # Pure Python implementations of C extensions
- "_pydecimal.py",
- "_pyio.py",
- # Misc unused or large files
- "pydoc_data/",
- "msilib/",
)
+OMIT_MODULE_FILES = {
+ "_asyncio": ["asyncio/"],
+ "audioop": ["aifc.py", "sunau.py", "wave.py"],
+ "_crypt": ["crypt.py"],
+ "_curses": ["curses/"],
+ "_ctypes": ["ctypes/"],
+ "_decimal": ["decimal.py"],
+ "_dbm": ["dbm/ndbm.py"],
+ "_gdbm": ["dbm/gnu.py"],
+ "_json": ["json/"],
+ "_multiprocessing": ["concurrent/", "multiprocessing/"],
+ "pyexpat": ["xml/", "xmlrpc/"],
+ "readline": ["rlcompleter.py"],
+ "_sqlite3": ["sqlite3/"],
+ "_ssl": ["ssl.py"],
+ "_tkinter": ["idlelib/", "tkinter/", "turtle.py", "turtledemo/"],
+
+ "_zoneinfo": ["zoneinfo/"],
+}
+
# regression test sub directories
OMIT_SUBDIRS = (
"ctypes/test/",
@@ -105,34 +122,59 @@ OMIT_SUBDIRS = (
)
-OMIT_ABSOLUTE = {SRCDIR_LIB / name for name in OMIT_FILES}
-OMIT_SUBDIRS_ABSOLUTE = tuple(str(SRCDIR_LIB / name) for name in OMIT_SUBDIRS)
-
-
-def filterfunc(name: str) -> bool:
- return not name.startswith(OMIT_SUBDIRS_ABSOLUTE)
-
-
def create_stdlib_zip(
- args: argparse.Namespace, compression: int = zipfile.ZIP_DEFLATED, *, optimize: int = 0
+ args: argparse.Namespace,
+ *,
+ optimize: int = 0,
) -> None:
- sysconfig_data = list(args.builddir.glob(SYSCONFIGDATA_GLOB))
- if not sysconfig_data:
- raise ValueError("No sysconfigdata file found")
+ def filterfunc(name: str) -> bool:
+ return not name.startswith(args.omit_subdirs_absolute)
with zipfile.PyZipFile(
- args.wasm_stdlib_zip, mode="w", compression=compression, optimize=0
+ args.wasm_stdlib_zip, mode="w", compression=args.compression, optimize=optimize
) as pzf:
+ if args.compresslevel is not None:
+ pzf.compresslevel = args.compresslevel
+ pzf.writepy(args.sysconfig_data)
for entry in sorted(args.srcdir_lib.iterdir()):
if entry.name == "__pycache__":
continue
- if entry in OMIT_ABSOLUTE:
+ if entry in args.omit_files_absolute:
continue
if entry.name.endswith(".py") or entry.is_dir():
# writepy() writes .pyc files (bytecode).
pzf.writepy(entry, filterfunc=filterfunc)
- for entry in sysconfig_data:
- pzf.writepy(entry)
+
+
+def detect_extension_modules(args: argparse.Namespace):
+ modules = {}
+
+ # disabled by Modules/Setup.local ?
+ with open(args.builddir / "Makefile") as f:
+ for line in f:
+ if line.startswith("MODDISABLED_NAMES="):
+ disabled = line.split("=", 1)[1].strip().split()
+ for modname in disabled:
+ modules[modname] = False
+ break
+
+ # disabled by configure?
+ with open(args.sysconfig_data) as f:
+ data = f.read()
+ loc = {}
+ exec(data, globals(), loc)
+
+ for name, value in loc["build_time_vars"].items():
+ if value not in {"yes", "missing", "disabled", "n/a"}:
+ continue
+ if not name.startswith("MODULE_"):
+ continue
+ if name.endswith(("_CFLAGS", "_DEPS", "_LDFLAGS")):
+ continue
+ modname = name.removeprefix("MODULE_").lower()
+ if modname not in modules:
+ modules[modname] = value == "yes"
+ return modules
def path(val: str) -> pathlib.Path:
@@ -147,7 +189,10 @@ parser.add_argument(
type=path,
)
parser.add_argument(
- "--prefix", help="install prefix", default=pathlib.Path("/usr/local"), type=path
+ "--prefix",
+ help="install prefix",
+ default=pathlib.Path("/usr/local"),
+ type=path,
)
@@ -162,6 +207,27 @@ def main():
args.wasm_stdlib = args.wasm_root / WASM_STDLIB
args.wasm_dynload = args.wasm_root / WASM_DYNLOAD
+ # bpo-17004: zipimport supports only zlib compression.
+ # Emscripten ZIP_STORED + -sLZ4=1 linker flags results in larger file.
+ args.compression = zipfile.ZIP_DEFLATED
+ args.compresslevel = 9
+
+ args.sysconfig_data = args.builddir / SYSCONFIGDATA
+ if not args.sysconfig_data.is_file():
+ raise ValueError(f"sysconfigdata file {SYSCONFIGDATA} missing.")
+
+ extmods = detect_extension_modules(args)
+ omit_files = list(OMIT_FILES)
+ omit_files.extend(OMIT_NETWORKING_FILES)
+ for modname, modfiles in OMIT_MODULE_FILES.items():
+ if not extmods.get(modname):
+ omit_files.extend(modfiles)
+
+ args.omit_files_absolute = {args.srcdir_lib / name for name in omit_files}
+ args.omit_subdirs_absolute = tuple(
+ str(args.srcdir_lib / name) for name in OMIT_SUBDIRS
+ )
+
# Empty, unused directory for dynamic libs, but required for site initialization.
args.wasm_dynload.mkdir(parents=True, exist_ok=True)
marker = args.wasm_dynload / ".empty"
@@ -170,7 +236,7 @@ def main():
shutil.copy(args.srcdir_lib / "os.py", args.wasm_stdlib)
# The rest of stdlib that's useful in a WASM context.
create_stdlib_zip(args)
- size = round(args.wasm_stdlib_zip.stat().st_size / 1024 ** 2, 2)
+ size = round(args.wasm_stdlib_zip.stat().st_size / 1024**2, 2)
parser.exit(0, f"Created {args.wasm_stdlib_zip} ({size} MiB)\n")