diff options
Diffstat (limited to 'Tools/wasm/wasm_build.py')
-rwxr-xr-x | Tools/wasm/wasm_build.py | 932 |
1 files changed, 0 insertions, 932 deletions
diff --git a/Tools/wasm/wasm_build.py b/Tools/wasm/wasm_build.py deleted file mode 100755 index bcb80212362..00000000000 --- a/Tools/wasm/wasm_build.py +++ /dev/null @@ -1,932 +0,0 @@ -#!/usr/bin/env python3 -"""Build script for Python on WebAssembly platforms. - - $ ./Tools/wasm/wasm_builder.py emscripten-browser build repl - $ ./Tools/wasm/wasm_builder.py emscripten-node-dl build test - $ ./Tools/wasm/wasm_builder.py wasi build test - -Primary build targets are "emscripten-node-dl" (NodeJS, dynamic linking), -"emscripten-browser", and "wasi". - -Emscripten builds require a recent Emscripten SDK. The tools looks for an -activated EMSDK environment (". /path/to/emsdk_env.sh"). System packages -(Debian, Homebrew) are not supported. - -WASI builds require WASI SDK and wasmtime. The tool looks for 'WASI_SDK_PATH' -and falls back to /opt/wasi-sdk. - -The 'build' Python interpreter must be rebuilt every time Python's byte code -changes. - - ./Tools/wasm/wasm_builder.py --clean build build - -""" -import argparse -import enum -import dataclasses -import logging -import os -import pathlib -import re -import shlex -import shutil -import socket -import subprocess -import sys -import sysconfig -import tempfile -import time -import warnings -import webbrowser - -# for Python 3.8 -from typing import ( - cast, - Any, - Callable, - Dict, - Iterable, - List, - Optional, - Tuple, - Union, -) - -logger = logging.getLogger("wasm_build") - -SRCDIR = pathlib.Path(__file__).parent.parent.parent.absolute() -WASMTOOLS = SRCDIR / "Tools" / "wasm" -BUILDDIR = SRCDIR / "builddir" -CONFIGURE = SRCDIR / "configure" -SETUP_LOCAL = SRCDIR / "Modules" / "Setup.local" - -HAS_CCACHE = shutil.which("ccache") is not None - -# path to WASI-SDK root -WASI_SDK_PATH = pathlib.Path(os.environ.get("WASI_SDK_PATH", "/opt/wasi-sdk")) - -# path to Emscripten SDK config file. -# auto-detect's EMSDK in /opt/emsdk without ". emsdk_env.sh". -EM_CONFIG = pathlib.Path(os.environ.setdefault("EM_CONFIG", "/opt/emsdk/.emscripten")) -EMSDK_MIN_VERSION = (3, 1, 19) -EMSDK_BROKEN_VERSION = { - (3, 1, 14): "https://github.com/emscripten-core/emscripten/issues/17338", - (3, 1, 16): "https://github.com/emscripten-core/emscripten/issues/17393", - (3, 1, 20): "https://github.com/emscripten-core/emscripten/issues/17720", -} -_MISSING = pathlib.Path("MISSING") - -WASM_WEBSERVER = WASMTOOLS / "wasm_webserver.py" - -CLEAN_SRCDIR = f""" -Builds require a clean source directory. Please use a clean checkout or -run "make clean -C '{SRCDIR}'". -""" - -INSTALL_NATIVE = """ -Builds require a C compiler (gcc, clang), make, pkg-config, and development -headers for dependencies like zlib. - -Debian/Ubuntu: sudo apt install build-essential git curl pkg-config zlib1g-dev -Fedora/CentOS: sudo dnf install gcc make git-core curl pkgconfig zlib-devel -""" - -INSTALL_EMSDK = """ -wasm32-emscripten builds need Emscripten SDK. Please follow instructions at -https://emscripten.org/docs/getting_started/downloads.html how to install -Emscripten and how to activate the SDK with "emsdk_env.sh". - - git clone https://github.com/emscripten-core/emsdk.git /path/to/emsdk - cd /path/to/emsdk - ./emsdk install latest - ./emsdk activate latest - source /path/to/emsdk_env.sh -""" - -INSTALL_WASI_SDK = """ -wasm32-wasi builds need WASI SDK. Please fetch the latest SDK from -https://github.com/WebAssembly/wasi-sdk/releases and install it to -"/opt/wasi-sdk". Alternatively you can install the SDK in a different location -and point the environment variable WASI_SDK_PATH to the root directory -of the SDK. The SDK is available for Linux x86_64, macOS x86_64, and MinGW. -""" - -INSTALL_WASMTIME = """ -wasm32-wasi tests require wasmtime on PATH. Please follow instructions at -https://wasmtime.dev/ to install wasmtime. -""" - - -def parse_emconfig( - emconfig: pathlib.Path = EM_CONFIG, -) -> Tuple[pathlib.Path, pathlib.Path]: - """Parse EM_CONFIG file and lookup EMSCRIPTEN_ROOT and NODE_JS. - - The ".emscripten" config file is a Python snippet that uses "EM_CONFIG" - environment variable. EMSCRIPTEN_ROOT is the "upstream/emscripten" - subdirectory with tools like "emconfigure". - """ - if not emconfig.exists(): - return _MISSING, _MISSING - with open(emconfig, encoding="utf-8") as f: - code = f.read() - # EM_CONFIG file is a Python snippet - local: Dict[str, Any] = {} - exec(code, globals(), local) - emscripten_root = pathlib.Path(local["EMSCRIPTEN_ROOT"]) - node_js = pathlib.Path(local["NODE_JS"]) - return emscripten_root, node_js - - -EMSCRIPTEN_ROOT, NODE_JS = parse_emconfig() - - -def read_python_version(configure: pathlib.Path = CONFIGURE) -> str: - """Read PACKAGE_VERSION from configure script - - configure and configure.ac are the canonical source for major and - minor version number. - """ - version_re = re.compile(r"^PACKAGE_VERSION='(\d\.\d+)'") - with configure.open(encoding="utf-8") as f: - for line in f: - mo = version_re.match(line) - if mo: - return mo.group(1) - raise ValueError(f"PACKAGE_VERSION not found in {configure}") - - -PYTHON_VERSION = read_python_version() - - -class ConditionError(ValueError): - def __init__(self, info: str, text: str) -> None: - self.info = info - self.text = text - - def __str__(self) -> str: - return f"{type(self).__name__}: '{self.info}'\n{self.text}" - - -class MissingDependency(ConditionError): - pass - - -class DirtySourceDirectory(ConditionError): - pass - - -@dataclasses.dataclass -class Platform: - """Platform-specific settings - - - CONFIG_SITE override - - configure wrapper (e.g. emconfigure) - - make wrapper (e.g. emmake) - - additional environment variables - - check function to verify SDK - """ - - name: str - pythonexe: str - config_site: Optional[pathlib.PurePath] - configure_wrapper: Optional[pathlib.Path] - make_wrapper: Optional[pathlib.PurePath] - environ: Dict[str, Any] - check: Callable[[], None] - # Used for build_emports(). - ports: Optional[pathlib.PurePath] - cc: Optional[pathlib.PurePath] - - def getenv(self, profile: "BuildProfile") -> Dict[str, Any]: - return self.environ.copy() - - -def _check_clean_src() -> None: - candidates = [ - SRCDIR / "Programs" / "python.o", - SRCDIR / "Python" / "frozen_modules" / "importlib._bootstrap.h", - ] - for candidate in candidates: - if candidate.exists(): - raise DirtySourceDirectory(os.fspath(candidate), CLEAN_SRCDIR) - - -def _check_native() -> None: - if not any(shutil.which(cc) for cc in ["cc", "gcc", "clang"]): - raise MissingDependency("cc", INSTALL_NATIVE) - if not shutil.which("make"): - raise MissingDependency("make", INSTALL_NATIVE) - if sys.platform == "linux": - # skip pkg-config check on macOS - if not shutil.which("pkg-config"): - raise MissingDependency("pkg-config", INSTALL_NATIVE) - # zlib is needed to create zip files - for devel in ["zlib"]: - try: - subprocess.check_call(["pkg-config", "--exists", devel]) - except subprocess.CalledProcessError: - raise MissingDependency(devel, INSTALL_NATIVE) from None - _check_clean_src() - - -NATIVE = Platform( - "native", - # macOS has python.exe - pythonexe=sysconfig.get_config_var("BUILDPYTHON") or "python", - config_site=None, - configure_wrapper=None, - ports=None, - cc=None, - make_wrapper=None, - environ={}, - check=_check_native, -) - - -def _check_emscripten() -> None: - if EMSCRIPTEN_ROOT is _MISSING: - raise MissingDependency("Emscripten SDK EM_CONFIG", INSTALL_EMSDK) - # sanity check - emconfigure = EMSCRIPTEN.configure_wrapper - if emconfigure is not None and not emconfigure.exists(): - raise MissingDependency(os.fspath(emconfigure), INSTALL_EMSDK) - # version check - version_txt = EMSCRIPTEN_ROOT / "emscripten-version.txt" - if not version_txt.exists(): - raise MissingDependency(os.fspath(version_txt), INSTALL_EMSDK) - with open(version_txt) as f: - version = f.read().strip().strip('"') - if version.endswith("-git"): - # git / upstream / tot-upstream installation - version = version[:-4] - version_tuple = cast( - Tuple[int, int, int], - tuple(int(v) for v in version.split(".")) - ) - if version_tuple < EMSDK_MIN_VERSION: - raise ConditionError( - os.fspath(version_txt), - f"Emscripten SDK {version} in '{EMSCRIPTEN_ROOT}' is older than " - "minimum required version " - f"{'.'.join(str(v) for v in EMSDK_MIN_VERSION)}.", - ) - broken = EMSDK_BROKEN_VERSION.get(version_tuple) - if broken is not None: - raise ConditionError( - os.fspath(version_txt), - ( - f"Emscripten SDK {version} in '{EMSCRIPTEN_ROOT}' has known " - f"bugs, see {broken}." - ), - ) - if os.environ.get("PKG_CONFIG_PATH"): - warnings.warn( - "PKG_CONFIG_PATH is set and not empty. emconfigure overrides " - "this environment variable. Use EM_PKG_CONFIG_PATH instead." - ) - _check_clean_src() - - -EMSCRIPTEN = Platform( - "emscripten", - pythonexe="python.js", - config_site=WASMTOOLS / "config.site-wasm32-emscripten", - configure_wrapper=EMSCRIPTEN_ROOT / "emconfigure", - ports=EMSCRIPTEN_ROOT / "embuilder", - cc=EMSCRIPTEN_ROOT / "emcc", - make_wrapper=EMSCRIPTEN_ROOT / "emmake", - environ={ - # workaround for https://github.com/emscripten-core/emscripten/issues/17635 - "TZ": "UTC", - "EM_COMPILER_WRAPPER": "ccache" if HAS_CCACHE else None, - "PATH": [EMSCRIPTEN_ROOT, os.environ["PATH"]], - }, - check=_check_emscripten, -) - - -def _check_wasi() -> None: - wasm_ld = WASI_SDK_PATH / "bin" / "wasm-ld" - if not wasm_ld.exists(): - raise MissingDependency(os.fspath(wasm_ld), INSTALL_WASI_SDK) - wasmtime = shutil.which("wasmtime") - if wasmtime is None: - raise MissingDependency("wasmtime", INSTALL_WASMTIME) - _check_clean_src() - - -WASI = Platform( - "wasi", - pythonexe="python.wasm", - config_site=WASMTOOLS / "config.site-wasm32-wasi", - configure_wrapper=WASMTOOLS / "wasi-env", - ports=None, - cc=WASI_SDK_PATH / "bin" / "clang", - make_wrapper=None, - environ={ - "WASI_SDK_PATH": WASI_SDK_PATH, - # workaround for https://github.com/python/cpython/issues/95952 - "HOSTRUNNER": ( - "wasmtime run " - "--wasm max-wasm-stack=16777216 " - "--wasi preview2 " - "--dir {srcdir}::/ " - "--env PYTHONPATH=/{relbuilddir}/build/lib.wasi-wasm32-{version}:/Lib" - ), - "PATH": [WASI_SDK_PATH / "bin", os.environ["PATH"]], - }, - check=_check_wasi, -) - - -class Host(enum.Enum): - """Target host triplet""" - - wasm32_emscripten = "wasm32-unknown-emscripten" - wasm64_emscripten = "wasm64-unknown-emscripten" - wasm32_wasi = "wasm32-unknown-wasi" - wasm64_wasi = "wasm64-unknown-wasi" - # current platform - build = sysconfig.get_config_var("BUILD_GNU_TYPE") - - @property - def platform(self) -> Platform: - if self.is_emscripten: - return EMSCRIPTEN - elif self.is_wasi: - return WASI - else: - return NATIVE - - @property - def is_emscripten(self) -> bool: - cls = type(self) - return self in {cls.wasm32_emscripten, cls.wasm64_emscripten} - - @property - def is_wasi(self) -> bool: - cls = type(self) - return self in {cls.wasm32_wasi, cls.wasm64_wasi} - - def get_extra_paths(self) -> Iterable[pathlib.PurePath]: - """Host-specific os.environ["PATH"] entries. - - Emscripten's Node version 14.x works well for wasm32-emscripten. - wasm64-emscripten requires more recent v8 version, e.g. node 16.x. - Attempt to use system's node command. - """ - cls = type(self) - if self == cls.wasm32_emscripten: - return [NODE_JS.parent] - elif self == cls.wasm64_emscripten: - # TODO: look for recent node - return [] - else: - return [] - - @property - def emport_args(self) -> List[str]: - """Host-specific port args (Emscripten).""" - cls = type(self) - if self is cls.wasm64_emscripten: - return ["-sMEMORY64=1"] - elif self is cls.wasm32_emscripten: - return ["-sMEMORY64=0"] - else: - return [] - - @property - def embuilder_args(self) -> List[str]: - """Host-specific embuilder args (Emscripten).""" - cls = type(self) - if self is cls.wasm64_emscripten: - return ["--wasm64"] - else: - return [] - - -class EmscriptenTarget(enum.Enum): - """Emscripten-specific targets (--with-emscripten-target)""" - - browser = "browser" - browser_debug = "browser-debug" - node = "node" - node_debug = "node-debug" - - @property - def is_browser(self) -> bool: - cls = type(self) - return self in {cls.browser, cls.browser_debug} - - @property - def emport_args(self) -> List[str]: - """Target-specific port args.""" - cls = type(self) - if self in {cls.browser_debug, cls.node_debug}: - # some libs come in debug and non-debug builds - return ["-O0"] - else: - return ["-O2"] - - -class SupportLevel(enum.Enum): - supported = "tier 3, supported" - working = "working, unsupported" - experimental = "experimental, may be broken" - broken = "broken / unavailable" - - def __bool__(self) -> bool: - cls = type(self) - return self in {cls.supported, cls.working} - - -@dataclasses.dataclass -class BuildProfile: - name: str - support_level: SupportLevel - host: Host - target: Union[EmscriptenTarget, None] = None - dynamic_linking: Union[bool, None] = None - pthreads: Union[bool, None] = None - default_testopts: str = "-j2" - - @property - def is_browser(self) -> bool: - """Is this a browser build?""" - return self.target is not None and self.target.is_browser - - @property - def builddir(self) -> pathlib.Path: - """Path to build directory""" - return BUILDDIR / self.name - - @property - def python_cmd(self) -> pathlib.Path: - """Path to python executable""" - return self.builddir / self.host.platform.pythonexe - - @property - def makefile(self) -> pathlib.Path: - """Path to Makefile""" - return self.builddir / "Makefile" - - @property - def configure_cmd(self) -> List[str]: - """Generate configure command""" - # use relative path, so WASI tests can find lib prefix. - # pathlib.Path.relative_to() does not work here. - configure = os.path.relpath(CONFIGURE, self.builddir) - cmd = [configure, "-C"] - platform = self.host.platform - if platform.configure_wrapper: - cmd.insert(0, os.fspath(platform.configure_wrapper)) - - cmd.append(f"--host={self.host.value}") - cmd.append(f"--build={Host.build.value}") - - if self.target is not None: - assert self.host.is_emscripten - cmd.append(f"--with-emscripten-target={self.target.value}") - - if self.dynamic_linking is not None: - assert self.host.is_emscripten - opt = "enable" if self.dynamic_linking else "disable" - cmd.append(f"--{opt}-wasm-dynamic-linking") - - if self.pthreads is not None: - opt = "enable" if self.pthreads else "disable" - cmd.append(f"--{opt}-wasm-pthreads") - - if self.host != Host.build: - cmd.append(f"--with-build-python={BUILD.python_cmd}") - - if platform.config_site is not None: - cmd.append(f"CONFIG_SITE={platform.config_site}") - - return cmd - - @property - def make_cmd(self) -> List[str]: - """Generate make command""" - cmd = ["make"] - platform = self.host.platform - if platform.make_wrapper: - cmd.insert(0, os.fspath(platform.make_wrapper)) - return cmd - - def getenv(self) -> Dict[str, Any]: - """Generate environ dict for platform""" - env = os.environ.copy() - if hasattr(os, 'process_cpu_count'): - cpu_count = os.process_cpu_count() - else: - cpu_count = os.cpu_count() - env.setdefault("MAKEFLAGS", f"-j{cpu_count}") - platenv = self.host.platform.getenv(self) - for key, value in platenv.items(): - if value is None: - env.pop(key, None) - elif key == "PATH": - # list of path items, prefix with extra paths - new_path: List[pathlib.PurePath] = [] - new_path.extend(self.host.get_extra_paths()) - new_path.extend(value) - env[key] = os.pathsep.join(os.fspath(p) for p in new_path) - elif isinstance(value, str): - env[key] = value.format( - relbuilddir=self.builddir.relative_to(SRCDIR), - srcdir=SRCDIR, - version=PYTHON_VERSION, - ) - else: - env[key] = value - return env - - def _run_cmd( - self, - cmd: Iterable[str], - args: Iterable[str] = (), - cwd: Optional[pathlib.Path] = None, - ) -> int: - cmd = list(cmd) - cmd.extend(args) - if cwd is None: - cwd = self.builddir - logger.info('Running "%s" in "%s"', shlex.join(cmd), cwd) - return subprocess.check_call( - cmd, - cwd=os.fspath(cwd), - env=self.getenv(), - ) - - def _check_execute(self) -> None: - if self.is_browser: - raise ValueError(f"Cannot execute on {self.target}") - - def run_build(self, *args: str) -> None: - """Run configure (if necessary) and make""" - if not self.makefile.exists(): - logger.info("Makefile not found, running configure") - self.run_configure(*args) - self.run_make("all", *args) - - def run_configure(self, *args: str) -> int: - """Run configure script to generate Makefile""" - os.makedirs(self.builddir, exist_ok=True) - return self._run_cmd(self.configure_cmd, args) - - def run_make(self, *args: str) -> int: - """Run make (defaults to build all)""" - return self._run_cmd(self.make_cmd, args) - - def run_pythoninfo(self, *args: str) -> int: - """Run 'make pythoninfo'""" - self._check_execute() - return self.run_make("pythoninfo", *args) - - def run_test(self, target: str, testopts: Optional[str] = None) -> int: - """Run buildbottests""" - self._check_execute() - if testopts is None: - testopts = self.default_testopts - return self.run_make(target, f"TESTOPTS={testopts}") - - def run_py(self, *args: str) -> int: - """Run Python with hostrunner""" - self._check_execute() - return self.run_make( - "--eval", f"run: all; $(HOSTRUNNER) ./$(PYTHON) {shlex.join(args)}", "run" - ) - - def run_browser(self, bind: str = "127.0.0.1", port: int = 8000) -> None: - """Run WASM webserver and open build in browser""" - relbuilddir = self.builddir.relative_to(SRCDIR) - url = f"http://{bind}:{port}/{relbuilddir}/python.html" - args = [ - sys.executable, - os.fspath(WASM_WEBSERVER), - "--bind", - bind, - "--port", - str(port), - ] - srv = subprocess.Popen(args, cwd=SRCDIR) - # wait for server - end = time.monotonic() + 3.0 - while time.monotonic() < end and srv.returncode is None: - try: - with socket.create_connection((bind, port), timeout=0.1) as _: - pass - except OSError: - time.sleep(0.01) - else: - break - - webbrowser.open(url) - - try: - srv.wait() - except KeyboardInterrupt: - pass - - def clean(self, all: bool = False) -> None: - """Clean build directory""" - if all: - if self.builddir.exists(): - shutil.rmtree(self.builddir) - elif self.makefile.exists(): - self.run_make("clean") - - def build_emports(self, force: bool = False) -> None: - """Pre-build emscripten ports.""" - platform = self.host.platform - if platform.ports is None or platform.cc is None: - raise ValueError("Need ports and CC command") - - embuilder_cmd = [os.fspath(platform.ports)] - embuilder_cmd.extend(self.host.embuilder_args) - if force: - embuilder_cmd.append("--force") - - ports_cmd = [os.fspath(platform.cc)] - ports_cmd.extend(self.host.emport_args) - if self.target: - ports_cmd.extend(self.target.emport_args) - - if self.dynamic_linking: - # Trigger PIC build. - ports_cmd.append("-sMAIN_MODULE") - embuilder_cmd.append("--pic") - - if self.pthreads: - # Trigger multi-threaded build. - ports_cmd.append("-sUSE_PTHREADS") - - # Pre-build libbz2, libsqlite3, libz, and some system libs. - ports_cmd.extend(["-sUSE_ZLIB", "-sUSE_BZIP2", "-sUSE_SQLITE3"]) - # Multi-threaded sqlite3 has different suffix - embuilder_cmd.extend( - ["build", "bzip2", "sqlite3-mt" if self.pthreads else "sqlite3", "zlib"] - ) - - self._run_cmd(embuilder_cmd, cwd=SRCDIR) - - with tempfile.TemporaryDirectory(suffix="-py-emport") as tmpdir: - tmppath = pathlib.Path(tmpdir) - main_c = tmppath / "main.c" - main_js = tmppath / "main.js" - with main_c.open("w") as f: - f.write("int main(void) { return 0; }\n") - args = [ - os.fspath(main_c), - "-o", - os.fspath(main_js), - ] - self._run_cmd(ports_cmd, args, cwd=tmppath) - - -# native build (build Python) -BUILD = BuildProfile( - "build", - support_level=SupportLevel.working, - host=Host.build, -) - -_profiles = [ - BUILD, - # wasm32-emscripten - BuildProfile( - "emscripten-browser", - support_level=SupportLevel.supported, - host=Host.wasm32_emscripten, - target=EmscriptenTarget.browser, - dynamic_linking=True, - ), - BuildProfile( - "emscripten-browser-debug", - support_level=SupportLevel.working, - host=Host.wasm32_emscripten, - target=EmscriptenTarget.browser_debug, - dynamic_linking=True, - ), - BuildProfile( - "emscripten-node-dl", - support_level=SupportLevel.supported, - host=Host.wasm32_emscripten, - target=EmscriptenTarget.node, - dynamic_linking=True, - ), - BuildProfile( - "emscripten-node-dl-debug", - support_level=SupportLevel.working, - host=Host.wasm32_emscripten, - target=EmscriptenTarget.node_debug, - dynamic_linking=True, - ), - BuildProfile( - "emscripten-node-pthreads", - support_level=SupportLevel.supported, - host=Host.wasm32_emscripten, - target=EmscriptenTarget.node, - pthreads=True, - ), - BuildProfile( - "emscripten-node-pthreads-debug", - support_level=SupportLevel.working, - host=Host.wasm32_emscripten, - target=EmscriptenTarget.node_debug, - pthreads=True, - ), - # Emscripten build with both pthreads and dynamic linking is crashing. - BuildProfile( - "emscripten-node-dl-pthreads-debug", - support_level=SupportLevel.broken, - host=Host.wasm32_emscripten, - target=EmscriptenTarget.node_debug, - dynamic_linking=True, - pthreads=True, - ), - # wasm64-emscripten (requires Emscripten >= 3.1.21) - BuildProfile( - "wasm64-emscripten-node-debug", - support_level=SupportLevel.experimental, - host=Host.wasm64_emscripten, - target=EmscriptenTarget.node_debug, - # MEMORY64 is not compatible with dynamic linking - dynamic_linking=False, - pthreads=False, - ), - # wasm32-wasi - BuildProfile( - "wasi", - support_level=SupportLevel.supported, - host=Host.wasm32_wasi, - ), - # wasm32-wasi-threads - BuildProfile( - "wasi-threads", - support_level=SupportLevel.experimental, - host=Host.wasm32_wasi, - pthreads=True, - ), - # no SDK available yet - # BuildProfile( - # "wasm64-wasi", - # support_level=SupportLevel.broken, - # host=Host.wasm64_wasi, - # ), -] - -PROFILES = {p.name: p for p in _profiles} - -parser = argparse.ArgumentParser( - "wasm_build.py", - description=__doc__, - formatter_class=argparse.RawTextHelpFormatter, -) - -parser.add_argument( - "--clean", - "-c", - help="Clean build directories first", - action="store_true", -) - -parser.add_argument( - "--verbose", - "-v", - help="Verbose logging", - action="store_true", -) - -parser.add_argument( - "--silent", - help="Run configure and make in silent mode", - action="store_true", -) - -parser.add_argument( - "--testopts", - help=( - "Additional test options for 'test' and 'hostrunnertest', e.g. " - "--testopts='-v test_os'." - ), - default=None, -) - -# Don't list broken and experimental variants in help -platforms_choices = list(p.name for p in _profiles) + ["cleanall"] -platforms_help = list(p.name for p in _profiles if p.support_level) + ["cleanall"] -parser.add_argument( - "platform", - metavar="PLATFORM", - help=f"Build platform: {', '.join(platforms_help)}", - choices=platforms_choices, -) - -ops = dict( - build="auto build (build 'build' Python, emports, configure, compile)", - configure="run ./configure", - compile="run 'make all'", - pythoninfo="run 'make pythoninfo'", - test="run 'make buildbottest TESTOPTS=...' (supports parallel tests)", - hostrunnertest="run 'make hostrunnertest TESTOPTS=...'", - repl="start interactive REPL / webserver + browser session", - clean="run 'make clean'", - cleanall="remove all build directories", - emports="build Emscripten port with embuilder (only Emscripten)", -) -ops_help = "\n".join(f"{op:16s} {help}" for op, help in ops.items()) -parser.add_argument( - "ops", - metavar="OP", - help=f"operation (default: build)\n\n{ops_help}", - choices=tuple(ops), - default="build", - nargs="*", -) - - -def main() -> None: - args = parser.parse_args() - logging.basicConfig( - level=logging.INFO if args.verbose else logging.ERROR, - format="%(message)s", - ) - - if args.platform == "cleanall": - for builder in PROFILES.values(): - builder.clean(all=True) - parser.exit(0) - - # additional configure and make args - cm_args = ("--silent",) if args.silent else () - - # nargs=* with default quirk - if args.ops == "build": - args.ops = ["build"] - - builder = PROFILES[args.platform] - try: - builder.host.platform.check() - except ConditionError as e: - parser.error(str(e)) - - if args.clean: - builder.clean(all=False) - - # hack for WASI - if builder.host.is_wasi and not SETUP_LOCAL.exists(): - SETUP_LOCAL.touch() - - # auto-build - if "build" in args.ops: - # check and create build Python - if builder is not BUILD: - logger.info("Auto-building 'build' Python.") - try: - BUILD.host.platform.check() - except ConditionError as e: - parser.error(str(e)) - if args.clean: - BUILD.clean(all=False) - BUILD.run_build(*cm_args) - # build Emscripten ports with embuilder - if builder.host.is_emscripten and "emports" not in args.ops: - builder.build_emports() - - for op in args.ops: - logger.info("\n*** %s %s", args.platform, op) - if op == "build": - builder.run_build(*cm_args) - elif op == "configure": - builder.run_configure(*cm_args) - elif op == "compile": - builder.run_make("all", *cm_args) - elif op == "pythoninfo": - builder.run_pythoninfo(*cm_args) - elif op == "repl": - if builder.is_browser: - builder.run_browser() - else: - builder.run_py() - elif op == "test": - builder.run_test("buildbottest", testopts=args.testopts) - elif op == "hostrunnertest": - builder.run_test("hostrunnertest", testopts=args.testopts) - elif op == "clean": - builder.clean(all=False) - elif op == "cleanall": - builder.clean(all=True) - elif op == "emports": - builder.build_emports(force=args.clean) - else: - raise ValueError(op) - - print(builder.builddir) - parser.exit(0) - - -if __name__ == "__main__": - main() |