Skip to content

Commit 637afd3

Browse files
authored
ci: update / clean up wasm build (#6166)
* ci: update / clean up wasm build * drop `--quiet` * fix CC variable conflict * switch wasm jobs to uv * debug * fix `test-wasm` path
1 parent b3aa7da commit 637afd3

9 files changed

Lines changed: 265 additions & 144 deletions

File tree

.github/workflows/ci.yml

Lines changed: 25 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -483,10 +483,9 @@ jobs:
483483
runs-on: ubuntu-latest
484484
steps:
485485
- uses: actions/checkout@v7.0.0
486-
- uses: actions/setup-python@v6
486+
- uses: astral-sh/setup-uv@v7
487487
with:
488-
python-version: 3.14
489-
id: setup-python
488+
save-cache: ${{ needs.resolve.outputs.save-cache }}
490489
- name: Install Rust toolchain
491490
uses: dtolnay/rust-toolchain@stable
492491
with:
@@ -495,42 +494,37 @@ jobs:
495494
- uses: actions/setup-node@v6
496495
with:
497496
node-version: 24
498-
- run: python -m pip install --upgrade pip && pip install nox[uv]
499497
- uses: actions/cache/restore@v5
500498
id: cache
501499
with:
502500
path: |
503501
.nox/emscripten
504-
key: emscripten-${{ hashFiles('emscripten/*') }}-${{ hashFiles('noxfile.py') }}-${{ steps.setup-python.outputs.python-path }}
502+
key: emscripten-${{ hashFiles('wasm/emscripten/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ env.UV_PYTHON }}
505503
- uses: Swatinem/rust-cache@v2
506504
with:
507505
save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }}
508506
- name: Build
509507
if: steps.cache.outputs.cache-hit != 'true'
510-
run: nox -s build-emscripten
508+
run: uvx nox -s build-emscripten
511509
- name: Test
512-
run: nox -s test-emscripten
510+
run: uvx nox -s test-emscripten
513511
- uses: actions/cache/save@v5
514512
if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }}
515513
with:
516514
path: |
517515
.nox/emscripten
518-
key: emscripten-${{ hashFiles('emscripten/*') }}-${{ hashFiles('noxfile.py') }}-${{ steps.setup-python.outputs.python-path }}
516+
key: emscripten-${{ hashFiles('wasm/emscripten/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ env.UV_PYTHON }}
519517

520518
wasm32-wasip1:
521519
name: wasm32-wasip1
522520
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}
523521
needs: [fmt]
524522
runs-on: ubuntu-latest
525-
env:
526-
WASI_SDK_PATH: "/opt/wasi-sdk"
527-
CPYTHON_PATH: "${{ github.workspace }}/wasi/cpython"
528523
steps:
529524
- uses: actions/checkout@v7.0.0
530-
- uses: actions/setup-python@v6
525+
- uses: astral-sh/setup-uv@v7
531526
with:
532-
python-version: 3.14
533-
id: setup-python
527+
save-cache: ${{ needs.resolve.outputs.save-cache }}
534528
- name: Install Rust toolchain
535529
uses: dtolnay/rust-toolchain@stable
536530
with:
@@ -539,59 +533,32 @@ jobs:
539533
- name: "Install wasmtime"
540534
uses: bytecodealliance/actions/wasmtime/setup@v1
541535
- name: "Install WASI SDK"
542-
run: |
543-
mkdir ${{ env.WASI_SDK_PATH }} && \
544-
curl -s -S --location https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-linux.tar.gz | \
545-
tar --strip-components 1 --directory ${{ env.WASI_SDK_PATH }} --extract --gunzip
546-
$WASI_SDK_PATH/bin/clang --version
547-
- uses: actions/cache/restore@v5
548-
id: cache-wasip1-python
536+
uses: bytecodealliance/setup-wasi-sdk-action@main
549537
with:
550-
path: ${{ env.CPYTHON_PATH }}/cross-build/
551-
key: wasm32-wasip1-python
552-
- uses: actions/checkout@v7.0.0
538+
version: "24"
539+
# wasi sdk sets CC variables which break Python's configure script
540+
# (it also sets WASI_SDK_PATH even without `add-to-path`, which is sufficient)
541+
add-to-path: false
542+
- uses: actions/cache/restore@v5
543+
id: cache
553544
with:
554-
repository: python/cpython
555-
ref: 3.14
556-
path: ${{ env.CPYTHON_PATH }}
557-
fetch-depth: 1
558-
- name: Build
559-
run: |
560-
cd ${{ env.CPYTHON_PATH }}
561-
cat >> Tools/wasm/wasi/config.site-wasm32-wasi <<'EOF'
562-
563-
# Force-disable POSIX dynamic loading for WASI
564-
ac_cv_func_dlopen=no
565-
ac_cv_lib_dl_dlopen=no
566-
EOF
567-
python Tools/wasm/wasi build --quiet -- --config-cache
568-
cp cross-build/wasm32-wasip1/libpython3.14.a \
569-
cross-build/wasm32-wasip1/Modules/_hacl/libHacl_HMAC.a \
570-
cross-build/wasm32-wasip1/Modules/_decimal/libmpdec/libmpdec.a \
571-
cross-build/wasm32-wasip1/Modules/expat/libexpat.a \
572-
cross-build/wasm32-wasip1/build/lib.wasi-wasm32-3.14/
545+
path: |
546+
.nox/wasi
547+
key: wasi-${{ hashFiles('wasm/wasi/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ env.UV_PYTHON }}
573548
- uses: Swatinem/rust-cache@v2
574549
with:
575550
save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }}
551+
- name: Build
552+
if: steps.cache.outputs.cache-hit != 'true'
553+
run: uvx nox -s build-wasm
576554
- name: Test
577-
env:
578-
PYO3_CROSS_LIB_DIR: ${{ env.CPYTHON_PATH }}/cross-build/wasm32-wasip1/build/lib.wasi-wasm32-3.14/
579-
CARGO_BUILD_TARGET: wasm32-wasip1
580-
CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime run --dir ${{ env.CPYTHON_PATH }}::/ --env PYTHONPATH=/lib
581-
RUSTFLAGS: >
582-
-C link-arg=-L${{ env.WASI_SDK_PATH }}/share/wasi-sysroot/lib/wasm32-wasi
583-
-C link-arg=-lwasi-emulated-signal
584-
-C link-arg=-lwasi-emulated-process-clocks
585-
-C link-arg=-lwasi-emulated-getpid
586-
-C link-arg=-lmpdec
587-
-C link-arg=-lHacl_HMAC
588-
-C link-arg=-lexpat
589-
run: RUSTDOCFLAGS=$RUSTFLAGS cargo test
555+
run: uvx nox -s test-wasm
590556
- uses: actions/cache/save@v5
591557
if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }}
592558
with:
593-
path: ${{ env.CPYTHON_PATH }}/cross-build/
594-
key: ${{ steps.cache-wasip1-python.outputs.cache-primary-key }}
559+
path: |
560+
.nox/wasi
561+
key: wasi-${{ hashFiles('wasm/wasi/*', 'wasm/common.mk') }}-${{ hashFiles('noxfile.py') }}-${{ env.UV_PYTHON }}
595562

596563
test-debug:
597564
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }}

