From f78b8613df17c32e02efb79de25bf15649ee525f Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:31:26 -0400 Subject: [PATCH 01/19] Update setup.py --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ea87da73..80524612 100644 --- a/setup.py +++ b/setup.py @@ -129,7 +129,8 @@ def run(self): # Locate our configure script configure = abshere("src/libsodium/configure") - # Run ./configure + # Mirrored by .github/bin/build_pyemscripten_libsodium.sh — + # keep in sync. configure_flags = [ "--disable-shared", "--enable-static", From efe97229aaca6cff8c5c0faf182ce1d8ea4e8001 Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:31:59 -0400 Subject: [PATCH 02/19] Update pyproject.toml --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 97aae1be..db2aa343 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,6 +149,8 @@ exclude_also = [ "@abc.abstractmethod", "@typing.overload", "if typing.TYPE_CHECKING", + # Pyodide branches never run under the coverage-collecting CI jobs. + 'if sys.platform == "emscripten":', ] [tool.coverage.html] @@ -157,4 +159,7 @@ show_contexts = true [tool.pytest.ini_options] addopts = "-r s --capture=no" console_output_style = "progress-even-when-capture-no" +markers = [ + "skip_emscripten: this test is not executed under Emscripten/Pyodide", +] From 36bd0e8076fdb520cdf7d7009fbc23085e70f762 Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:32:29 -0400 Subject: [PATCH 03/19] Update ci.yml --- .github/workflows/ci.yml | 99 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81086d53..642284f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,9 +131,106 @@ jobs: shell: cmd - uses: ./.github/actions/upload-coverage + pyemscripten: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + PYODIDE: + # TODO: switch to stable Pyodide 314.0 when released. Keep these + # pins in lockstep with wheel-builder.yml::pyemscripten. + - VERSION: "314.0.0a1" + EMSDK: "5.0.3" + PYTHON: "3.14" + name: "pyemscripten (Pyodide ${{ matrix.PYODIDE.VERSION }})" + env: + PYODIDE_VERSION: ${{ matrix.PYODIDE.VERSION }} + EMSDK_VERSION: ${{ matrix.PYODIDE.EMSDK }} + steps: + - uses: actions/checkout@v6.0.2 + + - name: Setup python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.PYODIDE.PYTHON }} + + - name: Install pyodide-build + # PYODIDE_VERSION pins the xbuildenv (Pyodide release), not the + # pyodide-build package — the two version independently. + run: python -m pip install "pyodide-build[resolve]" + + - name: Install Pyodide xbuildenv (cross-build artifacts + patches) + # pyodide-build reads PYODIDE_VERSION from the environment. + run: pyodide xbuildenv install + + - name: Cache emsdk install + id: emsdk-cache + uses: actions/cache@v5 + with: + path: /opt/emsdk + key: emsdk-${{ env.PYODIDE_VERSION }}-${{ env.EMSDK_VERSION }}-${{ runner.os }}-${{ runner.arch }}-0 + + - name: Install emsdk (Pyodide-pinned) + if: steps.emsdk-cache.outputs.cache-hit != 'true' + run: | + set -e + sudo git clone --depth 1 https://github.com/emscripten-core/emsdk.git /opt/emsdk + sudo chown -R "$(id -u):$(id -g)" /opt/emsdk + cd /opt/emsdk + ./emsdk install "${EMSDK_VERSION}" + ./emsdk activate "${EMSDK_VERSION}" + + - name: Cache cross-compiled libsodium + id: sodium-cache + uses: actions/cache@v5 + with: + path: /tmp/libsodium-pyodide + # min0/min1 partitions full vs --enable-minimal builds. + key: libsodium-pyodide-${{ env.PYODIDE_VERSION }}-${{ env.EMSDK_VERSION }}-min${{ matrix.PYODIDE.SODIUM_INSTALL_MINIMAL || '0' }}-${{ hashFiles('.github/bin/build_pyemscripten_libsodium.sh', 'src/libsodium/configure.ac') }}-${{ runner.os }}-${{ runner.arch }}-0 + + - name: Cross-compile libsodium for wasm32-emscripten + if: steps.sodium-cache.outputs.cache-hit != 'true' + env: + SODIUM_SRC: ${{ github.workspace }}/src/libsodium + SODIUM_PATH: /tmp/libsodium-pyodide + SODIUM_INSTALL_MINIMAL: ${{ matrix.PYODIDE.SODIUM_INSTALL_MINIMAL }} + run: | + set -e + # shellcheck disable=SC1091 + source /opt/emsdk/emsdk_env.sh + bash .github/bin/build_pyemscripten_libsodium.sh + + - name: Build pyemscripten wheel + env: + SODIUM_INSTALL: system + run: | + set -e + # shellcheck disable=SC1091 + source /opt/emsdk/emsdk_env.sh + export CFLAGS="-I/tmp/libsodium-pyodide/include ${CFLAGS:-}" + export LDFLAGS="-L/tmp/libsodium-pyodide/lib ${LDFLAGS:-}" + mkdir -p wheelhouse + # pyodide-build 0.34.x ignores --output-dir and emits two wheels + # in dist/; the pyemscripten_*-tagged repack is what ships to PyPI. + pyodide build . + cp dist/pynacl-*-pyemscripten_*_wasm32.whl wheelhouse/ + + - name: Create Pyodide venv and run tests + run: | + set -e + pyodide venv .venv-pyodide + # shellcheck disable=SC1091 + source .venv-pyodide/bin/activate + # pytest-xdist, pytest-cov, and pytest-pyodide are deliberately + # omitted — none works under Emscripten/wasm. + pip install pytest hypothesis + pip install wheelhouse/pynacl-*.whl + python .github/bin/smoketest.py + pytest -p no:cacheprovider tests/ + all-green: runs-on: ubuntu-latest - needs: [linux, macos, windows] + needs: [linux, macos, windows, pyemscripten] if: ${{ always() }} timeout-minutes: 3 steps: From d03cb1338d5d6915e86ee1c460ef74cac3be719b Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:32:45 -0400 Subject: [PATCH 04/19] Update wheel-builder.yml --- .github/workflows/wheel-builder.yml | 89 +++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index e5e3f0cb..75aa2fe3 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -97,8 +97,7 @@ jobs: mv dist/pynacl*.whl tmpwheelhouse/ - run: auditwheel repair --plat ${{ matrix.MANYLINUX.NAME }} tmpwheelhouse/pynacl*.whl -w wheelhouse/ - run: .venv/bin/pip install pynacl --no-index -f wheelhouse/ - - run: | - .venv/bin/python -c "import nacl.signing; key = nacl.signing.SigningKey.generate();signature = key.sign(b'test'); key.verify_key.verify(signature)" + - run: .venv/bin/python .github/bin/smoketest.py - run: mkdir pynacl-wheelhouse - run: mv wheelhouse/pynacl*.whl pynacl-wheelhouse/ @@ -158,8 +157,7 @@ jobs: ARCHFLAGS: '-arch arm64 -arch x86_64' _PYTHON_HOST_PLATFORM: 'macosx-10.9-universal2' - run: venv/bin/pip install -f wheelhouse --no-index pynacl - - run: | - venv/bin/python -c "import nacl.signing; key = nacl.signing.SigningKey.generate();signature = key.sign(b'test'); key.verify_key.verify(signature)" + - run: venv/bin/python .github/bin/smoketest.py - run: mkdir pynacl-wheelhouse - run: mv wheelhouse/pynacl*.whl pynacl-wheelhouse/ @@ -227,11 +225,90 @@ jobs: - name: Test installing the wheel run: pip install -f wheelhouse pynacl --no-index - name: Test the installed wheel - run: | - python -c "import nacl.signing; key = nacl.signing.SigningKey.generate();signature = key.sign(b'test'); key.verify_key.verify(signature)" + run: python .github/bin/smoketest.py - run: mkdir pynacl-wheelhouse - run: move wheelhouse\pynacl*.whl pynacl-wheelhouse\ - uses: actions/upload-artifact@v7 with: name: "pynacl-${{ github.event.inputs.version }}-win-${{ matrix.WINDOWS.ARCH }}-${{ matrix.PYTHON.VERSION }}" path: pynacl-wheelhouse\ + + pyemscripten: + needs: [sdist] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + PYODIDE: + # TODO: switch to stable Pyodide 314.0 when released. Keep these + # pins in lockstep with ci.yml::pyemscripten. + - VERSION: "314.0.0a1" + EMSDK: "5.0.3" + PYTHON: "3.14" + CIBW_BUILD: "cp314-pyodide_wasm32" + CIBW_ENABLE: "pyodide-prerelease" + WHEEL_TAG: "cp314-pyemscripten_2026_0_wasm32" + name: "${{ matrix.PYODIDE.WHEEL_TAG }}" + env: + PYODIDE_VERSION: ${{ matrix.PYODIDE.VERSION }} + EMSDK_VERSION: ${{ matrix.PYODIDE.EMSDK }} + steps: + - name: Get build script and bundled libsodium from repository + # The sdist doesn't carry the cross-compile script or .github/bin, + # but CIBW_BEFORE_ALL_PYODIDE and CIBW_TEST_COMMAND_PYODIDE need them. + uses: actions/checkout@v6.0.2 + with: + ref: ${{ github.event.inputs.version || github.ref }} + persist-credentials: false + sparse-checkout: | + .github/bin/build_pyemscripten_libsodium.sh + .github/bin/smoketest.py + src/libsodium + sparse-checkout-cone-mode: false + + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.PYODIDE.PYTHON }} + + - uses: actions/download-artifact@v8.0.1 + with: + name: pynacl-sdist + + # TODO: switch to released cibuildwheel once pypa/cibuildwheel#2812 + # (which adds cp314-pyodide_wasm32 / pyemscripten_2026_0_wasm32) is + # merged. + - run: pip install "cibuildwheel @ git+https://github.com/agriyakhetarpal/cibuildwheel.git@40ffec086b3068e0e4e2b86e9ef5ff6ff1156d9f" + - run: mkdir wheelhouse + + - name: Cache cross-compiled libsodium + # Key shape matches ci.yml::pyemscripten so both jobs share the cache. + uses: actions/cache@v5 + with: + path: /tmp/libsodium-pyodide + key: libsodium-pyodide-${{ env.PYODIDE_VERSION }}-${{ env.EMSDK_VERSION }}-min${{ matrix.PYODIDE.SODIUM_INSTALL_MINIMAL || '0' }}-${{ hashFiles('.github/bin/build_pyemscripten_libsodium.sh', 'src/libsodium/configure.ac') }}-${{ runner.os }}-${{ runner.arch }}-0 + + - name: Build the wheel + run: cibuildwheel --platform pyodide --output-dir wheelhouse/ pynacl-*.tar.gz + env: + CIBW_BUILD: ${{ matrix.PYODIDE.CIBW_BUILD }} + CIBW_ENABLE: ${{ matrix.PYODIDE.CIBW_ENABLE }} + # BEFORE_ALL (vs BEFORE_BUILD) so libsodium is cross-compiled + # once per host even if the matrix grows multiple cp* targets. + CIBW_BEFORE_ALL_PYODIDE: | + SODIUM_SRC=${{ github.workspace }}/src/libsodium \ + SODIUM_PATH=/tmp/libsodium-pyodide \ + SODIUM_INSTALL_MINIMAL=${{ matrix.PYODIDE.SODIUM_INSTALL_MINIMAL }} \ + bash ${{ github.workspace }}/.github/bin/build_pyemscripten_libsodium.sh + CIBW_ENVIRONMENT_PYODIDE: >- + SODIUM_INSTALL=system + CFLAGS=-I/tmp/libsodium-pyodide/include + LDFLAGS=-L/tmp/libsodium-pyodide/lib + # Full pytest suite runs in ci.yml::pyemscripten — smoketest only here. + CIBW_TEST_COMMAND_PYODIDE: python {project}/.github/bin/smoketest.py + + - run: mkdir pynacl-wheelhouse + - run: mv wheelhouse/pynacl*.whl pynacl-wheelhouse/ + - uses: actions/upload-artifact@v7 + with: + name: "pynacl-${{ github.event.inputs.version }}-${{ matrix.PYODIDE.WHEEL_TAG }}" + path: pynacl-wheelhouse/ From e78c7a1a02896b33a47d32267a672b46018e818c Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:33:18 -0400 Subject: [PATCH 05/19] Create build_pyemscripten_libsodium.sh --- .github/bin/build_pyemscripten_libsodium.sh | 65 +++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/bin/build_pyemscripten_libsodium.sh diff --git a/.github/bin/build_pyemscripten_libsodium.sh b/.github/bin/build_pyemscripten_libsodium.sh new file mode 100644 index 00000000..50d1e7c0 --- /dev/null +++ b/.github/bin/build_pyemscripten_libsodium.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# +# Cross-compile the bundled libsodium for wasm32-emscripten. +# +# Required env vars: +# SODIUM_SRC - absolute path to the bundled libsodium source tree +# SODIUM_PATH - absolute install prefix +# +# Optional env vars (mirror setup.py's build_clib contract): +# LIBSODIUM_MAKE_ARGS - args to `make` (default: -j$(nproc)) +# SODIUM_INSTALL_MINIMAL - if non-empty, pass --enable-minimal +# +# Requires emsdk activated on PATH (emcc, emconfigure, emmake). +# Idempotent so a warm actions/cache hit short-circuits the rebuild. + +set -euo pipefail + +: "${SODIUM_SRC:?SODIUM_SRC must point at the bundled libsodium source tree}" +: "${SODIUM_PATH:?SODIUM_PATH must point at the install prefix}" + +if [ -f "${SODIUM_PATH}/lib/libsodium.a" ]; then + echo "libsodium already built at ${SODIUM_PATH}; skipping rebuild." + exit 0 +fi + +# A fresh checkout on case-insensitive or unusual filesystems can lose +x. +chmod +x "${SODIUM_SRC}/configure" "${SODIUM_SRC}/autogen.sh" 2>/dev/null || true + +mkdir -p "${SODIUM_PATH}" + +NCORES="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2)" +read -r -a MAKE_ARGS <<< "${LIBSODIUM_MAKE_ARGS:--j${NCORES}}" + +CONFIGURE_EXTRA=() +if [ -n "${SODIUM_INSTALL_MINIMAL:-}" ]; then + CONFIGURE_EXTRA+=(--enable-minimal) +fi + +pushd "${SODIUM_SRC}" + +# The first six flags below also appear in setup.py:131-149 — keep both +# sites in sync. The remainder are emscripten-specific and would be +# rejected (or wrong) on a native build. +emconfigure ./configure \ + --host=wasm32-unknown-emscripten \ + --disable-shared \ + --enable-static \ + --with-pic \ + --disable-asm \ + --disable-pie \ + --disable-ssp \ + --disable-dependency-tracking \ + --disable-debug \ + --without-pthreads \ + "${CONFIGURE_EXTRA[@]}" \ + --prefix="${SODIUM_PATH}" + +emmake make "${MAKE_ARGS[@]}" +emmake make install + +popd + +test -f "${SODIUM_PATH}/lib/libsodium.a" +test -f "${SODIUM_PATH}/include/sodium.h" +echo "libsodium cross-compiled successfully to ${SODIUM_PATH}" From 15a5957abbb43ae4c981d3beff22f1ed387c1001 Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:33:34 -0400 Subject: [PATCH 06/19] Create smoketest.py --- .github/bin/smoketest.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/bin/smoketest.py diff --git a/.github/bin/smoketest.py b/.github/bin/smoketest.py new file mode 100644 index 00000000..34890508 --- /dev/null +++ b/.github/bin/smoketest.py @@ -0,0 +1,18 @@ +"""Post-build smoke test for PyNaCl wheels. + +Shared by every wheel-builder job and ``ci.yml::pyemscripten`` so the +exercised code path stays identical across platforms. +""" + +import nacl.signing + + +def main() -> None: + key = nacl.signing.SigningKey.generate() + signature = key.sign(b"smoketest") + key.verify_key.verify(signature) + print("OK: nacl.signing roundtrip succeeded") + + +if __name__ == "__main__": + main() From 47a1e62187b0b754b609cda2171c5f4f4f781044 Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:33:56 -0400 Subject: [PATCH 07/19] Create conftest.py --- tests/conftest.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..0a9a032b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,13 @@ +import sys + +import pytest + + +def pytest_runtest_setup(item: pytest.Item) -> None: + if sys.platform == "emscripten": + for marker in item.iter_markers(name="skip_emscripten"): + pytest.skip( + marker.kwargs.get( + "reason", "Skipped under Emscripten/Pyodide" + ) + ) From 39117100f8de6f71954f77827e0c5608e6a28836 Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:40:31 -0400 Subject: [PATCH 08/19] Update conftest.py --- tests/conftest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0a9a032b..f1e34e87 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,5 @@ def pytest_runtest_setup(item: pytest.Item) -> None: if sys.platform == "emscripten": for marker in item.iter_markers(name="skip_emscripten"): pytest.skip( - marker.kwargs.get( - "reason", "Skipped under Emscripten/Pyodide" - ) + marker.kwargs.get("reason", "Skipped under Emscripten/Pyodide") ) From fe05db71a28f2c4e6a36705e7deee5c8a59c2fd3 Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:40:57 -0400 Subject: [PATCH 09/19] Update wheel-builder.yml --- .github/workflows/wheel-builder.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index 75aa2fe3..c5244638 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -255,7 +255,7 @@ jobs: steps: - name: Get build script and bundled libsodium from repository # The sdist doesn't carry the cross-compile script or .github/bin, - # but CIBW_BEFORE_ALL_PYODIDE and CIBW_TEST_COMMAND_PYODIDE need them. + # but CIBW_BEFORE_BUILD_PYODIDE and CIBW_TEST_COMMAND_PYODIDE need them. uses: actions/checkout@v6.0.2 with: ref: ${{ github.event.inputs.version || github.ref }} @@ -292,9 +292,12 @@ jobs: env: CIBW_BUILD: ${{ matrix.PYODIDE.CIBW_BUILD }} CIBW_ENABLE: ${{ matrix.PYODIDE.CIBW_ENABLE }} - # BEFORE_ALL (vs BEFORE_BUILD) so libsodium is cross-compiled - # once per host even if the matrix grows multiple cp* targets. - CIBW_BEFORE_ALL_PYODIDE: | + # BEFORE_BUILD (not BEFORE_ALL) — emsdk only lives on PATH + # inside cibuildwheel's build env, where BEFORE_BUILD runs. + # BEFORE_ALL would run on the host with no emconfigure available. + # The script's own libsodium.a-exists check makes the per-Python + # repeat cheap if the matrix ever grows. + CIBW_BEFORE_BUILD_PYODIDE: | SODIUM_SRC=${{ github.workspace }}/src/libsodium \ SODIUM_PATH=/tmp/libsodium-pyodide \ SODIUM_INSTALL_MINIMAL=${{ matrix.PYODIDE.SODIUM_INSTALL_MINIMAL }} \ From 0736cc6f3c322e337fcde20d43ff0a260c4a2d68 Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:54:17 -0400 Subject: [PATCH 10/19] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 642284f6..e4df8359 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -225,7 +225,7 @@ jobs: # omitted — none works under Emscripten/wasm. pip install pytest hypothesis pip install wheelhouse/pynacl-*.whl - python .github/bin/smoketest.py + python -c "import nacl.signing; key = nacl.signing.SigningKey.generate(); signature = key.sign(b'test'); key.verify_key.verify(signature)" pytest -p no:cacheprovider tests/ all-green: From 295048a7c555c6eca9de3a45501c9fceda6dc23d Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:54:41 -0400 Subject: [PATCH 11/19] Update wheel-builder.yml --- .github/workflows/wheel-builder.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index c5244638..b7e577d0 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -97,7 +97,8 @@ jobs: mv dist/pynacl*.whl tmpwheelhouse/ - run: auditwheel repair --plat ${{ matrix.MANYLINUX.NAME }} tmpwheelhouse/pynacl*.whl -w wheelhouse/ - run: .venv/bin/pip install pynacl --no-index -f wheelhouse/ - - run: .venv/bin/python .github/bin/smoketest.py + - run: | + .venv/bin/python -c "import nacl.signing; key = nacl.signing.SigningKey.generate(); signature = key.sign(b'test'); key.verify_key.verify(signature)" - run: mkdir pynacl-wheelhouse - run: mv wheelhouse/pynacl*.whl pynacl-wheelhouse/ @@ -157,7 +158,8 @@ jobs: ARCHFLAGS: '-arch arm64 -arch x86_64' _PYTHON_HOST_PLATFORM: 'macosx-10.9-universal2' - run: venv/bin/pip install -f wheelhouse --no-index pynacl - - run: venv/bin/python .github/bin/smoketest.py + - run: | + venv/bin/python -c "import nacl.signing; key = nacl.signing.SigningKey.generate(); signature = key.sign(b'test'); key.verify_key.verify(signature)" - run: mkdir pynacl-wheelhouse - run: mv wheelhouse/pynacl*.whl pynacl-wheelhouse/ @@ -225,7 +227,8 @@ jobs: - name: Test installing the wheel run: pip install -f wheelhouse pynacl --no-index - name: Test the installed wheel - run: python .github/bin/smoketest.py + run: | + python -c "import nacl.signing; key = nacl.signing.SigningKey.generate(); signature = key.sign(b'test'); key.verify_key.verify(signature)" - run: mkdir pynacl-wheelhouse - run: move wheelhouse\pynacl*.whl pynacl-wheelhouse\ - uses: actions/upload-artifact@v7 @@ -262,7 +265,6 @@ jobs: persist-credentials: false sparse-checkout: | .github/bin/build_pyemscripten_libsodium.sh - .github/bin/smoketest.py src/libsodium sparse-checkout-cone-mode: false @@ -307,7 +309,8 @@ jobs: CFLAGS=-I/tmp/libsodium-pyodide/include LDFLAGS=-L/tmp/libsodium-pyodide/lib # Full pytest suite runs in ci.yml::pyemscripten — smoketest only here. - CIBW_TEST_COMMAND_PYODIDE: python {project}/.github/bin/smoketest.py + CIBW_TEST_COMMAND_PYODIDE: | + python -c "import nacl.signing; key = nacl.signing.SigningKey.generate(); signature = key.sign(b'test'); key.verify_key.verify(signature)" - run: mkdir pynacl-wheelhouse - run: mv wheelhouse/pynacl*.whl pynacl-wheelhouse/ From aa87d03fac4b035dbdb22a801ac78af785080bbf Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 5 May 2026 19:54:55 -0400 Subject: [PATCH 12/19] Delete .github/bin/smoketest.py --- .github/bin/smoketest.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/bin/smoketest.py diff --git a/.github/bin/smoketest.py b/.github/bin/smoketest.py deleted file mode 100644 index 34890508..00000000 --- a/.github/bin/smoketest.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Post-build smoke test for PyNaCl wheels. - -Shared by every wheel-builder job and ``ci.yml::pyemscripten`` so the -exercised code path stays identical across platforms. -""" - -import nacl.signing - - -def main() -> None: - key = nacl.signing.SigningKey.generate() - signature = key.sign(b"smoketest") - key.verify_key.verify(signature) - print("OK: nacl.signing roundtrip succeeded") - - -if __name__ == "__main__": - main() From 761c82a4d54650807739f9c8794e7d4f26cb20c5 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Sat, 9 May 2026 20:59:26 -0400 Subject: [PATCH 13/19] Update wheel-builder.yml --- .github/workflows/wheel-builder.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index b7e577d0..2d2460ee 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -276,10 +276,10 @@ jobs: with: name: pynacl-sdist - # TODO: switch to released cibuildwheel once pypa/cibuildwheel#2812 - # (which adds cp314-pyodide_wasm32 / pyemscripten_2026_0_wasm32) is - # merged. - - run: pip install "cibuildwheel @ git+https://github.com/agriyakhetarpal/cibuildwheel.git@40ffec086b3068e0e4e2b86e9ef5ff6ff1156d9f" + # TODO: switch to released cibuildwheel once a release containing + # pypa/cibuildwheel#2812 (cp314-pyodide_wasm32 / pyemscripten_2026_0_wasm32) + # is published. + - run: pip install "cibuildwheel @ git+https://github.com/pypa/cibuildwheel.git@main" - run: mkdir wheelhouse - name: Cache cross-compiled libsodium From 9a052c7fb46f2753af5d175493252678a62f71c2 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Sun, 10 May 2026 12:01:34 -0400 Subject: [PATCH 14/19] Update to use pyodide xbuildenv install-emscripten --- .github/workflows/ci.yml | 56 ++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4df8359..9202db6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,16 +136,19 @@ jobs: strategy: fail-fast: false matrix: + # Keep in lockstep with wheel-builder.yml::pyemscripten. PYODIDE: - # TODO: switch to stable Pyodide 314.0 when released. Keep these - # pins in lockstep with wheel-builder.yml::pyemscripten. + # TODO: switch to stable Pyodide 314.0 when released. - VERSION: "314.0.0a1" EMSDK: "5.0.3" PYTHON: "3.14" - name: "pyemscripten (Pyodide ${{ matrix.PYODIDE.VERSION }})" + # PEP 783 platform tag emitted by pyodide-build for this xbuildenv. + WHEEL_TAG: "cp314-pyemscripten_2026_0_wasm32" + name: "${{ matrix.PYODIDE.WHEEL_TAG }} (Pyodide ${{ matrix.PYODIDE.VERSION }})" env: PYODIDE_VERSION: ${{ matrix.PYODIDE.VERSION }} EMSDK_VERSION: ${{ matrix.PYODIDE.EMSDK }} + WHEEL_TAG: ${{ matrix.PYODIDE.WHEEL_TAG }} steps: - uses: actions/checkout@v6.0.2 @@ -154,31 +157,16 @@ jobs: with: python-version: ${{ matrix.PYODIDE.PYTHON }} - - name: Install pyodide-build - # PYODIDE_VERSION pins the xbuildenv (Pyodide release), not the - # pyodide-build package — the two version independently. - run: python -m pip install "pyodide-build[resolve]" + - name: Install pyodide-build (from main) + # TODO: No released pyodide-build yet supports Python 3.14 / pyemscripten_2026_0. Fix this when pyodide-build has a stable release with support. + run: python -m pip install "pyodide-build[resolve] @ git+https://github.com/pyodide/pyodide-build.git@main" - name: Install Pyodide xbuildenv (cross-build artifacts + patches) - # pyodide-build reads PYODIDE_VERSION from the environment. run: pyodide xbuildenv install - - name: Cache emsdk install - id: emsdk-cache - uses: actions/cache@v5 - with: - path: /opt/emsdk - key: emsdk-${{ env.PYODIDE_VERSION }}-${{ env.EMSDK_VERSION }}-${{ runner.os }}-${{ runner.arch }}-0 - - - name: Install emsdk (Pyodide-pinned) - if: steps.emsdk-cache.outputs.cache-hit != 'true' - run: | - set -e - sudo git clone --depth 1 https://github.com/emscripten-core/emsdk.git /opt/emsdk - sudo chown -R "$(id -u):$(id -g)" /opt/emsdk - cd /opt/emsdk - ./emsdk install "${EMSDK_VERSION}" - ./emsdk activate "${EMSDK_VERSION}" + - name: Install Pyodide-pinned Emscripten SDK + # Pyodide patches emscripten; vanilla emsdk produces broken wheels. + run: pyodide xbuildenv install-emscripten - name: Cache cross-compiled libsodium id: sodium-cache @@ -197,7 +185,7 @@ jobs: run: | set -e # shellcheck disable=SC1091 - source /opt/emsdk/emsdk_env.sh + source "$(pyodide config get emsdk_dir)/emsdk_env.sh" bash .github/bin/build_pyemscripten_libsodium.sh - name: Build pyemscripten wheel @@ -206,27 +194,27 @@ jobs: run: | set -e # shellcheck disable=SC1091 - source /opt/emsdk/emsdk_env.sh + source "$(pyodide config get emsdk_dir)/emsdk_env.sh" export CFLAGS="-I/tmp/libsodium-pyodide/include ${CFLAGS:-}" export LDFLAGS="-L/tmp/libsodium-pyodide/lib ${LDFLAGS:-}" mkdir -p wheelhouse - # pyodide-build 0.34.x ignores --output-dir and emits two wheels - # in dist/; the pyemscripten_*-tagged repack is what ships to PyPI. pyodide build . - cp dist/pynacl-*-pyemscripten_*_wasm32.whl wheelhouse/ + # Match WHEEL_TAG exactly: catches silent xbuildenv ABI drift. + cp "dist/pynacl-${WHEEL_TAG}.whl" wheelhouse/ 2>/dev/null \ + || cp dist/pynacl-*-${WHEEL_TAG}.whl wheelhouse/ - - name: Create Pyodide venv and run tests + - name: Run tests run: | set -e pyodide venv .venv-pyodide # shellcheck disable=SC1091 source .venv-pyodide/bin/activate - # pytest-xdist, pytest-cov, and pytest-pyodide are deliberately - # omitted — none works under Emscripten/wasm. - pip install pytest hypothesis + # pytest-xdist omitted: no multiprocessing under Emscripten. + pip install pytest pytest-cov hypothesis pip install wheelhouse/pynacl-*.whl python -c "import nacl.signing; key = nacl.signing.SigningKey.generate(); signature = key.sign(b'test'); key.verify_key.verify(signature)" - pytest -p no:cacheprovider tests/ + pytest -p no:cacheprovider --cov=nacl --cov=tests tests/ + - uses: ./.github/actions/upload-coverage all-green: runs-on: ubuntu-latest From bb0d0f47bac05c3bcc603179ab3b482d588affcb Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Sun, 10 May 2026 12:20:32 -0400 Subject: [PATCH 15/19] wheel-builder: pin pyemscripten upload glob to WHEEL_TAG Mirror ci.yml::pyemscripten's loud-fail behavior on ABI drift. The previous `wheelhouse/pynacl*.whl` glob would silently ship whatever cibuildwheel produced; if pyodide-build/cibuildwheel ever emits a different platform tag (e.g. an ABI bump from 2026_0), the upload artifact name would still claim WHEEL_TAG while the wheel inside is something else. Anchoring the mv to WHEEL_TAG makes that case fail at build time instead of leaking through to PyPI. --- .github/workflows/wheel-builder.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index 2d2460ee..ccc44575 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -255,6 +255,7 @@ jobs: env: PYODIDE_VERSION: ${{ matrix.PYODIDE.VERSION }} EMSDK_VERSION: ${{ matrix.PYODIDE.EMSDK }} + WHEEL_TAG: ${{ matrix.PYODIDE.WHEEL_TAG }} steps: - name: Get build script and bundled libsodium from repository # The sdist doesn't carry the cross-compile script or .github/bin, @@ -313,7 +314,8 @@ jobs: python -c "import nacl.signing; key = nacl.signing.SigningKey.generate(); signature = key.sign(b'test'); key.verify_key.verify(signature)" - run: mkdir pynacl-wheelhouse - - run: mv wheelhouse/pynacl*.whl pynacl-wheelhouse/ + # Match WHEEL_TAG exactly: catches silent xbuildenv ABI drift. + - run: mv wheelhouse/pynacl-*-${WHEEL_TAG}.whl pynacl-wheelhouse/ - uses: actions/upload-artifact@v7 with: name: "pynacl-${{ github.event.inputs.version }}-${{ matrix.PYODIDE.WHEEL_TAG }}" From 0b061ae3d7e8892ea5d3f7318032943f3805dcbd Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Fri, 15 May 2026 09:04:15 -0400 Subject: [PATCH 16/19] Pin cibuildwheel to 4.0.0rc1 (first release with pyemscripten support) --- .github/workflows/wheel-builder.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index ccc44575..ec823b25 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -277,10 +277,10 @@ jobs: with: name: pynacl-sdist - # TODO: switch to released cibuildwheel once a release containing - # pypa/cibuildwheel#2812 (cp314-pyodide_wasm32 / pyemscripten_2026_0_wasm32) - # is published. - - run: pip install "cibuildwheel @ git+https://github.com/pypa/cibuildwheel.git@main" + # 4.0.0rc1 is the first release carrying pypa/cibuildwheel#2812 + # (cp314-pyodide_wasm32 / pyemscripten_2026_0_wasm32). Bump to the + # stable 4.0.0 tag once it lands. + - run: pip install "cibuildwheel==4.0.0rc1" - run: mkdir wheelhouse - name: Cache cross-compiled libsodium From 9e151ebcb0c8788b84a8c843076e0c279f571dd4 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Fri, 15 May 2026 18:26:45 -0400 Subject: [PATCH 17/19] Bump Pyodide xbuildenv to 314.0.0a2 --- .github/workflows/ci.yml | 2 +- .github/workflows/wheel-builder.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9202db6f..c9e770c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,7 +139,7 @@ jobs: # Keep in lockstep with wheel-builder.yml::pyemscripten. PYODIDE: # TODO: switch to stable Pyodide 314.0 when released. - - VERSION: "314.0.0a1" + - VERSION: "314.0.0a2" EMSDK: "5.0.3" PYTHON: "3.14" # PEP 783 platform tag emitted by pyodide-build for this xbuildenv. diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index ec823b25..67378048 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -245,7 +245,7 @@ jobs: PYODIDE: # TODO: switch to stable Pyodide 314.0 when released. Keep these # pins in lockstep with ci.yml::pyemscripten. - - VERSION: "314.0.0a1" + - VERSION: "314.0.0a2" EMSDK: "5.0.3" PYTHON: "3.14" CIBW_BUILD: "cp314-pyodide_wasm32" From d23b05399dcd98182a29175a3eb09e094737ab31 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Fri, 15 May 2026 18:35:16 -0400 Subject: [PATCH 18/19] Pin pyodide-build==0.34.3 and pass PYODIDE_VERSION explicitly to xbuildenv install --- .github/workflows/ci.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9e770c1..e7935638 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -157,12 +157,16 @@ jobs: with: python-version: ${{ matrix.PYODIDE.PYTHON }} - - name: Install pyodide-build (from main) - # TODO: No released pyodide-build yet supports Python 3.14 / pyemscripten_2026_0. Fix this when pyodide-build has a stable release with support. - run: python -m pip install "pyodide-build[resolve] @ git+https://github.com/pyodide/pyodide-build.git@main" + - name: Install pyodide-build + # 0.34.1+ ships the PEP 783 `pyemscripten` platform name and the + # `xbuildenv install-emscripten` subcommand this job relies on. + run: python -m pip install "pyodide-build[resolve]==0.34.3" - name: Install Pyodide xbuildenv (cross-build artifacts + patches) - run: pyodide xbuildenv install + # Explicit version arg — `pyodide xbuildenv install` does not read + # PYODIDE_VERSION; without this the matrix pin would not be + # enforced (selection falls back to latest host-Python-compatible). + run: pyodide xbuildenv install "${PYODIDE_VERSION}" - name: Install Pyodide-pinned Emscripten SDK # Pyodide patches emscripten; vanilla emsdk produces broken wheels. From 11a3891f4e5b88726a3f1b7cc1238328ddc55316 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Sat, 16 May 2026 01:34:18 -0400 Subject: [PATCH 19/19] Fix mypy bytearray-vs-bytes error in test_aead --- tests/test_aead.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_aead.py b/tests/test_aead.py index c1b1a902..98a9902b 100644 --- a/tests/test_aead.py +++ b/tests/test_aead.py @@ -159,7 +159,7 @@ def test_variants_roundtrip_aad( with pytest.raises(exc.CryptoError): ct1 = bytearray(ct) ct1[0] = ct1[0] ^ 0xFF - c.decrypt(ct1, aad, unonce, ukey) + c.decrypt(bytes(ct1), aad, unonce, ukey) @given( @@ -193,7 +193,7 @@ def test_variants_roundtrip_no_aad( with pytest.raises(exc.CryptoError): ct1 = bytearray(ct) ct1[0] = ct1[0] ^ 0xFF - c.decrypt(ct1, aad, unonce, ukey) + c.decrypt(bytes(ct1), aad, unonce, ukey) @pytest.mark.parametrize(