aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Tools/wasm/wasm_build.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/wasm/wasm_build.py')
-rwxr-xr-xTools/wasm/wasm_build.py932
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()