emscripten/Makefile

Lines changed: 0 additions & 85 deletions
This file was deleted.

noxfile.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ def contributors(session: nox.Session) -> None:
426426

427427
class EmscriptenInfo:
428428
def __init__(self):
429-
self.emscripten_dir = PYO3_DIR / "emscripten"
429+
self.emscripten_dir = PYO3_DIR / "wasm" / "emscripten"
430430
self.builddir = PYO3_DIR / ".nox/emscripten"
431431
self.builddir.mkdir(exist_ok=True, parents=True)
432432

@@ -503,6 +503,75 @@ def test_emscripten(session: nox.Session):
503503
)
504504

505505

506+
class WasiInfo:
507+
def __init__(self):
508+
self.wasi_dir = PYO3_DIR / "wasm" / "wasi"
509+
self.builddir = PYO3_DIR / ".nox/wasi"
510+
self.builddir.mkdir(exist_ok=True, parents=True)
511+
512+
self.pyversion = sys.version.split()[0]
513+
self.pymajor, self.pyminor = self.pyversion.split(".")[:2]
514+
self.pymajorminor = f"{self.pymajor}.{self.pyminor}"
515+
516+
# In CI the WASI SDK is installed by setup-wasi-sdk-action (sets WASI_SDK_PATH);
517+
# otherwise the Makefile downloads it into the build dir.
518+
wasi_sdk_env = os.environ.get("WASI_SDK_PATH")
519+
self.wasi_sdk = (
520+
Path(wasi_sdk_env) if wasi_sdk_env else self.builddir / "wasi-sdk"
521+
)
522+
self.cpython_dir = self.builddir / "build" / f"Python-{self.pyversion}"
523+
crossbuild_dir = self.cpython_dir / "cross-build" / "wasm32-wasip1"
524+
self.libdir = crossbuild_dir / "build" / f"lib.wasi-wasm32-{self.pymajorminor}"
525+
526+
527+
@nox.session(name="build-wasm", venv_backend="none")
528+
def build_wasm(session: nox.Session):
529+
info = WasiInfo()
530+
_run(
531+
session,
532+
"make",
533+
"-C",
534+
str(info.wasi_dir),
535+
f"PYTHON={sys.executable}",
536+
f"BUILDROOT={info.builddir}",
537+
f"PYMAJORMINORMICRO={info.pyversion}",
538+
external=True,
539+
)
540+
541+
542+
@nox.session(name="test-wasm", venv_backend="none")
543+
def test_wasm(session: nox.Session):
544+
info = WasiInfo()
545+
546+
target = "wasm32-wasip1"
547+
548+
# if wasmtime was installed by build-wasm, this is where it would be;
549+
# in CI it is installed by the wasmtime/setup action
550+
session.env["PATH"] = (
551+
f"{info.builddir / 'wasmtime'}{os.pathsep}{os.environ['PATH']}"
552+
)
553+
session.env["PYO3_CROSS_LIB_DIR"] = str(info.libdir)
554+
session.env["CARGO_BUILD_TARGET"] = target
555+
session.env["CARGO_TARGET_WASM32_WASIP1_RUNNER"] = (
556+
f"wasmtime run --dir {info.cpython_dir}::/ --env PYTHONPATH=/lib"
557+
)
558+
session.env["RUSTFLAGS"] = " ".join(
559+
[
560+
f"-C link-arg=-L{info.wasi_sdk}/share/wasi-sysroot/lib/wasm32-wasi",
561+
"-C link-arg=-lwasi-emulated-signal",
562+
"-C link-arg=-lwasi-emulated-process-clocks",
563+
"-C link-arg=-lwasi-emulated-getpid",
564+
"-C link-arg=-lmpdec",
565+
"-C link-arg=-lHacl_HMAC",
566+
"-C link-arg=-lexpat",
567+
]
568+
)
569+
session.env["RUSTDOCFLAGS"] = session.env["RUSTFLAGS"]
570+
_run(session, "rustup", "target", "add", target, "--toolchain", "stable")
571+
572+
_run(session, "cargo", "test", *session.posargs, external=True)
573+
574+
506575
@nox.session(name="test-cross-compilation-windows")
507576
def test_cross_compilation_windows(session: nox.Session):
508577
session.install("cargo-xwin")

