17db96d56Sopenharmony_ci#!/usr/bin/env python 27db96d56Sopenharmony_ci"""Create a WASM asset bundle directory structure. 37db96d56Sopenharmony_ci 47db96d56Sopenharmony_ciThe WASM asset bundles are pre-loaded by the final WASM build. The bundle 57db96d56Sopenharmony_cicontains: 67db96d56Sopenharmony_ci 77db96d56Sopenharmony_ci- a stripped down, pyc-only stdlib zip file, e.g. {PREFIX}/lib/python311.zip 87db96d56Sopenharmony_ci- os.py as marker module {PREFIX}/lib/python3.11/os.py 97db96d56Sopenharmony_ci- empty lib-dynload directory, to make sure it is copied into the bundle {PREFIX}/lib/python3.11/lib-dynload/.empty 107db96d56Sopenharmony_ci""" 117db96d56Sopenharmony_ci 127db96d56Sopenharmony_ciimport argparse 137db96d56Sopenharmony_ciimport pathlib 147db96d56Sopenharmony_ciimport shutil 157db96d56Sopenharmony_ciimport sys 167db96d56Sopenharmony_ciimport sysconfig 177db96d56Sopenharmony_ciimport zipfile 187db96d56Sopenharmony_ci 197db96d56Sopenharmony_ci# source directory 207db96d56Sopenharmony_ciSRCDIR = pathlib.Path(__file__).parent.parent.parent.absolute() 217db96d56Sopenharmony_ciSRCDIR_LIB = SRCDIR / "Lib" 227db96d56Sopenharmony_ci 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_ci# Library directory relative to $(prefix). 257db96d56Sopenharmony_ciWASM_LIB = pathlib.PurePath("lib") 267db96d56Sopenharmony_ciWASM_STDLIB_ZIP = ( 277db96d56Sopenharmony_ci WASM_LIB / f"python{sys.version_info.major}{sys.version_info.minor}.zip" 287db96d56Sopenharmony_ci) 297db96d56Sopenharmony_ciWASM_STDLIB = ( 307db96d56Sopenharmony_ci WASM_LIB / f"python{sys.version_info.major}.{sys.version_info.minor}" 317db96d56Sopenharmony_ci) 327db96d56Sopenharmony_ciWASM_DYNLOAD = WASM_STDLIB / "lib-dynload" 337db96d56Sopenharmony_ci 347db96d56Sopenharmony_ci 357db96d56Sopenharmony_ci# Don't ship large files / packages that are not particularly useful at 367db96d56Sopenharmony_ci# the moment. 377db96d56Sopenharmony_ciOMIT_FILES = ( 387db96d56Sopenharmony_ci # regression tests 397db96d56Sopenharmony_ci "test/", 407db96d56Sopenharmony_ci # package management 417db96d56Sopenharmony_ci "ensurepip/", 427db96d56Sopenharmony_ci "venv/", 437db96d56Sopenharmony_ci # build system 447db96d56Sopenharmony_ci "distutils/", 457db96d56Sopenharmony_ci "lib2to3/", 467db96d56Sopenharmony_ci # deprecated 477db96d56Sopenharmony_ci "asyncore.py", 487db96d56Sopenharmony_ci "asynchat.py", 497db96d56Sopenharmony_ci "uu.py", 507db96d56Sopenharmony_ci "xdrlib.py", 517db96d56Sopenharmony_ci # other platforms 527db96d56Sopenharmony_ci "_aix_support.py", 537db96d56Sopenharmony_ci "_bootsubprocess.py", 547db96d56Sopenharmony_ci "_osx_support.py", 557db96d56Sopenharmony_ci # webbrowser 567db96d56Sopenharmony_ci "antigravity.py", 577db96d56Sopenharmony_ci "webbrowser.py", 587db96d56Sopenharmony_ci # Pure Python implementations of C extensions 597db96d56Sopenharmony_ci "_pydecimal.py", 607db96d56Sopenharmony_ci "_pyio.py", 617db96d56Sopenharmony_ci # concurrent threading 627db96d56Sopenharmony_ci "concurrent/futures/thread.py", 637db96d56Sopenharmony_ci # Misc unused or large files 647db96d56Sopenharmony_ci "pydoc_data/", 657db96d56Sopenharmony_ci "msilib/", 667db96d56Sopenharmony_ci) 677db96d56Sopenharmony_ci 687db96d56Sopenharmony_ci# Synchronous network I/O and protocols are not supported; for example, 697db96d56Sopenharmony_ci# socket.create_connection() raises an exception: 707db96d56Sopenharmony_ci# "BlockingIOError: [Errno 26] Operation in progress". 717db96d56Sopenharmony_ciOMIT_NETWORKING_FILES = ( 727db96d56Sopenharmony_ci "cgi.py", 737db96d56Sopenharmony_ci "cgitb.py", 747db96d56Sopenharmony_ci "email/", 757db96d56Sopenharmony_ci "ftplib.py", 767db96d56Sopenharmony_ci "http/", 777db96d56Sopenharmony_ci "imaplib.py", 787db96d56Sopenharmony_ci "mailbox.py", 797db96d56Sopenharmony_ci "mailcap.py", 807db96d56Sopenharmony_ci "nntplib.py", 817db96d56Sopenharmony_ci "poplib.py", 827db96d56Sopenharmony_ci "smtpd.py", 837db96d56Sopenharmony_ci "smtplib.py", 847db96d56Sopenharmony_ci "socketserver.py", 857db96d56Sopenharmony_ci "telnetlib.py", 867db96d56Sopenharmony_ci # keep urllib.parse for pydoc 877db96d56Sopenharmony_ci "urllib/error.py", 887db96d56Sopenharmony_ci "urllib/request.py", 897db96d56Sopenharmony_ci "urllib/response.py", 907db96d56Sopenharmony_ci "urllib/robotparser.py", 917db96d56Sopenharmony_ci "wsgiref/", 927db96d56Sopenharmony_ci) 937db96d56Sopenharmony_ci 947db96d56Sopenharmony_ciOMIT_MODULE_FILES = { 957db96d56Sopenharmony_ci "_asyncio": ["asyncio/"], 967db96d56Sopenharmony_ci "audioop": ["aifc.py", "sunau.py", "wave.py"], 977db96d56Sopenharmony_ci "_crypt": ["crypt.py"], 987db96d56Sopenharmony_ci "_curses": ["curses/"], 997db96d56Sopenharmony_ci "_ctypes": ["ctypes/"], 1007db96d56Sopenharmony_ci "_decimal": ["decimal.py"], 1017db96d56Sopenharmony_ci "_dbm": ["dbm/ndbm.py"], 1027db96d56Sopenharmony_ci "_gdbm": ["dbm/gnu.py"], 1037db96d56Sopenharmony_ci "_json": ["json/"], 1047db96d56Sopenharmony_ci "_multiprocessing": ["concurrent/futures/process.py", "multiprocessing/"], 1057db96d56Sopenharmony_ci "pyexpat": ["xml/", "xmlrpc/"], 1067db96d56Sopenharmony_ci "readline": ["rlcompleter.py"], 1077db96d56Sopenharmony_ci "_sqlite3": ["sqlite3/"], 1087db96d56Sopenharmony_ci "_ssl": ["ssl.py"], 1097db96d56Sopenharmony_ci "_tkinter": ["idlelib/", "tkinter/", "turtle.py", "turtledemo/"], 1107db96d56Sopenharmony_ci "_zoneinfo": ["zoneinfo/"], 1117db96d56Sopenharmony_ci} 1127db96d56Sopenharmony_ci 1137db96d56Sopenharmony_ci# regression test sub directories 1147db96d56Sopenharmony_ciOMIT_SUBDIRS = ( 1157db96d56Sopenharmony_ci "ctypes/test/", 1167db96d56Sopenharmony_ci "tkinter/test/", 1177db96d56Sopenharmony_ci "unittest/test/", 1187db96d56Sopenharmony_ci) 1197db96d56Sopenharmony_ci 1207db96d56Sopenharmony_ciSYSCONFIG_NAMES = ( 1217db96d56Sopenharmony_ci "_sysconfigdata__emscripten_wasm32-emscripten", 1227db96d56Sopenharmony_ci "_sysconfigdata__emscripten_wasm32-emscripten", 1237db96d56Sopenharmony_ci "_sysconfigdata__wasi_wasm32-wasi", 1247db96d56Sopenharmony_ci "_sysconfigdata__wasi_wasm64-wasi", 1257db96d56Sopenharmony_ci) 1267db96d56Sopenharmony_ci 1277db96d56Sopenharmony_ci 1287db96d56Sopenharmony_cidef get_builddir(args: argparse.Namespace) -> pathlib.Path: 1297db96d56Sopenharmony_ci """Get builddir path from pybuilddir.txt""" 1307db96d56Sopenharmony_ci with open("pybuilddir.txt", encoding="utf-8") as f: 1317db96d56Sopenharmony_ci builddir = f.read() 1327db96d56Sopenharmony_ci return pathlib.Path(builddir) 1337db96d56Sopenharmony_ci 1347db96d56Sopenharmony_ci 1357db96d56Sopenharmony_cidef get_sysconfigdata(args: argparse.Namespace) -> pathlib.Path: 1367db96d56Sopenharmony_ci """Get path to sysconfigdata relative to build root""" 1377db96d56Sopenharmony_ci data_name = sysconfig._get_sysconfigdata_name() 1387db96d56Sopenharmony_ci if not data_name.startswith(SYSCONFIG_NAMES): 1397db96d56Sopenharmony_ci raise ValueError( 1407db96d56Sopenharmony_ci f"Invalid sysconfig data name '{data_name}'.", SYSCONFIG_NAMES 1417db96d56Sopenharmony_ci ) 1427db96d56Sopenharmony_ci filename = data_name + ".py" 1437db96d56Sopenharmony_ci return args.builddir / filename 1447db96d56Sopenharmony_ci 1457db96d56Sopenharmony_ci 1467db96d56Sopenharmony_cidef create_stdlib_zip( 1477db96d56Sopenharmony_ci args: argparse.Namespace, 1487db96d56Sopenharmony_ci *, 1497db96d56Sopenharmony_ci optimize: int = 0, 1507db96d56Sopenharmony_ci) -> None: 1517db96d56Sopenharmony_ci def filterfunc(filename: str) -> bool: 1527db96d56Sopenharmony_ci pathname = pathlib.Path(filename).resolve() 1537db96d56Sopenharmony_ci return pathname not in args.omit_files_absolute 1547db96d56Sopenharmony_ci 1557db96d56Sopenharmony_ci with zipfile.PyZipFile( 1567db96d56Sopenharmony_ci args.wasm_stdlib_zip, 1577db96d56Sopenharmony_ci mode="w", 1587db96d56Sopenharmony_ci compression=args.compression, 1597db96d56Sopenharmony_ci optimize=optimize, 1607db96d56Sopenharmony_ci ) as pzf: 1617db96d56Sopenharmony_ci if args.compresslevel is not None: 1627db96d56Sopenharmony_ci pzf.compresslevel = args.compresslevel 1637db96d56Sopenharmony_ci pzf.writepy(args.sysconfig_data) 1647db96d56Sopenharmony_ci for entry in sorted(args.srcdir_lib.iterdir()): 1657db96d56Sopenharmony_ci entry = entry.resolve() 1667db96d56Sopenharmony_ci if entry.name == "__pycache__": 1677db96d56Sopenharmony_ci continue 1687db96d56Sopenharmony_ci if entry.name.endswith(".py") or entry.is_dir(): 1697db96d56Sopenharmony_ci # writepy() writes .pyc files (bytecode). 1707db96d56Sopenharmony_ci pzf.writepy(entry, filterfunc=filterfunc) 1717db96d56Sopenharmony_ci 1727db96d56Sopenharmony_ci 1737db96d56Sopenharmony_cidef detect_extension_modules(args: argparse.Namespace): 1747db96d56Sopenharmony_ci modules = {} 1757db96d56Sopenharmony_ci 1767db96d56Sopenharmony_ci # disabled by Modules/Setup.local ? 1777db96d56Sopenharmony_ci with open(args.buildroot / "Makefile") as f: 1787db96d56Sopenharmony_ci for line in f: 1797db96d56Sopenharmony_ci if line.startswith("MODDISABLED_NAMES="): 1807db96d56Sopenharmony_ci disabled = line.split("=", 1)[1].strip().split() 1817db96d56Sopenharmony_ci for modname in disabled: 1827db96d56Sopenharmony_ci modules[modname] = False 1837db96d56Sopenharmony_ci break 1847db96d56Sopenharmony_ci 1857db96d56Sopenharmony_ci # disabled by configure? 1867db96d56Sopenharmony_ci with open(args.sysconfig_data) as f: 1877db96d56Sopenharmony_ci data = f.read() 1887db96d56Sopenharmony_ci loc = {} 1897db96d56Sopenharmony_ci exec(data, globals(), loc) 1907db96d56Sopenharmony_ci 1917db96d56Sopenharmony_ci for key, value in loc["build_time_vars"].items(): 1927db96d56Sopenharmony_ci if not key.startswith("MODULE_") or not key.endswith("_STATE"): 1937db96d56Sopenharmony_ci continue 1947db96d56Sopenharmony_ci if value not in {"yes", "disabled", "missing", "n/a"}: 1957db96d56Sopenharmony_ci raise ValueError(f"Unsupported value '{value}' for {key}") 1967db96d56Sopenharmony_ci 1977db96d56Sopenharmony_ci modname = key[7:-6].lower() 1987db96d56Sopenharmony_ci if modname not in modules: 1997db96d56Sopenharmony_ci modules[modname] = value == "yes" 2007db96d56Sopenharmony_ci return modules 2017db96d56Sopenharmony_ci 2027db96d56Sopenharmony_ci 2037db96d56Sopenharmony_cidef path(val: str) -> pathlib.Path: 2047db96d56Sopenharmony_ci return pathlib.Path(val).absolute() 2057db96d56Sopenharmony_ci 2067db96d56Sopenharmony_ci 2077db96d56Sopenharmony_ciparser = argparse.ArgumentParser() 2087db96d56Sopenharmony_ciparser.add_argument( 2097db96d56Sopenharmony_ci "--buildroot", 2107db96d56Sopenharmony_ci help="absolute path to build root", 2117db96d56Sopenharmony_ci default=pathlib.Path(".").absolute(), 2127db96d56Sopenharmony_ci type=path, 2137db96d56Sopenharmony_ci) 2147db96d56Sopenharmony_ciparser.add_argument( 2157db96d56Sopenharmony_ci "--prefix", 2167db96d56Sopenharmony_ci help="install prefix", 2177db96d56Sopenharmony_ci default=pathlib.Path("/usr/local"), 2187db96d56Sopenharmony_ci type=path, 2197db96d56Sopenharmony_ci) 2207db96d56Sopenharmony_ci 2217db96d56Sopenharmony_ci 2227db96d56Sopenharmony_cidef main(): 2237db96d56Sopenharmony_ci args = parser.parse_args() 2247db96d56Sopenharmony_ci 2257db96d56Sopenharmony_ci relative_prefix = args.prefix.relative_to(pathlib.Path("/")) 2267db96d56Sopenharmony_ci args.srcdir = SRCDIR 2277db96d56Sopenharmony_ci args.srcdir_lib = SRCDIR_LIB 2287db96d56Sopenharmony_ci args.wasm_root = args.buildroot / relative_prefix 2297db96d56Sopenharmony_ci args.wasm_stdlib_zip = args.wasm_root / WASM_STDLIB_ZIP 2307db96d56Sopenharmony_ci args.wasm_stdlib = args.wasm_root / WASM_STDLIB 2317db96d56Sopenharmony_ci args.wasm_dynload = args.wasm_root / WASM_DYNLOAD 2327db96d56Sopenharmony_ci 2337db96d56Sopenharmony_ci # bpo-17004: zipimport supports only zlib compression. 2347db96d56Sopenharmony_ci # Emscripten ZIP_STORED + -sLZ4=1 linker flags results in larger file. 2357db96d56Sopenharmony_ci args.compression = zipfile.ZIP_DEFLATED 2367db96d56Sopenharmony_ci args.compresslevel = 9 2377db96d56Sopenharmony_ci 2387db96d56Sopenharmony_ci args.builddir = get_builddir(args) 2397db96d56Sopenharmony_ci args.sysconfig_data = get_sysconfigdata(args) 2407db96d56Sopenharmony_ci if not args.sysconfig_data.is_file(): 2417db96d56Sopenharmony_ci raise ValueError(f"sysconfigdata file {args.sysconfig_data} missing.") 2427db96d56Sopenharmony_ci 2437db96d56Sopenharmony_ci extmods = detect_extension_modules(args) 2447db96d56Sopenharmony_ci omit_files = list(OMIT_FILES) 2457db96d56Sopenharmony_ci if sysconfig.get_platform().startswith("emscripten"): 2467db96d56Sopenharmony_ci omit_files.extend(OMIT_NETWORKING_FILES) 2477db96d56Sopenharmony_ci for modname, modfiles in OMIT_MODULE_FILES.items(): 2487db96d56Sopenharmony_ci if not extmods.get(modname): 2497db96d56Sopenharmony_ci omit_files.extend(modfiles) 2507db96d56Sopenharmony_ci 2517db96d56Sopenharmony_ci args.omit_files_absolute = { 2527db96d56Sopenharmony_ci (args.srcdir_lib / name).resolve() for name in omit_files 2537db96d56Sopenharmony_ci } 2547db96d56Sopenharmony_ci 2557db96d56Sopenharmony_ci # Empty, unused directory for dynamic libs, but required for site initialization. 2567db96d56Sopenharmony_ci args.wasm_dynload.mkdir(parents=True, exist_ok=True) 2577db96d56Sopenharmony_ci marker = args.wasm_dynload / ".empty" 2587db96d56Sopenharmony_ci marker.touch() 2597db96d56Sopenharmony_ci # os.py is a marker for finding the correct lib directory. 2607db96d56Sopenharmony_ci shutil.copy(args.srcdir_lib / "os.py", args.wasm_stdlib) 2617db96d56Sopenharmony_ci # The rest of stdlib that's useful in a WASM context. 2627db96d56Sopenharmony_ci create_stdlib_zip(args) 2637db96d56Sopenharmony_ci size = round(args.wasm_stdlib_zip.stat().st_size / 1024**2, 2) 2647db96d56Sopenharmony_ci parser.exit(0, f"Created {args.wasm_stdlib_zip} ({size} MiB)\n") 2657db96d56Sopenharmony_ci 2667db96d56Sopenharmony_ci 2677db96d56Sopenharmony_ciif __name__ == "__main__": 2687db96d56Sopenharmony_ci main() 269