17db96d56Sopenharmony_ci#!/usr/bin/env python3
27db96d56Sopenharmony_ci"""Build script for Python on WebAssembly platforms.
37db96d56Sopenharmony_ci
47db96d56Sopenharmony_ci  $ ./Tools/wasm/wasm_builder.py emscripten-browser build repl
57db96d56Sopenharmony_ci  $ ./Tools/wasm/wasm_builder.py emscripten-node-dl build test
67db96d56Sopenharmony_ci  $ ./Tools/wasm/wasm_builder.py wasi build test
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_ciPrimary build targets are "emscripten-node-dl" (NodeJS, dynamic linking),
97db96d56Sopenharmony_ci"emscripten-browser", and "wasi".
107db96d56Sopenharmony_ci
117db96d56Sopenharmony_ciEmscripten builds require a recent Emscripten SDK. The tools looks for an
127db96d56Sopenharmony_ciactivated EMSDK environment (". /path/to/emsdk_env.sh"). System packages
137db96d56Sopenharmony_ci(Debian, Homebrew) are not supported.
147db96d56Sopenharmony_ci
157db96d56Sopenharmony_ciWASI builds require WASI SDK and wasmtime. The tool looks for 'WASI_SDK_PATH'
167db96d56Sopenharmony_ciand falls back to /opt/wasi-sdk.
177db96d56Sopenharmony_ci
187db96d56Sopenharmony_ciThe 'build' Python interpreter must be rebuilt every time Python's byte code
197db96d56Sopenharmony_cichanges.
207db96d56Sopenharmony_ci
217db96d56Sopenharmony_ci  ./Tools/wasm/wasm_builder.py --clean build build
227db96d56Sopenharmony_ci
237db96d56Sopenharmony_ci"""
247db96d56Sopenharmony_ciimport argparse
257db96d56Sopenharmony_ciimport enum
267db96d56Sopenharmony_ciimport dataclasses
277db96d56Sopenharmony_ciimport logging
287db96d56Sopenharmony_ciimport os
297db96d56Sopenharmony_ciimport pathlib
307db96d56Sopenharmony_ciimport re
317db96d56Sopenharmony_ciimport shlex
327db96d56Sopenharmony_ciimport shutil
337db96d56Sopenharmony_ciimport socket
347db96d56Sopenharmony_ciimport subprocess
357db96d56Sopenharmony_ciimport sys
367db96d56Sopenharmony_ciimport sysconfig
377db96d56Sopenharmony_ciimport tempfile
387db96d56Sopenharmony_ciimport time
397db96d56Sopenharmony_ciimport warnings
407db96d56Sopenharmony_ciimport webbrowser
417db96d56Sopenharmony_ci
427db96d56Sopenharmony_ci# for Python 3.8
437db96d56Sopenharmony_cifrom typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
447db96d56Sopenharmony_ci
457db96d56Sopenharmony_cilogger = logging.getLogger("wasm_build")
467db96d56Sopenharmony_ci
477db96d56Sopenharmony_ciSRCDIR = pathlib.Path(__file__).parent.parent.parent.absolute()
487db96d56Sopenharmony_ciWASMTOOLS = SRCDIR / "Tools" / "wasm"
497db96d56Sopenharmony_ciBUILDDIR = SRCDIR / "builddir"
507db96d56Sopenharmony_ciCONFIGURE = SRCDIR / "configure"
517db96d56Sopenharmony_ciSETUP_LOCAL = SRCDIR / "Modules" / "Setup.local"
527db96d56Sopenharmony_ci
537db96d56Sopenharmony_ciHAS_CCACHE = shutil.which("ccache") is not None
547db96d56Sopenharmony_ci
557db96d56Sopenharmony_ci# path to WASI-SDK root
567db96d56Sopenharmony_ciWASI_SDK_PATH = pathlib.Path(os.environ.get("WASI_SDK_PATH", "/opt/wasi-sdk"))
577db96d56Sopenharmony_ci
587db96d56Sopenharmony_ci# path to Emscripten SDK config file.
597db96d56Sopenharmony_ci# auto-detect's EMSDK in /opt/emsdk without ". emsdk_env.sh".
607db96d56Sopenharmony_ciEM_CONFIG = pathlib.Path(os.environ.setdefault("EM_CONFIG", "/opt/emsdk/.emscripten"))
617db96d56Sopenharmony_ciEMSDK_MIN_VERSION = (3, 1, 19)
627db96d56Sopenharmony_ciEMSDK_BROKEN_VERSION = {
637db96d56Sopenharmony_ci    (3, 1, 14): "https://github.com/emscripten-core/emscripten/issues/17338",
647db96d56Sopenharmony_ci    (3, 1, 16): "https://github.com/emscripten-core/emscripten/issues/17393",
657db96d56Sopenharmony_ci    (3, 1, 20): "https://github.com/emscripten-core/emscripten/issues/17720",
667db96d56Sopenharmony_ci}
677db96d56Sopenharmony_ci_MISSING = pathlib.PurePath("MISSING")
687db96d56Sopenharmony_ci
697db96d56Sopenharmony_ciWASM_WEBSERVER = WASMTOOLS / "wasm_webserver.py"
707db96d56Sopenharmony_ci
717db96d56Sopenharmony_ciCLEAN_SRCDIR = f"""
727db96d56Sopenharmony_ciBuilds require a clean source directory. Please use a clean checkout or
737db96d56Sopenharmony_cirun "make clean -C '{SRCDIR}'".
747db96d56Sopenharmony_ci"""
757db96d56Sopenharmony_ci
767db96d56Sopenharmony_ciINSTALL_NATIVE = f"""
777db96d56Sopenharmony_ciBuilds require a C compiler (gcc, clang), make, pkg-config, and development
787db96d56Sopenharmony_ciheaders for dependencies like zlib.
797db96d56Sopenharmony_ci
807db96d56Sopenharmony_ciDebian/Ubuntu: sudo apt install build-essential git curl pkg-config zlib1g-dev
817db96d56Sopenharmony_ciFedora/CentOS: sudo dnf install gcc make git-core curl pkgconfig zlib-devel
827db96d56Sopenharmony_ci"""
837db96d56Sopenharmony_ci
847db96d56Sopenharmony_ciINSTALL_EMSDK = """
857db96d56Sopenharmony_ciwasm32-emscripten builds need Emscripten SDK. Please follow instructions at
867db96d56Sopenharmony_cihttps://emscripten.org/docs/getting_started/downloads.html how to install
877db96d56Sopenharmony_ciEmscripten and how to activate the SDK with "emsdk_env.sh".
887db96d56Sopenharmony_ci
897db96d56Sopenharmony_ci    git clone https://github.com/emscripten-core/emsdk.git /path/to/emsdk
907db96d56Sopenharmony_ci    cd /path/to/emsdk
917db96d56Sopenharmony_ci    ./emsdk install latest
927db96d56Sopenharmony_ci    ./emsdk activate latest
937db96d56Sopenharmony_ci    source /path/to/emsdk_env.sh
947db96d56Sopenharmony_ci"""
957db96d56Sopenharmony_ci
967db96d56Sopenharmony_ciINSTALL_WASI_SDK = """
977db96d56Sopenharmony_ciwasm32-wasi builds need WASI SDK. Please fetch the latest SDK from
987db96d56Sopenharmony_cihttps://github.com/WebAssembly/wasi-sdk/releases and install it to
997db96d56Sopenharmony_ci"/opt/wasi-sdk". Alternatively you can install the SDK in a different location
1007db96d56Sopenharmony_ciand point the environment variable WASI_SDK_PATH to the root directory
1017db96d56Sopenharmony_ciof the SDK. The SDK is available for Linux x86_64, macOS x86_64, and MinGW.
1027db96d56Sopenharmony_ci"""
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ciINSTALL_WASMTIME = """
1057db96d56Sopenharmony_ciwasm32-wasi tests require wasmtime on PATH. Please follow instructions at
1067db96d56Sopenharmony_cihttps://wasmtime.dev/ to install wasmtime.
1077db96d56Sopenharmony_ci"""
1087db96d56Sopenharmony_ci
1097db96d56Sopenharmony_ci
1107db96d56Sopenharmony_cidef parse_emconfig(
1117db96d56Sopenharmony_ci    emconfig: pathlib.Path = EM_CONFIG,
1127db96d56Sopenharmony_ci) -> Tuple[pathlib.PurePath, pathlib.PurePath]:
1137db96d56Sopenharmony_ci    """Parse EM_CONFIG file and lookup EMSCRIPTEN_ROOT and NODE_JS.
1147db96d56Sopenharmony_ci
1157db96d56Sopenharmony_ci    The ".emscripten" config file is a Python snippet that uses "EM_CONFIG"
1167db96d56Sopenharmony_ci    environment variable. EMSCRIPTEN_ROOT is the "upstream/emscripten"
1177db96d56Sopenharmony_ci    subdirectory with tools like "emconfigure".
1187db96d56Sopenharmony_ci    """
1197db96d56Sopenharmony_ci    if not emconfig.exists():
1207db96d56Sopenharmony_ci        return _MISSING, _MISSING
1217db96d56Sopenharmony_ci    with open(emconfig, encoding="utf-8") as f:
1227db96d56Sopenharmony_ci        code = f.read()
1237db96d56Sopenharmony_ci    # EM_CONFIG file is a Python snippet
1247db96d56Sopenharmony_ci    local: Dict[str, Any] = {}
1257db96d56Sopenharmony_ci    exec(code, globals(), local)
1267db96d56Sopenharmony_ci    emscripten_root = pathlib.Path(local["EMSCRIPTEN_ROOT"])
1277db96d56Sopenharmony_ci    node_js = pathlib.Path(local["NODE_JS"])
1287db96d56Sopenharmony_ci    return emscripten_root, node_js
1297db96d56Sopenharmony_ci
1307db96d56Sopenharmony_ci
1317db96d56Sopenharmony_ciEMSCRIPTEN_ROOT, NODE_JS = parse_emconfig()
1327db96d56Sopenharmony_ci
1337db96d56Sopenharmony_ci
1347db96d56Sopenharmony_cidef read_python_version(configure: pathlib.Path = CONFIGURE) -> str:
1357db96d56Sopenharmony_ci    """Read PACKAGE_VERSION from configure script
1367db96d56Sopenharmony_ci
1377db96d56Sopenharmony_ci    configure and configure.ac are the canonical source for major and
1387db96d56Sopenharmony_ci    minor version number.
1397db96d56Sopenharmony_ci    """
1407db96d56Sopenharmony_ci    version_re = re.compile("^PACKAGE_VERSION='(\d\.\d+)'")
1417db96d56Sopenharmony_ci    with configure.open(encoding="utf-8") as f:
1427db96d56Sopenharmony_ci        for line in f:
1437db96d56Sopenharmony_ci            mo = version_re.match(line)
1447db96d56Sopenharmony_ci            if mo:
1457db96d56Sopenharmony_ci                return mo.group(1)
1467db96d56Sopenharmony_ci    raise ValueError(f"PACKAGE_VERSION not found in {configure}")
1477db96d56Sopenharmony_ci
1487db96d56Sopenharmony_ci
1497db96d56Sopenharmony_ciPYTHON_VERSION = read_python_version()
1507db96d56Sopenharmony_ci
1517db96d56Sopenharmony_ci
1527db96d56Sopenharmony_ciclass ConditionError(ValueError):
1537db96d56Sopenharmony_ci    def __init__(self, info: str, text: str):
1547db96d56Sopenharmony_ci        self.info = info
1557db96d56Sopenharmony_ci        self.text = text
1567db96d56Sopenharmony_ci
1577db96d56Sopenharmony_ci    def __str__(self):
1587db96d56Sopenharmony_ci        return f"{type(self).__name__}: '{self.info}'\n{self.text}"
1597db96d56Sopenharmony_ci
1607db96d56Sopenharmony_ci
1617db96d56Sopenharmony_ciclass MissingDependency(ConditionError):
1627db96d56Sopenharmony_ci    pass
1637db96d56Sopenharmony_ci
1647db96d56Sopenharmony_ci
1657db96d56Sopenharmony_ciclass DirtySourceDirectory(ConditionError):
1667db96d56Sopenharmony_ci    pass
1677db96d56Sopenharmony_ci
1687db96d56Sopenharmony_ci
1697db96d56Sopenharmony_ci@dataclasses.dataclass
1707db96d56Sopenharmony_ciclass Platform:
1717db96d56Sopenharmony_ci    """Platform-specific settings
1727db96d56Sopenharmony_ci
1737db96d56Sopenharmony_ci    - CONFIG_SITE override
1747db96d56Sopenharmony_ci    - configure wrapper (e.g. emconfigure)
1757db96d56Sopenharmony_ci    - make wrapper (e.g. emmake)
1767db96d56Sopenharmony_ci    - additional environment variables
1777db96d56Sopenharmony_ci    - check function to verify SDK
1787db96d56Sopenharmony_ci    """
1797db96d56Sopenharmony_ci
1807db96d56Sopenharmony_ci    name: str
1817db96d56Sopenharmony_ci    pythonexe: str
1827db96d56Sopenharmony_ci    config_site: Optional[pathlib.PurePath]
1837db96d56Sopenharmony_ci    configure_wrapper: Optional[pathlib.PurePath]
1847db96d56Sopenharmony_ci    make_wrapper: Optional[pathlib.PurePath]
1857db96d56Sopenharmony_ci    environ: dict
1867db96d56Sopenharmony_ci    check: Callable[[], None]
1877db96d56Sopenharmony_ci    # Used for build_emports().
1887db96d56Sopenharmony_ci    ports: Optional[pathlib.PurePath]
1897db96d56Sopenharmony_ci    cc: Optional[pathlib.PurePath]
1907db96d56Sopenharmony_ci
1917db96d56Sopenharmony_ci    def getenv(self, profile: "BuildProfile") -> dict:
1927db96d56Sopenharmony_ci        return self.environ.copy()
1937db96d56Sopenharmony_ci
1947db96d56Sopenharmony_ci
1957db96d56Sopenharmony_cidef _check_clean_src():
1967db96d56Sopenharmony_ci    candidates = [
1977db96d56Sopenharmony_ci        SRCDIR / "Programs" / "python.o",
1987db96d56Sopenharmony_ci        SRCDIR / "Python" / "frozen_modules" / "importlib._bootstrap.h",
1997db96d56Sopenharmony_ci    ]
2007db96d56Sopenharmony_ci    for candidate in candidates:
2017db96d56Sopenharmony_ci        if candidate.exists():
2027db96d56Sopenharmony_ci            raise DirtySourceDirectory(os.fspath(candidate), CLEAN_SRCDIR)
2037db96d56Sopenharmony_ci
2047db96d56Sopenharmony_ci
2057db96d56Sopenharmony_cidef _check_native():
2067db96d56Sopenharmony_ci    if not any(shutil.which(cc) for cc in ["cc", "gcc", "clang"]):
2077db96d56Sopenharmony_ci        raise MissingDependency("cc", INSTALL_NATIVE)
2087db96d56Sopenharmony_ci    if not shutil.which("make"):
2097db96d56Sopenharmony_ci        raise MissingDependency("make", INSTALL_NATIVE)
2107db96d56Sopenharmony_ci    if sys.platform == "linux":
2117db96d56Sopenharmony_ci        # skip pkg-config check on macOS
2127db96d56Sopenharmony_ci        if not shutil.which("pkg-config"):
2137db96d56Sopenharmony_ci            raise MissingDependency("pkg-config", INSTALL_NATIVE)
2147db96d56Sopenharmony_ci        # zlib is needed to create zip files
2157db96d56Sopenharmony_ci        for devel in ["zlib"]:
2167db96d56Sopenharmony_ci            try:
2177db96d56Sopenharmony_ci                subprocess.check_call(["pkg-config", "--exists", devel])
2187db96d56Sopenharmony_ci            except subprocess.CalledProcessError:
2197db96d56Sopenharmony_ci                raise MissingDependency(devel, INSTALL_NATIVE) from None
2207db96d56Sopenharmony_ci    _check_clean_src()
2217db96d56Sopenharmony_ci
2227db96d56Sopenharmony_ci
2237db96d56Sopenharmony_ciNATIVE = Platform(
2247db96d56Sopenharmony_ci    "native",
2257db96d56Sopenharmony_ci    # macOS has python.exe
2267db96d56Sopenharmony_ci    pythonexe=sysconfig.get_config_var("BUILDPYTHON") or "python",
2277db96d56Sopenharmony_ci    config_site=None,
2287db96d56Sopenharmony_ci    configure_wrapper=None,
2297db96d56Sopenharmony_ci    ports=None,
2307db96d56Sopenharmony_ci    cc=None,
2317db96d56Sopenharmony_ci    make_wrapper=None,
2327db96d56Sopenharmony_ci    environ={},
2337db96d56Sopenharmony_ci    check=_check_native,
2347db96d56Sopenharmony_ci)
2357db96d56Sopenharmony_ci
2367db96d56Sopenharmony_ci
2377db96d56Sopenharmony_cidef _check_emscripten():
2387db96d56Sopenharmony_ci    if EMSCRIPTEN_ROOT is _MISSING:
2397db96d56Sopenharmony_ci        raise MissingDependency("Emscripten SDK EM_CONFIG", INSTALL_EMSDK)
2407db96d56Sopenharmony_ci    # sanity check
2417db96d56Sopenharmony_ci    emconfigure = EMSCRIPTEN.configure_wrapper
2427db96d56Sopenharmony_ci    if not emconfigure.exists():
2437db96d56Sopenharmony_ci        raise MissingDependency(os.fspath(emconfigure), INSTALL_EMSDK)
2447db96d56Sopenharmony_ci    # version check
2457db96d56Sopenharmony_ci    version_txt = EMSCRIPTEN_ROOT / "emscripten-version.txt"
2467db96d56Sopenharmony_ci    if not version_txt.exists():
2477db96d56Sopenharmony_ci        raise MissingDependency(os.fspath(version_txt), INSTALL_EMSDK)
2487db96d56Sopenharmony_ci    with open(version_txt) as f:
2497db96d56Sopenharmony_ci        version = f.read().strip().strip('"')
2507db96d56Sopenharmony_ci    if version.endswith("-git"):
2517db96d56Sopenharmony_ci        # git / upstream / tot-upstream installation
2527db96d56Sopenharmony_ci        version = version[:-4]
2537db96d56Sopenharmony_ci    version_tuple = tuple(int(v) for v in version.split("."))
2547db96d56Sopenharmony_ci    if version_tuple < EMSDK_MIN_VERSION:
2557db96d56Sopenharmony_ci        raise ConditionError(
2567db96d56Sopenharmony_ci            os.fspath(version_txt),
2577db96d56Sopenharmony_ci            f"Emscripten SDK {version} in '{EMSCRIPTEN_ROOT}' is older than "
2587db96d56Sopenharmony_ci            "minimum required version "
2597db96d56Sopenharmony_ci            f"{'.'.join(str(v) for v in EMSDK_MIN_VERSION)}.",
2607db96d56Sopenharmony_ci        )
2617db96d56Sopenharmony_ci    broken = EMSDK_BROKEN_VERSION.get(version_tuple)
2627db96d56Sopenharmony_ci    if broken is not None:
2637db96d56Sopenharmony_ci        raise ConditionError(
2647db96d56Sopenharmony_ci            os.fspath(version_txt),
2657db96d56Sopenharmony_ci            (
2667db96d56Sopenharmony_ci                f"Emscripten SDK {version} in '{EMSCRIPTEN_ROOT}' has known "
2677db96d56Sopenharmony_ci                f"bugs, see {broken}."
2687db96d56Sopenharmony_ci            ),
2697db96d56Sopenharmony_ci        )
2707db96d56Sopenharmony_ci    if os.environ.get("PKG_CONFIG_PATH"):
2717db96d56Sopenharmony_ci        warnings.warn(
2727db96d56Sopenharmony_ci            "PKG_CONFIG_PATH is set and not empty. emconfigure overrides "
2737db96d56Sopenharmony_ci            "this environment variable. Use EM_PKG_CONFIG_PATH instead."
2747db96d56Sopenharmony_ci        )
2757db96d56Sopenharmony_ci    _check_clean_src()
2767db96d56Sopenharmony_ci
2777db96d56Sopenharmony_ci
2787db96d56Sopenharmony_ciEMSCRIPTEN = Platform(
2797db96d56Sopenharmony_ci    "emscripten",
2807db96d56Sopenharmony_ci    pythonexe="python.js",
2817db96d56Sopenharmony_ci    config_site=WASMTOOLS / "config.site-wasm32-emscripten",
2827db96d56Sopenharmony_ci    configure_wrapper=EMSCRIPTEN_ROOT / "emconfigure",
2837db96d56Sopenharmony_ci    ports=EMSCRIPTEN_ROOT / "embuilder",
2847db96d56Sopenharmony_ci    cc=EMSCRIPTEN_ROOT / "emcc",
2857db96d56Sopenharmony_ci    make_wrapper=EMSCRIPTEN_ROOT / "emmake",
2867db96d56Sopenharmony_ci    environ={
2877db96d56Sopenharmony_ci        # workaround for https://github.com/emscripten-core/emscripten/issues/17635
2887db96d56Sopenharmony_ci        "TZ": "UTC",
2897db96d56Sopenharmony_ci        "EM_COMPILER_WRAPPER": "ccache" if HAS_CCACHE else None,
2907db96d56Sopenharmony_ci        "PATH": [EMSCRIPTEN_ROOT, os.environ["PATH"]],
2917db96d56Sopenharmony_ci    },
2927db96d56Sopenharmony_ci    check=_check_emscripten,
2937db96d56Sopenharmony_ci)
2947db96d56Sopenharmony_ci
2957db96d56Sopenharmony_ci
2967db96d56Sopenharmony_cidef _check_wasi():
2977db96d56Sopenharmony_ci    wasm_ld = WASI_SDK_PATH / "bin" / "wasm-ld"
2987db96d56Sopenharmony_ci    if not wasm_ld.exists():
2997db96d56Sopenharmony_ci        raise MissingDependency(os.fspath(wasm_ld), INSTALL_WASI_SDK)
3007db96d56Sopenharmony_ci    wasmtime = shutil.which("wasmtime")
3017db96d56Sopenharmony_ci    if wasmtime is None:
3027db96d56Sopenharmony_ci        raise MissingDependency("wasmtime", INSTALL_WASMTIME)
3037db96d56Sopenharmony_ci    _check_clean_src()
3047db96d56Sopenharmony_ci
3057db96d56Sopenharmony_ci
3067db96d56Sopenharmony_ciWASI = Platform(
3077db96d56Sopenharmony_ci    "wasi",
3087db96d56Sopenharmony_ci    pythonexe="python.wasm",
3097db96d56Sopenharmony_ci    config_site=WASMTOOLS / "config.site-wasm32-wasi",
3107db96d56Sopenharmony_ci    configure_wrapper=WASMTOOLS / "wasi-env",
3117db96d56Sopenharmony_ci    ports=None,
3127db96d56Sopenharmony_ci    cc=WASI_SDK_PATH / "bin" / "clang",
3137db96d56Sopenharmony_ci    make_wrapper=None,
3147db96d56Sopenharmony_ci    environ={
3157db96d56Sopenharmony_ci        "WASI_SDK_PATH": WASI_SDK_PATH,
3167db96d56Sopenharmony_ci        # workaround for https://github.com/python/cpython/issues/95952
3177db96d56Sopenharmony_ci        "HOSTRUNNER": (
3187db96d56Sopenharmony_ci            "wasmtime run "
3197db96d56Sopenharmony_ci            "--env PYTHONPATH=/{relbuilddir}/build/lib.wasi-wasm32-{version}:/Lib "
3207db96d56Sopenharmony_ci            "--mapdir /::{srcdir} --"
3217db96d56Sopenharmony_ci        ),
3227db96d56Sopenharmony_ci        "PATH": [WASI_SDK_PATH / "bin", os.environ["PATH"]],
3237db96d56Sopenharmony_ci    },
3247db96d56Sopenharmony_ci    check=_check_wasi,
3257db96d56Sopenharmony_ci)
3267db96d56Sopenharmony_ci
3277db96d56Sopenharmony_ci
3287db96d56Sopenharmony_ciclass Host(enum.Enum):
3297db96d56Sopenharmony_ci    """Target host triplet"""
3307db96d56Sopenharmony_ci
3317db96d56Sopenharmony_ci    wasm32_emscripten = "wasm32-unknown-emscripten"
3327db96d56Sopenharmony_ci    wasm64_emscripten = "wasm64-unknown-emscripten"
3337db96d56Sopenharmony_ci    wasm32_wasi = "wasm32-unknown-wasi"
3347db96d56Sopenharmony_ci    wasm64_wasi = "wasm64-unknown-wasi"
3357db96d56Sopenharmony_ci    # current platform
3367db96d56Sopenharmony_ci    build = sysconfig.get_config_var("BUILD_GNU_TYPE")
3377db96d56Sopenharmony_ci
3387db96d56Sopenharmony_ci    @property
3397db96d56Sopenharmony_ci    def platform(self) -> Platform:
3407db96d56Sopenharmony_ci        if self.is_emscripten:
3417db96d56Sopenharmony_ci            return EMSCRIPTEN
3427db96d56Sopenharmony_ci        elif self.is_wasi:
3437db96d56Sopenharmony_ci            return WASI
3447db96d56Sopenharmony_ci        else:
3457db96d56Sopenharmony_ci            return NATIVE
3467db96d56Sopenharmony_ci
3477db96d56Sopenharmony_ci    @property
3487db96d56Sopenharmony_ci    def is_emscripten(self) -> bool:
3497db96d56Sopenharmony_ci        cls = type(self)
3507db96d56Sopenharmony_ci        return self in {cls.wasm32_emscripten, cls.wasm64_emscripten}
3517db96d56Sopenharmony_ci
3527db96d56Sopenharmony_ci    @property
3537db96d56Sopenharmony_ci    def is_wasi(self) -> bool:
3547db96d56Sopenharmony_ci        cls = type(self)
3557db96d56Sopenharmony_ci        return self in {cls.wasm32_wasi, cls.wasm64_wasi}
3567db96d56Sopenharmony_ci
3577db96d56Sopenharmony_ci    def get_extra_paths(self) -> Iterable[pathlib.PurePath]:
3587db96d56Sopenharmony_ci        """Host-specific os.environ["PATH"] entries.
3597db96d56Sopenharmony_ci
3607db96d56Sopenharmony_ci        Emscripten's Node version 14.x works well for wasm32-emscripten.
3617db96d56Sopenharmony_ci        wasm64-emscripten requires more recent v8 version, e.g. node 16.x.
3627db96d56Sopenharmony_ci        Attempt to use system's node command.
3637db96d56Sopenharmony_ci        """
3647db96d56Sopenharmony_ci        cls = type(self)
3657db96d56Sopenharmony_ci        if self == cls.wasm32_emscripten:
3667db96d56Sopenharmony_ci            return [NODE_JS.parent]
3677db96d56Sopenharmony_ci        elif self == cls.wasm64_emscripten:
3687db96d56Sopenharmony_ci            # TODO: look for recent node
3697db96d56Sopenharmony_ci            return []
3707db96d56Sopenharmony_ci        else:
3717db96d56Sopenharmony_ci            return []
3727db96d56Sopenharmony_ci
3737db96d56Sopenharmony_ci    @property
3747db96d56Sopenharmony_ci    def emport_args(self) -> List[str]:
3757db96d56Sopenharmony_ci        """Host-specific port args (Emscripten)."""
3767db96d56Sopenharmony_ci        cls = type(self)
3777db96d56Sopenharmony_ci        if self is cls.wasm64_emscripten:
3787db96d56Sopenharmony_ci            return ["-sMEMORY64=1"]
3797db96d56Sopenharmony_ci        elif self is cls.wasm32_emscripten:
3807db96d56Sopenharmony_ci            return ["-sMEMORY64=0"]
3817db96d56Sopenharmony_ci        else:
3827db96d56Sopenharmony_ci            return []
3837db96d56Sopenharmony_ci
3847db96d56Sopenharmony_ci    @property
3857db96d56Sopenharmony_ci    def embuilder_args(self) -> List[str]:
3867db96d56Sopenharmony_ci        """Host-specific embuilder args (Emscripten)."""
3877db96d56Sopenharmony_ci        cls = type(self)
3887db96d56Sopenharmony_ci        if self is cls.wasm64_emscripten:
3897db96d56Sopenharmony_ci            return ["--wasm64"]
3907db96d56Sopenharmony_ci        else:
3917db96d56Sopenharmony_ci            return []
3927db96d56Sopenharmony_ci
3937db96d56Sopenharmony_ci
3947db96d56Sopenharmony_ciclass EmscriptenTarget(enum.Enum):
3957db96d56Sopenharmony_ci    """Emscripten-specific targets (--with-emscripten-target)"""
3967db96d56Sopenharmony_ci
3977db96d56Sopenharmony_ci    browser = "browser"
3987db96d56Sopenharmony_ci    browser_debug = "browser-debug"
3997db96d56Sopenharmony_ci    node = "node"
4007db96d56Sopenharmony_ci    node_debug = "node-debug"
4017db96d56Sopenharmony_ci
4027db96d56Sopenharmony_ci    @property
4037db96d56Sopenharmony_ci    def is_browser(self):
4047db96d56Sopenharmony_ci        cls = type(self)
4057db96d56Sopenharmony_ci        return self in {cls.browser, cls.browser_debug}
4067db96d56Sopenharmony_ci
4077db96d56Sopenharmony_ci    @property
4087db96d56Sopenharmony_ci    def emport_args(self) -> List[str]:
4097db96d56Sopenharmony_ci        """Target-specific port args."""
4107db96d56Sopenharmony_ci        cls = type(self)
4117db96d56Sopenharmony_ci        if self in {cls.browser_debug, cls.node_debug}:
4127db96d56Sopenharmony_ci            # some libs come in debug and non-debug builds
4137db96d56Sopenharmony_ci            return ["-O0"]
4147db96d56Sopenharmony_ci        else:
4157db96d56Sopenharmony_ci            return ["-O2"]
4167db96d56Sopenharmony_ci
4177db96d56Sopenharmony_ci
4187db96d56Sopenharmony_ciclass SupportLevel(enum.Enum):
4197db96d56Sopenharmony_ci    supported = "tier 3, supported"
4207db96d56Sopenharmony_ci    working = "working, unsupported"
4217db96d56Sopenharmony_ci    experimental = "experimental, may be broken"
4227db96d56Sopenharmony_ci    broken = "broken / unavailable"
4237db96d56Sopenharmony_ci
4247db96d56Sopenharmony_ci    def __bool__(self):
4257db96d56Sopenharmony_ci        cls = type(self)
4267db96d56Sopenharmony_ci        return self in {cls.supported, cls.working}
4277db96d56Sopenharmony_ci
4287db96d56Sopenharmony_ci
4297db96d56Sopenharmony_ci@dataclasses.dataclass
4307db96d56Sopenharmony_ciclass BuildProfile:
4317db96d56Sopenharmony_ci    name: str
4327db96d56Sopenharmony_ci    support_level: SupportLevel
4337db96d56Sopenharmony_ci    host: Host
4347db96d56Sopenharmony_ci    target: Union[EmscriptenTarget, None] = None
4357db96d56Sopenharmony_ci    dynamic_linking: Union[bool, None] = None
4367db96d56Sopenharmony_ci    pthreads: Union[bool, None] = None
4377db96d56Sopenharmony_ci    default_testopts: str = "-j2"
4387db96d56Sopenharmony_ci
4397db96d56Sopenharmony_ci    @property
4407db96d56Sopenharmony_ci    def is_browser(self) -> bool:
4417db96d56Sopenharmony_ci        """Is this a browser build?"""
4427db96d56Sopenharmony_ci        return self.target is not None and self.target.is_browser
4437db96d56Sopenharmony_ci
4447db96d56Sopenharmony_ci    @property
4457db96d56Sopenharmony_ci    def builddir(self) -> pathlib.Path:
4467db96d56Sopenharmony_ci        """Path to build directory"""
4477db96d56Sopenharmony_ci        return BUILDDIR / self.name
4487db96d56Sopenharmony_ci
4497db96d56Sopenharmony_ci    @property
4507db96d56Sopenharmony_ci    def python_cmd(self) -> pathlib.Path:
4517db96d56Sopenharmony_ci        """Path to python executable"""
4527db96d56Sopenharmony_ci        return self.builddir / self.host.platform.pythonexe
4537db96d56Sopenharmony_ci
4547db96d56Sopenharmony_ci    @property
4557db96d56Sopenharmony_ci    def makefile(self) -> pathlib.Path:
4567db96d56Sopenharmony_ci        """Path to Makefile"""
4577db96d56Sopenharmony_ci        return self.builddir / "Makefile"
4587db96d56Sopenharmony_ci
4597db96d56Sopenharmony_ci    @property
4607db96d56Sopenharmony_ci    def configure_cmd(self) -> List[str]:
4617db96d56Sopenharmony_ci        """Generate configure command"""
4627db96d56Sopenharmony_ci        # use relative path, so WASI tests can find lib prefix.
4637db96d56Sopenharmony_ci        # pathlib.Path.relative_to() does not work here.
4647db96d56Sopenharmony_ci        configure = os.path.relpath(CONFIGURE, self.builddir)
4657db96d56Sopenharmony_ci        cmd = [configure, "-C"]
4667db96d56Sopenharmony_ci        platform = self.host.platform
4677db96d56Sopenharmony_ci        if platform.configure_wrapper:
4687db96d56Sopenharmony_ci            cmd.insert(0, os.fspath(platform.configure_wrapper))
4697db96d56Sopenharmony_ci
4707db96d56Sopenharmony_ci        cmd.append(f"--host={self.host.value}")
4717db96d56Sopenharmony_ci        cmd.append(f"--build={Host.build.value}")
4727db96d56Sopenharmony_ci
4737db96d56Sopenharmony_ci        if self.target is not None:
4747db96d56Sopenharmony_ci            assert self.host.is_emscripten
4757db96d56Sopenharmony_ci            cmd.append(f"--with-emscripten-target={self.target.value}")
4767db96d56Sopenharmony_ci
4777db96d56Sopenharmony_ci        if self.dynamic_linking is not None:
4787db96d56Sopenharmony_ci            assert self.host.is_emscripten
4797db96d56Sopenharmony_ci            opt = "enable" if self.dynamic_linking else "disable"
4807db96d56Sopenharmony_ci            cmd.append(f"--{opt}-wasm-dynamic-linking")
4817db96d56Sopenharmony_ci
4827db96d56Sopenharmony_ci        if self.pthreads is not None:
4837db96d56Sopenharmony_ci            assert self.host.is_emscripten
4847db96d56Sopenharmony_ci            opt = "enable" if self.pthreads else "disable"
4857db96d56Sopenharmony_ci            cmd.append(f"--{opt}-wasm-pthreads")
4867db96d56Sopenharmony_ci
4877db96d56Sopenharmony_ci        if self.host != Host.build:
4887db96d56Sopenharmony_ci            cmd.append(f"--with-build-python={BUILD.python_cmd}")
4897db96d56Sopenharmony_ci
4907db96d56Sopenharmony_ci        if platform.config_site is not None:
4917db96d56Sopenharmony_ci            cmd.append(f"CONFIG_SITE={platform.config_site}")
4927db96d56Sopenharmony_ci
4937db96d56Sopenharmony_ci        return cmd
4947db96d56Sopenharmony_ci
4957db96d56Sopenharmony_ci    @property
4967db96d56Sopenharmony_ci    def make_cmd(self) -> List[str]:
4977db96d56Sopenharmony_ci        """Generate make command"""
4987db96d56Sopenharmony_ci        cmd = ["make"]
4997db96d56Sopenharmony_ci        platform = self.host.platform
5007db96d56Sopenharmony_ci        if platform.make_wrapper:
5017db96d56Sopenharmony_ci            cmd.insert(0, os.fspath(platform.make_wrapper))
5027db96d56Sopenharmony_ci        return cmd
5037db96d56Sopenharmony_ci
5047db96d56Sopenharmony_ci    def getenv(self) -> dict:
5057db96d56Sopenharmony_ci        """Generate environ dict for platform"""
5067db96d56Sopenharmony_ci        env = os.environ.copy()
5077db96d56Sopenharmony_ci        env.setdefault("MAKEFLAGS", f"-j{os.cpu_count()}")
5087db96d56Sopenharmony_ci        platenv = self.host.platform.getenv(self)
5097db96d56Sopenharmony_ci        for key, value in platenv.items():
5107db96d56Sopenharmony_ci            if value is None:
5117db96d56Sopenharmony_ci                env.pop(key, None)
5127db96d56Sopenharmony_ci            elif key == "PATH":
5137db96d56Sopenharmony_ci                # list of path items, prefix with extra paths
5147db96d56Sopenharmony_ci                new_path: List[pathlib.PurePath] = []
5157db96d56Sopenharmony_ci                new_path.extend(self.host.get_extra_paths())
5167db96d56Sopenharmony_ci                new_path.extend(value)
5177db96d56Sopenharmony_ci                env[key] = os.pathsep.join(os.fspath(p) for p in new_path)
5187db96d56Sopenharmony_ci            elif isinstance(value, str):
5197db96d56Sopenharmony_ci                env[key] = value.format(
5207db96d56Sopenharmony_ci                    relbuilddir=self.builddir.relative_to(SRCDIR),
5217db96d56Sopenharmony_ci                    srcdir=SRCDIR,
5227db96d56Sopenharmony_ci                    version=PYTHON_VERSION,
5237db96d56Sopenharmony_ci                )
5247db96d56Sopenharmony_ci            else:
5257db96d56Sopenharmony_ci                env[key] = value
5267db96d56Sopenharmony_ci        return env
5277db96d56Sopenharmony_ci
5287db96d56Sopenharmony_ci    def _run_cmd(
5297db96d56Sopenharmony_ci        self,
5307db96d56Sopenharmony_ci        cmd: Iterable[str],
5317db96d56Sopenharmony_ci        args: Iterable[str] = (),
5327db96d56Sopenharmony_ci        cwd: Optional[pathlib.Path] = None,
5337db96d56Sopenharmony_ci    ):
5347db96d56Sopenharmony_ci        cmd = list(cmd)
5357db96d56Sopenharmony_ci        cmd.extend(args)
5367db96d56Sopenharmony_ci        if cwd is None:
5377db96d56Sopenharmony_ci            cwd = self.builddir
5387db96d56Sopenharmony_ci        logger.info('Running "%s" in "%s"', shlex.join(cmd), cwd)
5397db96d56Sopenharmony_ci        return subprocess.check_call(
5407db96d56Sopenharmony_ci            cmd,
5417db96d56Sopenharmony_ci            cwd=os.fspath(cwd),
5427db96d56Sopenharmony_ci            env=self.getenv(),
5437db96d56Sopenharmony_ci        )
5447db96d56Sopenharmony_ci
5457db96d56Sopenharmony_ci    def _check_execute(self):
5467db96d56Sopenharmony_ci        if self.is_browser:
5477db96d56Sopenharmony_ci            raise ValueError(f"Cannot execute on {self.target}")
5487db96d56Sopenharmony_ci
5497db96d56Sopenharmony_ci    def run_build(self, *args):
5507db96d56Sopenharmony_ci        """Run configure (if necessary) and make"""
5517db96d56Sopenharmony_ci        if not self.makefile.exists():
5527db96d56Sopenharmony_ci            logger.info("Makefile not found, running configure")
5537db96d56Sopenharmony_ci            self.run_configure(*args)
5547db96d56Sopenharmony_ci        self.run_make("all", *args)
5557db96d56Sopenharmony_ci
5567db96d56Sopenharmony_ci    def run_configure(self, *args):
5577db96d56Sopenharmony_ci        """Run configure script to generate Makefile"""
5587db96d56Sopenharmony_ci        os.makedirs(self.builddir, exist_ok=True)
5597db96d56Sopenharmony_ci        return self._run_cmd(self.configure_cmd, args)
5607db96d56Sopenharmony_ci
5617db96d56Sopenharmony_ci    def run_make(self, *args):
5627db96d56Sopenharmony_ci        """Run make (defaults to build all)"""
5637db96d56Sopenharmony_ci        return self._run_cmd(self.make_cmd, args)
5647db96d56Sopenharmony_ci
5657db96d56Sopenharmony_ci    def run_pythoninfo(self, *args):
5667db96d56Sopenharmony_ci        """Run 'make pythoninfo'"""
5677db96d56Sopenharmony_ci        self._check_execute()
5687db96d56Sopenharmony_ci        return self.run_make("pythoninfo", *args)
5697db96d56Sopenharmony_ci
5707db96d56Sopenharmony_ci    def run_test(self, target: str, testopts: Optional[str] = None):
5717db96d56Sopenharmony_ci        """Run buildbottests"""
5727db96d56Sopenharmony_ci        self._check_execute()
5737db96d56Sopenharmony_ci        if testopts is None:
5747db96d56Sopenharmony_ci            testopts = self.default_testopts
5757db96d56Sopenharmony_ci        return self.run_make(target, f"TESTOPTS={testopts}")
5767db96d56Sopenharmony_ci
5777db96d56Sopenharmony_ci    def run_py(self, *args):
5787db96d56Sopenharmony_ci        """Run Python with hostrunner"""
5797db96d56Sopenharmony_ci        self._check_execute()
5807db96d56Sopenharmony_ci        self.run_make(
5817db96d56Sopenharmony_ci            "--eval", f"run: all; $(HOSTRUNNER) ./$(PYTHON) {shlex.join(args)}", "run"
5827db96d56Sopenharmony_ci        )
5837db96d56Sopenharmony_ci
5847db96d56Sopenharmony_ci    def run_browser(self, bind="127.0.0.1", port=8000):
5857db96d56Sopenharmony_ci        """Run WASM webserver and open build in browser"""
5867db96d56Sopenharmony_ci        relbuilddir = self.builddir.relative_to(SRCDIR)
5877db96d56Sopenharmony_ci        url = f"http://{bind}:{port}/{relbuilddir}/python.html"
5887db96d56Sopenharmony_ci        args = [
5897db96d56Sopenharmony_ci            sys.executable,
5907db96d56Sopenharmony_ci            os.fspath(WASM_WEBSERVER),
5917db96d56Sopenharmony_ci            "--bind",
5927db96d56Sopenharmony_ci            bind,
5937db96d56Sopenharmony_ci            "--port",
5947db96d56Sopenharmony_ci            str(port),
5957db96d56Sopenharmony_ci        ]
5967db96d56Sopenharmony_ci        srv = subprocess.Popen(args, cwd=SRCDIR)
5977db96d56Sopenharmony_ci        # wait for server
5987db96d56Sopenharmony_ci        end = time.monotonic() + 3.0
5997db96d56Sopenharmony_ci        while time.monotonic() < end and srv.returncode is None:
6007db96d56Sopenharmony_ci            try:
6017db96d56Sopenharmony_ci                with socket.create_connection((bind, port), timeout=0.1) as s:
6027db96d56Sopenharmony_ci                    pass
6037db96d56Sopenharmony_ci            except OSError:
6047db96d56Sopenharmony_ci                time.sleep(0.01)
6057db96d56Sopenharmony_ci            else:
6067db96d56Sopenharmony_ci                break
6077db96d56Sopenharmony_ci
6087db96d56Sopenharmony_ci        webbrowser.open(url)
6097db96d56Sopenharmony_ci
6107db96d56Sopenharmony_ci        try:
6117db96d56Sopenharmony_ci            srv.wait()
6127db96d56Sopenharmony_ci        except KeyboardInterrupt:
6137db96d56Sopenharmony_ci            pass
6147db96d56Sopenharmony_ci
6157db96d56Sopenharmony_ci    def clean(self, all: bool = False):
6167db96d56Sopenharmony_ci        """Clean build directory"""
6177db96d56Sopenharmony_ci        if all:
6187db96d56Sopenharmony_ci            if self.builddir.exists():
6197db96d56Sopenharmony_ci                shutil.rmtree(self.builddir)
6207db96d56Sopenharmony_ci        elif self.makefile.exists():
6217db96d56Sopenharmony_ci            self.run_make("clean")
6227db96d56Sopenharmony_ci
6237db96d56Sopenharmony_ci    def build_emports(self, force: bool = False):
6247db96d56Sopenharmony_ci        """Pre-build emscripten ports."""
6257db96d56Sopenharmony_ci        platform = self.host.platform
6267db96d56Sopenharmony_ci        if platform.ports is None or platform.cc is None:
6277db96d56Sopenharmony_ci            raise ValueError("Need ports and CC command")
6287db96d56Sopenharmony_ci
6297db96d56Sopenharmony_ci        embuilder_cmd = [os.fspath(platform.ports)]
6307db96d56Sopenharmony_ci        embuilder_cmd.extend(self.host.embuilder_args)
6317db96d56Sopenharmony_ci        if force:
6327db96d56Sopenharmony_ci            embuilder_cmd.append("--force")
6337db96d56Sopenharmony_ci
6347db96d56Sopenharmony_ci        ports_cmd = [os.fspath(platform.cc)]
6357db96d56Sopenharmony_ci        ports_cmd.extend(self.host.emport_args)
6367db96d56Sopenharmony_ci        if self.target:
6377db96d56Sopenharmony_ci            ports_cmd.extend(self.target.emport_args)
6387db96d56Sopenharmony_ci
6397db96d56Sopenharmony_ci        if self.dynamic_linking:
6407db96d56Sopenharmony_ci            # Trigger PIC build.
6417db96d56Sopenharmony_ci            ports_cmd.append("-sMAIN_MODULE")
6427db96d56Sopenharmony_ci            embuilder_cmd.append("--pic")
6437db96d56Sopenharmony_ci
6447db96d56Sopenharmony_ci        if self.pthreads:
6457db96d56Sopenharmony_ci            # Trigger multi-threaded build.
6467db96d56Sopenharmony_ci            ports_cmd.append("-sUSE_PTHREADS")
6477db96d56Sopenharmony_ci
6487db96d56Sopenharmony_ci        # Pre-build libbz2, libsqlite3, libz, and some system libs.
6497db96d56Sopenharmony_ci        ports_cmd.extend(["-sUSE_ZLIB", "-sUSE_BZIP2", "-sUSE_SQLITE3"])
6507db96d56Sopenharmony_ci        # Multi-threaded sqlite3 has different suffix
6517db96d56Sopenharmony_ci        embuilder_cmd.extend(
6527db96d56Sopenharmony_ci            ["build", "bzip2", "sqlite3-mt" if self.pthreads else "sqlite3", "zlib"]
6537db96d56Sopenharmony_ci        )
6547db96d56Sopenharmony_ci
6557db96d56Sopenharmony_ci        self._run_cmd(embuilder_cmd, cwd=SRCDIR)
6567db96d56Sopenharmony_ci
6577db96d56Sopenharmony_ci        with tempfile.TemporaryDirectory(suffix="-py-emport") as tmpdir:
6587db96d56Sopenharmony_ci            tmppath = pathlib.Path(tmpdir)
6597db96d56Sopenharmony_ci            main_c = tmppath / "main.c"
6607db96d56Sopenharmony_ci            main_js = tmppath / "main.js"
6617db96d56Sopenharmony_ci            with main_c.open("w") as f:
6627db96d56Sopenharmony_ci                f.write("int main(void) { return 0; }\n")
6637db96d56Sopenharmony_ci            args = [
6647db96d56Sopenharmony_ci                os.fspath(main_c),
6657db96d56Sopenharmony_ci                "-o",
6667db96d56Sopenharmony_ci                os.fspath(main_js),
6677db96d56Sopenharmony_ci            ]
6687db96d56Sopenharmony_ci            self._run_cmd(ports_cmd, args, cwd=tmppath)
6697db96d56Sopenharmony_ci
6707db96d56Sopenharmony_ci
6717db96d56Sopenharmony_ci# native build (build Python)
6727db96d56Sopenharmony_ciBUILD = BuildProfile(
6737db96d56Sopenharmony_ci    "build",
6747db96d56Sopenharmony_ci    support_level=SupportLevel.working,
6757db96d56Sopenharmony_ci    host=Host.build,
6767db96d56Sopenharmony_ci)
6777db96d56Sopenharmony_ci
6787db96d56Sopenharmony_ci_profiles = [
6797db96d56Sopenharmony_ci    BUILD,
6807db96d56Sopenharmony_ci    # wasm32-emscripten
6817db96d56Sopenharmony_ci    BuildProfile(
6827db96d56Sopenharmony_ci        "emscripten-browser",
6837db96d56Sopenharmony_ci        support_level=SupportLevel.supported,
6847db96d56Sopenharmony_ci        host=Host.wasm32_emscripten,
6857db96d56Sopenharmony_ci        target=EmscriptenTarget.browser,
6867db96d56Sopenharmony_ci        dynamic_linking=True,
6877db96d56Sopenharmony_ci    ),
6887db96d56Sopenharmony_ci    BuildProfile(
6897db96d56Sopenharmony_ci        "emscripten-browser-debug",
6907db96d56Sopenharmony_ci        support_level=SupportLevel.working,
6917db96d56Sopenharmony_ci        host=Host.wasm32_emscripten,
6927db96d56Sopenharmony_ci        target=EmscriptenTarget.browser_debug,
6937db96d56Sopenharmony_ci        dynamic_linking=True,
6947db96d56Sopenharmony_ci    ),
6957db96d56Sopenharmony_ci    BuildProfile(
6967db96d56Sopenharmony_ci        "emscripten-node-dl",
6977db96d56Sopenharmony_ci        support_level=SupportLevel.supported,
6987db96d56Sopenharmony_ci        host=Host.wasm32_emscripten,
6997db96d56Sopenharmony_ci        target=EmscriptenTarget.node,
7007db96d56Sopenharmony_ci        dynamic_linking=True,
7017db96d56Sopenharmony_ci    ),
7027db96d56Sopenharmony_ci    BuildProfile(
7037db96d56Sopenharmony_ci        "emscripten-node-dl-debug",
7047db96d56Sopenharmony_ci        support_level=SupportLevel.working,
7057db96d56Sopenharmony_ci        host=Host.wasm32_emscripten,
7067db96d56Sopenharmony_ci        target=EmscriptenTarget.node_debug,
7077db96d56Sopenharmony_ci        dynamic_linking=True,
7087db96d56Sopenharmony_ci    ),
7097db96d56Sopenharmony_ci    BuildProfile(
7107db96d56Sopenharmony_ci        "emscripten-node-pthreads",
7117db96d56Sopenharmony_ci        support_level=SupportLevel.supported,
7127db96d56Sopenharmony_ci        host=Host.wasm32_emscripten,
7137db96d56Sopenharmony_ci        target=EmscriptenTarget.node,
7147db96d56Sopenharmony_ci        pthreads=True,
7157db96d56Sopenharmony_ci    ),
7167db96d56Sopenharmony_ci    BuildProfile(
7177db96d56Sopenharmony_ci        "emscripten-node-pthreads-debug",
7187db96d56Sopenharmony_ci        support_level=SupportLevel.working,
7197db96d56Sopenharmony_ci        host=Host.wasm32_emscripten,
7207db96d56Sopenharmony_ci        target=EmscriptenTarget.node_debug,
7217db96d56Sopenharmony_ci        pthreads=True,
7227db96d56Sopenharmony_ci    ),
7237db96d56Sopenharmony_ci    # Emscripten build with both pthreads and dynamic linking is crashing.
7247db96d56Sopenharmony_ci    BuildProfile(
7257db96d56Sopenharmony_ci        "emscripten-node-dl-pthreads-debug",
7267db96d56Sopenharmony_ci        support_level=SupportLevel.broken,
7277db96d56Sopenharmony_ci        host=Host.wasm32_emscripten,
7287db96d56Sopenharmony_ci        target=EmscriptenTarget.node_debug,
7297db96d56Sopenharmony_ci        dynamic_linking=True,
7307db96d56Sopenharmony_ci        pthreads=True,
7317db96d56Sopenharmony_ci    ),
7327db96d56Sopenharmony_ci    # wasm64-emscripten (requires Emscripten >= 3.1.21)
7337db96d56Sopenharmony_ci    BuildProfile(
7347db96d56Sopenharmony_ci        "wasm64-emscripten-node-debug",
7357db96d56Sopenharmony_ci        support_level=SupportLevel.experimental,
7367db96d56Sopenharmony_ci        host=Host.wasm64_emscripten,
7377db96d56Sopenharmony_ci        target=EmscriptenTarget.node_debug,
7387db96d56Sopenharmony_ci        # MEMORY64 is not compatible with dynamic linking
7397db96d56Sopenharmony_ci        dynamic_linking=False,
7407db96d56Sopenharmony_ci        pthreads=False,
7417db96d56Sopenharmony_ci    ),
7427db96d56Sopenharmony_ci    # wasm32-wasi
7437db96d56Sopenharmony_ci    BuildProfile(
7447db96d56Sopenharmony_ci        "wasi",
7457db96d56Sopenharmony_ci        support_level=SupportLevel.supported,
7467db96d56Sopenharmony_ci        host=Host.wasm32_wasi,
7477db96d56Sopenharmony_ci    ),
7487db96d56Sopenharmony_ci    # no SDK available yet
7497db96d56Sopenharmony_ci    # BuildProfile(
7507db96d56Sopenharmony_ci    #    "wasm64-wasi",
7517db96d56Sopenharmony_ci    #    support_level=SupportLevel.broken,
7527db96d56Sopenharmony_ci    #    host=Host.wasm64_wasi,
7537db96d56Sopenharmony_ci    # ),
7547db96d56Sopenharmony_ci]
7557db96d56Sopenharmony_ci
7567db96d56Sopenharmony_ciPROFILES = {p.name: p for p in _profiles}
7577db96d56Sopenharmony_ci
7587db96d56Sopenharmony_ciparser = argparse.ArgumentParser(
7597db96d56Sopenharmony_ci    "wasm_build.py",
7607db96d56Sopenharmony_ci    description=__doc__,
7617db96d56Sopenharmony_ci    formatter_class=argparse.RawTextHelpFormatter,
7627db96d56Sopenharmony_ci)
7637db96d56Sopenharmony_ci
7647db96d56Sopenharmony_ciparser.add_argument(
7657db96d56Sopenharmony_ci    "--clean",
7667db96d56Sopenharmony_ci    "-c",
7677db96d56Sopenharmony_ci    help="Clean build directories first",
7687db96d56Sopenharmony_ci    action="store_true",
7697db96d56Sopenharmony_ci)
7707db96d56Sopenharmony_ci
7717db96d56Sopenharmony_ciparser.add_argument(
7727db96d56Sopenharmony_ci    "--verbose",
7737db96d56Sopenharmony_ci    "-v",
7747db96d56Sopenharmony_ci    help="Verbose logging",
7757db96d56Sopenharmony_ci    action="store_true",
7767db96d56Sopenharmony_ci)
7777db96d56Sopenharmony_ci
7787db96d56Sopenharmony_ciparser.add_argument(
7797db96d56Sopenharmony_ci    "--silent",
7807db96d56Sopenharmony_ci    help="Run configure and make in silent mode",
7817db96d56Sopenharmony_ci    action="store_true",
7827db96d56Sopenharmony_ci)
7837db96d56Sopenharmony_ci
7847db96d56Sopenharmony_ciparser.add_argument(
7857db96d56Sopenharmony_ci    "--testopts",
7867db96d56Sopenharmony_ci    help=(
7877db96d56Sopenharmony_ci        "Additional test options for 'test' and 'hostrunnertest', e.g. "
7887db96d56Sopenharmony_ci        "--testopts='-v test_os'."
7897db96d56Sopenharmony_ci    ),
7907db96d56Sopenharmony_ci    default=None,
7917db96d56Sopenharmony_ci)
7927db96d56Sopenharmony_ci
7937db96d56Sopenharmony_ci# Don't list broken and experimental variants in help
7947db96d56Sopenharmony_ciplatforms_choices = list(p.name for p in _profiles) + ["cleanall"]
7957db96d56Sopenharmony_ciplatforms_help = list(p.name for p in _profiles if p.support_level) + ["cleanall"]
7967db96d56Sopenharmony_ciparser.add_argument(
7977db96d56Sopenharmony_ci    "platform",
7987db96d56Sopenharmony_ci    metavar="PLATFORM",
7997db96d56Sopenharmony_ci    help=f"Build platform: {', '.join(platforms_help)}",
8007db96d56Sopenharmony_ci    choices=platforms_choices,
8017db96d56Sopenharmony_ci)
8027db96d56Sopenharmony_ci
8037db96d56Sopenharmony_ciops = dict(
8047db96d56Sopenharmony_ci    build="auto build (build 'build' Python, emports, configure, compile)",
8057db96d56Sopenharmony_ci    configure="run ./configure",
8067db96d56Sopenharmony_ci    compile="run 'make all'",
8077db96d56Sopenharmony_ci    pythoninfo="run 'make pythoninfo'",
8087db96d56Sopenharmony_ci    test="run 'make buildbottest TESTOPTS=...' (supports parallel tests)",
8097db96d56Sopenharmony_ci    hostrunnertest="run 'make hostrunnertest TESTOPTS=...'",
8107db96d56Sopenharmony_ci    repl="start interactive REPL / webserver + browser session",
8117db96d56Sopenharmony_ci    clean="run 'make clean'",
8127db96d56Sopenharmony_ci    cleanall="remove all build directories",
8137db96d56Sopenharmony_ci    emports="build Emscripten port with embuilder (only Emscripten)",
8147db96d56Sopenharmony_ci)
8157db96d56Sopenharmony_ciops_help = "\n".join(f"{op:16s} {help}" for op, help in ops.items())
8167db96d56Sopenharmony_ciparser.add_argument(
8177db96d56Sopenharmony_ci    "ops",
8187db96d56Sopenharmony_ci    metavar="OP",
8197db96d56Sopenharmony_ci    help=f"operation (default: build)\n\n{ops_help}",
8207db96d56Sopenharmony_ci    choices=tuple(ops),
8217db96d56Sopenharmony_ci    default="build",
8227db96d56Sopenharmony_ci    nargs="*",
8237db96d56Sopenharmony_ci)
8247db96d56Sopenharmony_ci
8257db96d56Sopenharmony_ci
8267db96d56Sopenharmony_cidef main():
8277db96d56Sopenharmony_ci    args = parser.parse_args()
8287db96d56Sopenharmony_ci    logging.basicConfig(
8297db96d56Sopenharmony_ci        level=logging.INFO if args.verbose else logging.ERROR,
8307db96d56Sopenharmony_ci        format="%(message)s",
8317db96d56Sopenharmony_ci    )
8327db96d56Sopenharmony_ci
8337db96d56Sopenharmony_ci    if args.platform == "cleanall":
8347db96d56Sopenharmony_ci        for builder in PROFILES.values():
8357db96d56Sopenharmony_ci            builder.clean(all=True)
8367db96d56Sopenharmony_ci        parser.exit(0)
8377db96d56Sopenharmony_ci
8387db96d56Sopenharmony_ci    # additional configure and make args
8397db96d56Sopenharmony_ci    cm_args = ("--silent",) if args.silent else ()
8407db96d56Sopenharmony_ci
8417db96d56Sopenharmony_ci    # nargs=* with default quirk
8427db96d56Sopenharmony_ci    if args.ops == "build":
8437db96d56Sopenharmony_ci        args.ops = ["build"]
8447db96d56Sopenharmony_ci
8457db96d56Sopenharmony_ci    builder = PROFILES[args.platform]
8467db96d56Sopenharmony_ci    try:
8477db96d56Sopenharmony_ci        builder.host.platform.check()
8487db96d56Sopenharmony_ci    except ConditionError as e:
8497db96d56Sopenharmony_ci        parser.error(str(e))
8507db96d56Sopenharmony_ci
8517db96d56Sopenharmony_ci    if args.clean:
8527db96d56Sopenharmony_ci        builder.clean(all=False)
8537db96d56Sopenharmony_ci
8547db96d56Sopenharmony_ci    # hack for WASI
8557db96d56Sopenharmony_ci    if builder.host.is_wasi and not SETUP_LOCAL.exists():
8567db96d56Sopenharmony_ci        SETUP_LOCAL.touch()
8577db96d56Sopenharmony_ci
8587db96d56Sopenharmony_ci    # auto-build
8597db96d56Sopenharmony_ci    if "build" in args.ops:
8607db96d56Sopenharmony_ci        # check and create build Python
8617db96d56Sopenharmony_ci        if builder is not BUILD:
8627db96d56Sopenharmony_ci            logger.info("Auto-building 'build' Python.")
8637db96d56Sopenharmony_ci            try:
8647db96d56Sopenharmony_ci                BUILD.host.platform.check()
8657db96d56Sopenharmony_ci            except ConditionError as e:
8667db96d56Sopenharmony_ci                parser.error(str(e))
8677db96d56Sopenharmony_ci            if args.clean:
8687db96d56Sopenharmony_ci                BUILD.clean(all=False)
8697db96d56Sopenharmony_ci            BUILD.run_build(*cm_args)
8707db96d56Sopenharmony_ci        # build Emscripten ports with embuilder
8717db96d56Sopenharmony_ci        if builder.host.is_emscripten and "emports" not in args.ops:
8727db96d56Sopenharmony_ci            builder.build_emports()
8737db96d56Sopenharmony_ci
8747db96d56Sopenharmony_ci    for op in args.ops:
8757db96d56Sopenharmony_ci        logger.info("\n*** %s %s", args.platform, op)
8767db96d56Sopenharmony_ci        if op == "build":
8777db96d56Sopenharmony_ci            builder.run_build(*cm_args)
8787db96d56Sopenharmony_ci        elif op == "configure":
8797db96d56Sopenharmony_ci            builder.run_configure(*cm_args)
8807db96d56Sopenharmony_ci        elif op == "compile":
8817db96d56Sopenharmony_ci            builder.run_make("all", *cm_args)
8827db96d56Sopenharmony_ci        elif op == "pythoninfo":
8837db96d56Sopenharmony_ci            builder.run_pythoninfo(*cm_args)
8847db96d56Sopenharmony_ci        elif op == "repl":
8857db96d56Sopenharmony_ci            if builder.is_browser:
8867db96d56Sopenharmony_ci                builder.run_browser()
8877db96d56Sopenharmony_ci            else:
8887db96d56Sopenharmony_ci                builder.run_py()
8897db96d56Sopenharmony_ci        elif op == "test":
8907db96d56Sopenharmony_ci            builder.run_test("buildbottest", testopts=args.testopts)
8917db96d56Sopenharmony_ci        elif op == "hostrunnertest":
8927db96d56Sopenharmony_ci            builder.run_test("hostrunnertest", testopts=args.testopts)
8937db96d56Sopenharmony_ci        elif op == "clean":
8947db96d56Sopenharmony_ci            builder.clean(all=False)
8957db96d56Sopenharmony_ci        elif op == "cleanall":
8967db96d56Sopenharmony_ci            builder.clean(all=True)
8977db96d56Sopenharmony_ci        elif op == "emports":
8987db96d56Sopenharmony_ci            builder.build_emports(force=args.clean)
8997db96d56Sopenharmony_ci        else:
9007db96d56Sopenharmony_ci            raise ValueError(op)
9017db96d56Sopenharmony_ci
9027db96d56Sopenharmony_ci    print(builder.builddir)
9037db96d56Sopenharmony_ci    parser.exit(0)
9047db96d56Sopenharmony_ci
9057db96d56Sopenharmony_ci
9067db96d56Sopenharmony_ciif __name__ == "__main__":
9077db96d56Sopenharmony_ci    main()
908