wasm/common.mk

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Shared logic to download Python source tarball for the wasm builds.
2+
3+
CURDIR=$(abspath .)
4+
5+
# These three are passed in from nox.
6+
BUILDROOT ?= $(CURDIR)/builddir
7+
PYTHON ?= python3
8+
PYMAJORMINORMICRO ?= $(shell $(PYTHON) --version 2>&1 | awk '{print $$2}')
9+
10+
# Set version variables.
11+
version_tuple := $(subst ., ,$(PYMAJORMINORMICRO:v%=%))
12+
PYMAJOR=$(word 1,$(version_tuple))
13+
PYMINOR=$(word 2,$(version_tuple))
14+
PYMICRO=$(word 3,$(version_tuple))
15+
PYVERSION=$(PYMAJORMINORMICRO)
16+
PYMAJORMINOR=$(PYMAJOR).$(PYMINOR)
17+
18+
ifneq ($(PYMAJORMINOR),3.14)
19+
$(error PYMAJORMINOR must be 3.14, got '$(PYMAJORMINOR)')
20+
endif
21+
22+
PYTHONURL=https://www.python.org/ftp/python/$(PYMAJORMINORMICRO)/Python-$(PYVERSION).tgz
23+
PYTHONTARBALL=$(BUILDROOT)/downloads/Python-$(PYVERSION).tgz
24+
PYTHONBUILD=$(BUILDROOT)/build/Python-$(PYVERSION)
25+
26+
.DEFAULT_GOAL := all
27+
28+
$(BUILDROOT)/.exists:
29+
mkdir -p $(BUILDROOT)
30+
touch $@
31+
32+
$(PYTHONTARBALL): $(BUILDROOT)/.exists
33+
mkdir -p $(BUILDROOT)/downloads
34+
curl -sL $(PYTHONURL) -o $@
35+
36+
$(PYTHONBUILD)/.exists: $(PYTHONTARBALL)
37+
[ -d $(PYTHONBUILD) ] || ( \
38+
mkdir -p $(dir $(PYTHONBUILD));\
39+
tar -C $(dir $(PYTHONBUILD)) -xf $(PYTHONTARBALL) \
40+
)
41+
touch $@
42+
43+
clean:
44+
rm -rf $(BUILDROOT)

0 commit comments

Comments
 (0)