summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/distutils/tests/test_sysconfig.py1
-rw-r--r--Lib/test/test_decimal.py3
-rw-r--r--Lib/test/test_sysconfig.py1
-rw-r--r--Lib/test/test_unicode_file_functions.py5
-rw-r--r--Lib/test/test_warnings/__init__.py9
-rw-r--r--Makefile.pre.in9
-rw-r--r--Misc/NEWS.d/next/Tools-Demos/2022-08-10-17-08-43.gh-issue-95853.HCjC2m.rst2
-rw-r--r--Misc/NEWS.d/next/Tools-Demos/2022-08-29-17-25-13.gh-issue-95853.Ce17cT.rst2
-rw-r--r--Modules/pyexpat.c2
-rw-r--r--Python/sysmodule.c10
-rw-r--r--Tools/wasm/README.md94
-rwxr-xr-xTools/wasm/wasi-env3
-rwxr-xr-xTools/wasm/wasm_assets.py14
-rwxr-xr-xTools/wasm/wasm_build.py907
-rwxr-xr-xconfigure168
-rw-r--r--configure.ac46
16 files changed, 1236 insertions, 40 deletions
diff --git a/Lib/distutils/tests/test_sysconfig.py b/Lib/distutils/tests/test_sysconfig.py
index d1c4727..6833d22 100644
--- a/Lib/distutils/tests/test_sysconfig.py
+++ b/Lib/distutils/tests/test_sysconfig.py
@@ -49,6 +49,7 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase):
self.assertIsInstance(cvars, dict)
self.assertTrue(cvars)
+ @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds")
def test_srcdir(self):
# See Issues #15322, #15364.
srcdir = sysconfig.get_config_var('srcdir')
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index 33d9c6d..67ccaab 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -37,7 +37,7 @@ from test.support import (run_unittest, run_doctest, is_resource_enabled,
requires_legacy_unicode_capi, check_sanitizer)
from test.support import (TestFailed,
run_with_locale, cpython_only,
- darwin_malloc_err_warning)
+ darwin_malloc_err_warning, is_emscripten)
from test.support.import_helper import import_fresh_module
from test.support import threading_helper
from test.support import warnings_helper
@@ -5623,6 +5623,7 @@ class CWhitebox(unittest.TestCase):
# Issue 41540:
@unittest.skipIf(sys.platform.startswith("aix"),
"AIX: default ulimit: test is flaky because of extreme over-allocation")
+ @unittest.skipIf(is_emscripten, "Test is unstable on Emscripten")
@unittest.skipIf(check_sanitizer(address=True, memory=True),
"ASAN/MSAN sanitizer defaults to crashing "
"instead of returning NULL for malloc failure.")
diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
index 578ac1d..d96371d 100644
--- a/Lib/test/test_sysconfig.py
+++ b/Lib/test/test_sysconfig.py
@@ -439,6 +439,7 @@ class TestSysConfig(unittest.TestCase):
self.assertEqual(status, 0)
self.assertEqual(my_platform, test_platform)
+ @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds")
def test_srcdir(self):
# See Issues #15322, #15364.
srcdir = sysconfig.get_config_var('srcdir')
diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py
index 54916dec4..47619c8 100644
--- a/Lib/test/test_unicode_file_functions.py
+++ b/Lib/test/test_unicode_file_functions.py
@@ -6,6 +6,7 @@ import unittest
import warnings
from unicodedata import normalize
from test.support import os_helper
+from test import support
filenames = [
@@ -123,6 +124,10 @@ class UnicodeFileTests(unittest.TestCase):
# NFKD in Python is useless, because darwin will normalize it later and so
# open(), os.stat(), etc. don't raise any exception.
@unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X')
+ @unittest.skipIf(
+ support.is_emscripten or support.is_wasi,
+ "test fails on Emscripten/WASI when host platform is macOS."
+ )
def test_normalize(self):
files = set(self.files)
others = set()
diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py
index 0f960b8..61a6444 100644
--- a/Lib/test/test_warnings/__init__.py
+++ b/Lib/test/test_warnings/__init__.py
@@ -489,7 +489,14 @@ class WarnTests(BaseTest):
module=self.module) as w:
self.module.resetwarnings()
self.module.filterwarnings("always", category=UserWarning)
- for filename in ("nonascii\xe9\u20ac", "surrogate\udc80"):
+ filenames = ["nonascii\xe9\u20ac"]
+ if not support.is_emscripten:
+ # JavaScript does not like surrogates.
+ # Invalid UTF-8 leading byte 0x80 encountered when
+ # deserializing a UTF-8 string in wasm memory to a JS
+ # string!
+ filenames.append("surrogate\udc80")
+ for filename in filenames:
try:
os.fsencode(filename)
except UnicodeEncodeError:
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 8fbcd7a..3efc6c2 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -817,10 +817,11 @@ $(DLLLIBRARY) libpython$(LDVERSION).dll.a: $(LIBRARY_OBJS)
# wasm assets directory is relative to current build dir, e.g. "./usr/local".
# --preload-file turns a relative asset path into an absolute path.
+.PHONY: wasm_stdlib
+wasm_stdlib: $(WASM_STDLIB)
$(WASM_STDLIB): $(srcdir)/Lib/*.py $(srcdir)/Lib/*/*.py \
$(srcdir)/Tools/wasm/wasm_assets.py \
- Makefile pybuilddir.txt Modules/Setup.local \
- python.html python.worker.js
+ Makefile pybuilddir.txt Modules/Setup.local
$(PYTHON_FOR_BUILD) $(srcdir)/Tools/wasm/wasm_assets.py \
--buildroot . --prefix $(prefix)
@@ -1713,6 +1714,10 @@ buildbottest: all
fi
$(TESTRUNNER) -j 1 -u all -W --slowest --fail-env-changed --timeout=$(TESTTIMEOUT) $(TESTOPTS)
+# Like testall, but run Python tests with HOSTRUNNER directly.
+hostrunnertest: all
+ $(RUNSHARED) $(HOSTRUNNER) ./$(BUILDPYTHON) -m test -u all $(TESTOPTS)
+
pythoninfo: all
$(RUNSHARED) $(HOSTRUNNER) ./$(BUILDPYTHON) -m test.pythoninfo
diff --git a/Misc/NEWS.d/next/Tools-Demos/2022-08-10-17-08-43.gh-issue-95853.HCjC2m.rst b/Misc/NEWS.d/next/Tools-Demos/2022-08-10-17-08-43.gh-issue-95853.HCjC2m.rst
new file mode 100644
index 0000000..c38db3a
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2022-08-10-17-08-43.gh-issue-95853.HCjC2m.rst
@@ -0,0 +1,2 @@
+The new tool ``Tools/wasm/wasm_builder.py`` automates configure, compile, and
+test steps for building CPython on WebAssembly platforms.
diff --git a/Misc/NEWS.d/next/Tools-Demos/2022-08-29-17-25-13.gh-issue-95853.Ce17cT.rst b/Misc/NEWS.d/next/Tools-Demos/2022-08-29-17-25-13.gh-issue-95853.Ce17cT.rst
new file mode 100644
index 0000000..1cd1ce1
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2022-08-29-17-25-13.gh-issue-95853.Ce17cT.rst
@@ -0,0 +1,2 @@
+The ``wasm_build.py`` script now pre-builds Emscripten ports, checks for
+broken EMSDK versions, and warns about pkg-config env vars.
diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c
index 12319ee..a971342 100644
--- a/Modules/pyexpat.c
+++ b/Modules/pyexpat.c
@@ -775,7 +775,7 @@ readinst(char *buf, int buf_size, PyObject *meth)
Py_ssize_t len;
const char *ptr;
- str = PyObject_CallFunction(meth, "n", buf_size);
+ str = PyObject_CallFunction(meth, "i", buf_size);
if (str == NULL)
goto error;
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index dca97f2..6f703e3 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -2775,14 +2775,18 @@ EM_JS(char *, _Py_emscripten_runtime, (void), {
if (typeof navigator == 'object') {
info = navigator.userAgent;
} else if (typeof process == 'object') {
- info = "Node.js ".concat(process.version)
+ info = "Node.js ".concat(process.version);
} else {
- info = "UNKNOWN"
+ info = "UNKNOWN";
}
var len = lengthBytesUTF8(info) + 1;
var res = _malloc(len);
- stringToUTF8(info, res, len);
+ if (res) stringToUTF8(info, res, len);
+#if __wasm64__
+ return BigInt(res);
+#else
return res;
+#endif
});
static PyObject *
diff --git a/Tools/wasm/README.md b/Tools/wasm/README.md
index 6496a29..fe9a1dc 100644
--- a/Tools/wasm/README.md
+++ b/Tools/wasm/README.md
@@ -1,11 +1,16 @@
# Python WebAssembly (WASM) build
-**WARNING: WASM support is highly experimental! Lots of features are not working yet.**
+**WARNING: WASM support is work-in-progress! Lots of features are not working yet.**
This directory contains configuration and helpers to facilitate cross
-compilation of CPython to WebAssembly (WASM). For now we support
-*wasm32-emscripten* builds for modern browser and for *Node.js*. WASI
-(*wasm32-wasi*) is work-in-progress
+compilation of CPython to WebAssembly (WASM). Python supports Emscripten
+(*wasm32-emscripten*) and WASI (*wasm32-wasi*) targets. Emscripten builds
+run in modern browsers and JavaScript runtimes like *Node.js*. WASI builds
+use WASM runtimes such as *wasmtime*.
+
+Users and developers are encouraged to use the script
+`Tools/wasm/wasm_build.py`. The tool automates the build process and provides
+assistance with installation of SDKs.
## wasm32-emscripten build
@@ -17,7 +22,7 @@ access the file system directly.
Cross compiling to the wasm32-emscripten platform needs the
[Emscripten](https://emscripten.org/) SDK and a build Python interpreter.
-Emscripten 3.1.8 or newer are recommended. All commands below are relative
+Emscripten 3.1.19 or newer are recommended. All commands below are relative
to a repository checkout.
Christian Heimes maintains a container image with Emscripten SDK, Python
@@ -35,7 +40,13 @@ docker run --rm -ti -v $(pwd):/python-wasm/cpython -w /python-wasm/cpython quay.
### Compile a build Python interpreter
-From within the container, run the following commands:
+From within the container, run the following command:
+
+```shell
+./Tools/wasm/wasm_build.py build
+```
+
+The command is roughly equivalent to:
```shell
mkdir -p builddir/build
@@ -45,13 +56,13 @@ make -j$(nproc)
popd
```
-### Fetch and build additional emscripten ports
+### Cross-compile to wasm32-emscripten for browser
```shell
-embuilder build zlib bzip2
+./Tools/wasm/wasm_build.py emscripten-browser
```
-### Cross compile to wasm32-emscripten for browser
+The command is roughly equivalent to:
```shell
mkdir -p builddir/emscripten-browser
@@ -85,14 +96,21 @@ and header files with debug builds.
### Cross compile to wasm32-emscripten for node
```shell
-mkdir -p builddir/emscripten-node
-pushd builddir/emscripten-node
+./Tools/wasm/wasm_build.py emscripten-browser-dl
+```
+
+The command is roughly equivalent to:
+
+```shell
+mkdir -p builddir/emscripten-node-dl
+pushd builddir/emscripten-node-dl
CONFIG_SITE=../../Tools/wasm/config.site-wasm32-emscripten \
emconfigure ../../configure -C \
--host=wasm32-unknown-emscripten \
--build=$(../../config.guess) \
--with-emscripten-target=node \
+ --enable-wasm-dynamic-linking \
--with-build-python=$(pwd)/../build/python
emmake make -j$(nproc)
@@ -100,7 +118,7 @@ popd
```
```shell
-node --experimental-wasm-threads --experimental-wasm-bulk-memory --experimental-wasm-bigint builddir/emscripten-node/python.js
+node --experimental-wasm-threads --experimental-wasm-bulk-memory --experimental-wasm-bigint builddir/emscripten-node-dl/python.js
```
(``--experimental-wasm-bigint`` is not needed with recent NodeJS versions)
@@ -199,6 +217,15 @@ Node builds use ``NODERAWFS``.
- Node RawFS allows direct access to the host file system without need to
perform ``FS.mount()`` call.
+## wasm64-emscripten
+
+- wasm64 requires recent NodeJS and ``--experimental-wasm-memory64``.
+- ``EM_JS`` functions must return ``BigInt()``.
+- ``Py_BuildValue()`` format strings must match size of types. Confusing 32
+ and 64 bits types leads to memory corruption, see
+ [gh-95876](https://github.com/python/cpython/issues/95876) and
+ [gh-95878](https://github.com/python/cpython/issues/95878).
+
# Hosting Python WASM builds
The simple REPL terminal uses SharedArrayBuffer. For security reasons
@@ -235,6 +262,12 @@ The script ``wasi-env`` sets necessary compiler and linker flags as well as
``/opt/wasi-sdk`` or ``$WASI_SDK_PATH``.
```shell
+./Tools/wasm/wasm_build.py wasi
+```
+
+The command is roughly equivalent to:
+
+```shell
mkdir -p builddir/wasi
pushd builddir/wasi
@@ -308,26 +341,46 @@ if os.name == "posix":
```python
>>> import os, sys
>>> os.uname()
-posix.uname_result(sysname='Emscripten', nodename='emscripten', release='1.0', version='#1', machine='wasm32')
+posix.uname_result(
+ sysname='Emscripten',
+ nodename='emscripten',
+ release='3.1.19',
+ version='#1',
+ machine='wasm32'
+)
>>> os.name
'posix'
>>> sys.platform
'emscripten'
>>> sys._emscripten_info
sys._emscripten_info(
- emscripten_version=(3, 1, 8),
- runtime='Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0',
+ emscripten_version=(3, 1, 10),
+ runtime='Mozilla/5.0 (X11; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0',
pthreads=False,
shared_memory=False
)
+```
+
+```python
>>> sys._emscripten_info
-sys._emscripten_info(emscripten_version=(3, 1, 8), runtime='Node.js v14.18.2', pthreads=True, shared_memory=True)
+sys._emscripten_info(
+ emscripten_version=(3, 1, 19),
+ runtime='Node.js v14.18.2',
+ pthreads=True,
+ shared_memory=True
+)
```
```python
>>> import os, sys
>>> os.uname()
-posix.uname_result(sysname='wasi', nodename='(none)', release='0.0.0', version='0.0.0', machine='wasm32')
+posix.uname_result(
+ sysname='wasi',
+ nodename='(none)',
+ release='0.0.0',
+ version='0.0.0',
+ machine='wasm32'
+)
>>> os.name
'posix'
>>> sys.platform
@@ -418,7 +471,8 @@ embuilder build --pic zlib bzip2 MINIMAL_PIC
**NOTE**: WASI-SDK's clang may show a warning on Fedora:
``/lib64/libtinfo.so.6: no version information available``,
-[RHBZ#1875587](https://bugzilla.redhat.com/show_bug.cgi?id=1875587).
+[RHBZ#1875587](https://bugzilla.redhat.com/show_bug.cgi?id=1875587). The
+warning can be ignored.
```shell
export WASI_VERSION=16
@@ -443,6 +497,8 @@ ln -srf -t /usr/local/bin/ ~/.wasmtime/bin/wasmtime
### WASI debugging
-* ``wasmtime run -g`` generates debugging symbols for gdb and lldb.
+* ``wasmtime run -g`` generates debugging symbols for gdb and lldb. The
+ feature is currently broken, see
+ https://github.com/bytecodealliance/wasmtime/issues/4669 .
* The environment variable ``RUST_LOG=wasi_common`` enables debug and
trace logging.
diff --git a/Tools/wasm/wasi-env b/Tools/wasm/wasi-env
index 6c2d56e..48908b0 100755
--- a/Tools/wasm/wasi-env
+++ b/Tools/wasm/wasi-env
@@ -72,4 +72,5 @@ export CFLAGS LDFLAGS
export PKG_CONFIG_PATH PKG_CONFIG_LIBDIR PKG_CONFIG_SYSROOT_DIR
export PATH
-exec "$@"
+# no exec, it makes arvg[0] path absolute.
+"$@"
diff --git a/Tools/wasm/wasm_assets.py b/Tools/wasm/wasm_assets.py
index 34a9bb5..9b0bee3 100755
--- a/Tools/wasm/wasm_assets.py
+++ b/Tools/wasm/wasm_assets.py
@@ -116,6 +116,14 @@ OMIT_SUBDIRS = (
"unittest/test/",
)
+SYSCONFIG_NAMES = (
+ "_sysconfigdata__emscripten_wasm32-emscripten",
+ "_sysconfigdata__emscripten_wasm32-emscripten",
+ "_sysconfigdata__wasi_wasm32-wasi",
+ "_sysconfigdata__wasi_wasm64-wasi",
+)
+
+
def get_builddir(args: argparse.Namespace) -> pathlib.Path:
"""Get builddir path from pybuilddir.txt
"""
@@ -128,7 +136,11 @@ def get_sysconfigdata(args: argparse.Namespace) -> pathlib.Path:
"""Get path to sysconfigdata relative to build root
"""
data_name = sysconfig._get_sysconfigdata_name()
- assert "emscripten_wasm32" in data_name
+ if not data_name.startswith(SYSCONFIG_NAMES):
+ raise ValueError(
+ f"Invalid sysconfig data name '{data_name}'.",
+ SYSCONFIG_NAMES
+ )
filename = data_name + ".py"
return args.builddir / filename
diff --git a/Tools/wasm/wasm_build.py b/Tools/wasm/wasm_build.py
new file mode 100755
index 0000000..63812c6
--- /dev/null
+++ b/Tools/wasm/wasm_build.py
@@ -0,0 +1,907 @@
+#!/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 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.PurePath("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 = f"""
+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.PurePath, pathlib.PurePath]:
+ """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("^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):
+ self.info = info
+ self.text = text
+
+ def __str__(self):
+ 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.PurePath]
+ make_wrapper: Optional[pathlib.PurePath]
+ environ: dict
+ check: Callable[[], None]
+ # Used for build_emports().
+ ports: Optional[pathlib.PurePath]
+ cc: Optional[pathlib.PurePath]
+
+ def getenv(self, profile: "BuildProfile") -> dict:
+ return self.environ.copy()
+
+
+def _check_clean_src():
+ 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():
+ 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():
+ if EMSCRIPTEN_ROOT is _MISSING:
+ raise MissingDependency("Emscripten SDK EM_CONFIG", INSTALL_EMSDK)
+ # sanity check
+ emconfigure = EMSCRIPTEN.configure_wrapper
+ if 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 = 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():
+ 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 "
+ "--env PYTHONPATH=/{relbuilddir}/build/lib.wasi-wasm32-{version}:/Lib "
+ "--mapdir /::{srcdir} --"
+ ),
+ "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):
+ 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):
+ 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:
+ assert self.host.is_emscripten
+ 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:
+ """Generate environ dict for platform"""
+ env = os.environ.copy()
+ env.setdefault("MAKEFLAGS", f"-j{os.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,
+ ):
+ 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):
+ if self.is_browser:
+ raise ValueError(f"Cannot execute on {self.target}")
+
+ def run_build(self, *args):
+ """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):
+ """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):
+ """Run make (defaults to build all)"""
+ return self._run_cmd(self.make_cmd, args)
+
+ def run_pythoninfo(self, *args):
+ """Run 'make pythoninfo'"""
+ self._check_execute()
+ return self.run_make("pythoninfo", *args)
+
+ def run_test(self, target: str, testopts: Optional[str] = None):
+ """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):
+ """Run Python with hostrunner"""
+ self._check_execute()
+ self.run_make(
+ "--eval", f"run: all; $(HOSTRUNNER) ./$(PYTHON) {shlex.join(args)}", "run"
+ )
+
+ def run_browser(self, bind="127.0.0.1", port=8000):
+ """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 s:
+ pass
+ except OSError:
+ time.sleep(0.01)
+ else:
+ break
+
+ webbrowser.open(url)
+
+ try:
+ srv.wait()
+ except KeyboardInterrupt:
+ pass
+
+ def clean(self, all: bool = False):
+ """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):
+ """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,
+ ),
+ # 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():
+ 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()
diff --git a/configure b/configure
index 784f8d3..2d03c4f 100755
--- a/configure
+++ b/configure
@@ -888,6 +888,7 @@ AR
LINK_PYTHON_OBJS
LINK_PYTHON_DEPS
LIBRARY_DEPS
+NODE
HOSTRUNNER
STATIC_LIBPYTHON
GNULD
@@ -4038,6 +4039,16 @@ if test -z "$CFLAGS"; then
CFLAGS=
fi
+case $host in #(
+ wasm64-*-emscripten) :
+
+ as_fn_append CFLAGS " -sMEMORY64=1"
+ as_fn_append LDFLAGS " -sMEMORY64=1"
+ ;; #(
+ *) :
+ ;;
+esac
+
if test "$ac_sys_system" = "Darwin"
then
# Compiler selection on MacOSX is more complicated than
@@ -6158,7 +6169,7 @@ cat > conftest.c <<EOF
# error unknown wasm32 platform
# endif
#elif defined(__wasm64__)
-# if defined(__EMSCRIPTEN)
+# if defined(__EMSCRIPTEN__)
wasm64-emscripten
# elif defined(__wasi__)
wasm64-wasi
@@ -6778,20 +6789,163 @@ if test "$cross_compiling" = yes; then
fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking HOSTRUNNER" >&5
-$as_echo_n "checking HOSTRUNNER... " >&6; }
if test -z "$HOSTRUNNER"
then
case $ac_sys_system/$ac_sys_emscripten_target in #(
Emscripten/node*) :
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}node", so it can be a program name with args.
+set dummy ${ac_tool_prefix}node; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NODE+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NODE in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NODE="$NODE" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NODE="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NODE=$ac_cv_path_NODE
+if test -n "$NODE"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NODE" >&5
+$as_echo "$NODE" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_path_NODE"; then
+ ac_pt_NODE=$NODE
+ # Extract the first word of "node", so it can be a program name with args.
+set dummy node; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_ac_pt_NODE+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $ac_pt_NODE in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_ac_pt_NODE="$ac_pt_NODE" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_ac_pt_NODE="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+ac_pt_NODE=$ac_cv_path_ac_pt_NODE
+if test -n "$ac_pt_NODE"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_NODE" >&5
+$as_echo "$ac_pt_NODE" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_pt_NODE" = x; then
+ NODE="node"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ NODE=$ac_pt_NODE
+ fi
+else
+ NODE="$ac_cv_path_NODE"
+fi
+
+ HOSTRUNNER="$NODE"
# bigint for ctypes c_longlong, c_longdouble
- HOSTRUNNER="node --experimental-wasm-bigint"
+ # no longer available in Node 16
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for node --experimental-wasm-bigint" >&5
+$as_echo_n "checking for node --experimental-wasm-bigint... " >&6; }
+if ${ac_cv_tool_node_wasm_bigint+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ if $NODE -v --experimental-wasm-bigint > /dev/null 2>&1; then
+ ac_cv_tool_node_wasm_bigint=yes
+ else
+ ac_cv_tool_node_wasm_bigint=no
+ fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_tool_node_wasm_bigint" >&5
+$as_echo "$ac_cv_tool_node_wasm_bigint" >&6; }
+ if test "x$ac_cv_tool_node_wasm_bigint" = xyes; then :
+
+ as_fn_append HOSTRUNNER " --experimental-wasm-bigint"
+
+fi
+
if test "x$enable_wasm_pthreads" = xyes; then :
- HOSTRUNNER="$HOSTRUNNER --experimental-wasm-threads --experimental-wasm-bulk-memory"
+ as_fn_append HOSTRUNNER " --experimental-wasm-threads"
+ # no longer available in Node 16
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for node --experimental-wasm-bulk-memory" >&5
+$as_echo_n "checking for node --experimental-wasm-bulk-memory... " >&6; }
+if ${ac_cv_tool_node_wasm_bulk_memory+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ if $NODE -v --experimental-wasm-bulk-memory > /dev/null 2>&1; then
+ ac_cv_tool_node_wasm_bulk_memory=yes
+ else
+ ac_cv_tool_node_wasm_bulk_memory=no
+ fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_tool_node_wasm_bulk_memory" >&5
+$as_echo "$ac_cv_tool_node_wasm_bulk_memory" >&6; }
+ if test "x$ac_cv_tool_node_wasm_bulk_memory" = xyes; then :
+
+ as_fn_append HOSTRUNNER " --experimental-wasm-bulk-memory"
fi
+
+fi
+
+ if test "x$host_cpu" = xwasm64; then :
+ as_fn_append HOSTRUNNER " --experimental-wasm-memory64"
+fi
;; #(
WASI/*) :
HOSTRUNNER='wasmtime run --env PYTHONPATH=/$(shell realpath --relative-to $(abs_srcdir) $(abs_builddir))/$(shell cat pybuilddir.txt) --mapdir /::$(srcdir) --' ;; #(
@@ -6801,6 +6955,8 @@ fi
esac
fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking HOSTRUNNER" >&5
+$as_echo_n "checking HOSTRUNNER... " >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $HOSTRUNNER" >&5
$as_echo "$HOSTRUNNER" >&6; }
@@ -6814,7 +6970,7 @@ $as_echo "$LDLIBRARY" >&6; }
# LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable
case $ac_sys_system/$ac_sys_emscripten_target in #(
Emscripten/browser*) :
- LIBRARY_DEPS='$(PY3LIBRARY) $(WASM_STDLIB)' ;; #(
+ LIBRARY_DEPS='$(PY3LIBRARY) $(WASM_STDLIB) python.html python.worker.js' ;; #(
*) :
LIBRARY_DEPS='$(PY3LIBRARY) $(EXPORTSYMS)'
;;
diff --git a/configure.ac b/configure.ac
index ab5e1de..1d7e1be 100644
--- a/configure.ac
+++ b/configure.ac
@@ -753,6 +753,16 @@ if test -z "$CFLAGS"; then
CFLAGS=
fi
+dnl Emscripten SDK and WASI SDK default to wasm32.
+dnl On Emscripten use MEMORY64 setting to build target wasm64-emscripten.
+dnl for wasm64.
+AS_CASE([$host],
+ [wasm64-*-emscripten], [
+ AS_VAR_APPEND([CFLAGS], [" -sMEMORY64=1"])
+ AS_VAR_APPEND([LDFLAGS], [" -sMEMORY64=1"])
+ ],
+)
+
if test "$ac_sys_system" = "Darwin"
then
# Compiler selection on MacOSX is more complicated than
@@ -1056,7 +1066,7 @@ cat > conftest.c <<EOF
# error unknown wasm32 platform
# endif
#elif defined(__wasm64__)
-# if defined(__EMSCRIPTEN)
+# if defined(__EMSCRIPTEN__)
wasm64-emscripten
# elif defined(__wasi__)
wasm64-wasi
@@ -1523,16 +1533,41 @@ if test "$cross_compiling" = yes; then
fi
AC_ARG_VAR([HOSTRUNNER], [Program to run CPython for the host platform])
-AC_MSG_CHECKING([HOSTRUNNER])
if test -z "$HOSTRUNNER"
then
AS_CASE([$ac_sys_system/$ac_sys_emscripten_target],
[Emscripten/node*], [
+ AC_PATH_TOOL([NODE], [node], [node])
+ HOSTRUNNER="$NODE"
# bigint for ctypes c_longlong, c_longdouble
- HOSTRUNNER="node --experimental-wasm-bigint"
+ # no longer available in Node 16
+ AC_CACHE_CHECK([for node --experimental-wasm-bigint], [ac_cv_tool_node_wasm_bigint], [
+ if $NODE -v --experimental-wasm-bigint > /dev/null 2>&1; then
+ ac_cv_tool_node_wasm_bigint=yes
+ else
+ ac_cv_tool_node_wasm_bigint=no
+ fi
+ ])
+ AS_VAR_IF([ac_cv_tool_node_wasm_bigint], [yes], [
+ AS_VAR_APPEND([HOSTRUNNER], [" --experimental-wasm-bigint"])
+ ])
+
AS_VAR_IF([enable_wasm_pthreads], [yes], [
- HOSTRUNNER="$HOSTRUNNER --experimental-wasm-threads --experimental-wasm-bulk-memory"
+ AS_VAR_APPEND([HOSTRUNNER], [" --experimental-wasm-threads"])
+ # no longer available in Node 16
+ AC_CACHE_CHECK([for node --experimental-wasm-bulk-memory], [ac_cv_tool_node_wasm_bulk_memory], [
+ if $NODE -v --experimental-wasm-bulk-memory > /dev/null 2>&1; then
+ ac_cv_tool_node_wasm_bulk_memory=yes
+ else
+ ac_cv_tool_node_wasm_bulk_memory=no
+ fi
+ ])
+ AS_VAR_IF([ac_cv_tool_node_wasm_bulk_memory], [yes], [
+ AS_VAR_APPEND([HOSTRUNNER], [" --experimental-wasm-bulk-memory"])
+ ])
])
+
+ AS_VAR_IF([host_cpu], [wasm64], [AS_VAR_APPEND([HOSTRUNNER], [" --experimental-wasm-memory64"])])
],
dnl TODO: support other WASI runtimes
dnl wasmtime starts the proces with "/" as CWD. For OOT builds add the
@@ -1542,6 +1577,7 @@ then
)
fi
AC_SUBST([HOSTRUNNER])
+AC_MSG_CHECKING([HOSTRUNNER])
AC_MSG_RESULT([$HOSTRUNNER])
if test -n "$HOSTRUNNER"; then
@@ -1553,7 +1589,7 @@ AC_MSG_RESULT($LDLIBRARY)
# LIBRARY_DEPS, LINK_PYTHON_OBJS and LINK_PYTHON_DEPS variable
AS_CASE([$ac_sys_system/$ac_sys_emscripten_target],
- [Emscripten/browser*], [LIBRARY_DEPS='$(PY3LIBRARY) $(WASM_STDLIB)'],
+ [Emscripten/browser*], [LIBRARY_DEPS='$(PY3LIBRARY) $(WASM_STDLIB) python.html python.worker.js'],
[LIBRARY_DEPS='$(PY3LIBRARY) $(EXPORTSYMS)']
)
LINK_PYTHON_DEPS='$(LIBRARY_DEPS)'