The `build-pyodide` CI job has been failing consistently because
`_copy_bins()` cannot find the z3 executable after a successful cmake
build.
**Root cause**: `pyproject.toml`'s `[tool.pyodide.build]` sets `ldflags
= "... -sSIDE_MODULE=1"`. pyodide-build injects this into the
environment `LDFLAGS`, which cmake propagates to
`CMAKE_EXE_LINKER_FLAGS`. The z3 shell target is therefore linked as a
WASM side-module — emscripten emits a `.wasm` file whose name doesn't
match any of the expected paths (`z3.wasm`, `z3.js.wasm`, `z3`):
```
error: Could not find any executable in build directory. Tried:
- .../build/z3.wasm
- .../build/z3.js.wasm
- .../build/z3
```
**Fix** — `src/api/python/setup.py`:
- **`_configure_z3()`**: pass `Z3_BUILD_EXECUTABLE=FALSE` to cmake when
`IS_PYODIDE`, so the shell target is never built
- **`_copy_bins()`**: guard BINS_DIR creation and executable copy behind
`if not IS_PYODIDE`
- **`setup()`**: set `data_files=[]` for Pyodide builds (no executable
to package)
The z3 CLI isn't usable in a Pyodide environment anyway — Python users
only need `libz3.so` via ctypes, which continues to be built and
packaged correctly. Non-Pyodide builds (Linux/macOS/Windows) are
unaffected.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
`EXECUTABLE_FILE_FALLBACKS` was referenced on all platforms in
`_copy_bins()` but only defined in the `emscripten` and `else` (Linux)
branches — causing a `NameError` crash on Windows and macOS wheel
builds.
## Change
Add `EXECUTABLE_FILE_FALLBACKS = []` to the missing platform branches in
`src/api/python/setup.py`:
```python
if BUILD_PLATFORM in ('sequoia','darwin', 'osx'):
LIBRARY_FILE = "libz3.dylib"
EXECUTABLE_FILE = "z3"
EXECUTABLE_FILE_FALLBACKS = [] # added
elif BUILD_PLATFORM in ('win32', 'cygwin', 'win'):
LIBRARY_FILE = "libz3.dll"
EXECUTABLE_FILE = "z3.exe"
EXECUTABLE_FILE_FALLBACKS = [] # added
elif BUILD_PLATFORM in ('emscripten',):
...
EXECUTABLE_FILE_FALLBACKS = ["z3.js.wasm", "z3"]
else:
...
EXECUTABLE_FILE_FALLBACKS = []
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Nikolaj Bjorner <nikolaj@cs.stanford.edu>
`build-pyodide` failed because Python packaging assumed the wasm shell
artifact is always emitted as `build/z3.wasm`. In the failing run, the
expected file was absent, causing wheel build to abort during binary
copy.
- **Root cause handling in `setup.py`**
- In the emscripten path, add executable fallbacks (`z3.js.wasm`, `z3`)
alongside the canonical `z3.wasm`.
- During packaging, probe known output names in order and copy the first
match.
- **Stable wheel layout**
- Keep packaged artifact name stable as `bin/z3.wasm` regardless of
which build output variant exists.
- **Failure diagnostics**
- If no candidate exists, raise a `FileNotFoundError` listing all
attempted paths to make CI failures immediately actionable.
```python
executable_names = (EXECUTABLE_FILE,) + tuple(EXECUTABLE_FILE_FALLBACKS)
for executable_name in executable_names:
candidate = os.path.join(BUILD_DIR, executable_name)
if os.path.exists(candidate):
shutil.copy(candidate, os.path.join(BINS_DIR, EXECUTABLE_FILE))
break
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
The `build` job in the Pyodide workflow was failing at wheel smoke-test
import time because `libz3.so` was not produced as a proper wasm side
module (`need the dylink section to be first`, missing exported
symbols). This PR restores the required Pyodide linker mode.
- **Root cause**
- Recent Pyodide linker flag changes enabled wasm EH/longjmp but omitted
`-sSIDE_MODULE=1`, so the generated `libz3.so` was not loadable by
Pyodide’s dynamic loader.
- **Changes**
- **`src/api/python/pyproject.toml`**
- Added `-sSIDE_MODULE=1` to `[tool.pyodide.build].ldflags`.
- **`src/api/python/setup.py`**
- Added `-sSIDE_MODULE=1` to the Pyodide `LDFLAGS` path to keep direct
`setup.py`-driven builds aligned with `pyproject.toml` behavior.
- **Flag delta (core fix)**
```toml
# src/api/python/pyproject.toml
[tool.pyodide.build]
ldflags = "-fwasm-exceptions -sSUPPORT_LONGJMP=wasm -sWASM_BIGINT -sSIDE_MODULE=1"
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
## Summary
z3 already builds under Pyodide (there is a `pyodide.yml` workflow and
an
`IS_PYODIDE` path in `setup.py`), but that path uses `pyodide build` and
produces
a wheel tagged `emscripten_<pyodide-version>_wasm32`, which is pinned to
a single
Pyodide release and rejected by PyPI — so today it's only usable as a CI
artifact.
[PEP 783](https://peps.python.org/pep-0783/) introduced the portable
`pyemscripten_<date>_wasm32` platform tag that **PyPI accepts** and
`micropip` can
install at runtime. This makes `z3-solver` build that wheel via
`cibuildwheel --platform pyodide`.
## Changes
- **`setup.py`** — for the emscripten target, use the wheel platform tag
that
pyodide-build provides verbatim via `_PYTHON_HOST_PLATFORM` (e.g.
`pyemscripten_2026_0_wasm32`) instead of reconstructing an
`emscripten_*` tag.
Falls back to the previous behaviour when that env var is absent.
- **`setup.py` / `CMakeLists.txt` / `pyproject.toml`** — switch the
Pyodide build
from JS-based exceptions (`-fexceptions`,
`-sDISABLE_EXCEPTION_CATCHING=0`) to
**native wasm exception handling** (`-fwasm-exceptions
-sSUPPORT_LONGJMP=wasm`),
matching the ABI of the Pyodide 314 / emscripten 5 main module. With the
old
flags `libz3.so` imports `invoke_*` trampolines the runtime no longer
provides,
so the wheel builds but the first `Z3_mk_config` call fails at runtime
with
`Dynamic linking error: cannot resolve symbol invoke_vi`.
- **`pyproject.toml`** — add `[tool.cibuildwheel]` /
`[tool.pyodide.build]` so
`cibuildwheel --platform pyodide` builds and tests the wheel (`cp314`).
- **`.github/workflows/pyodide-pypi.yml`** (new) — build with
cibuildwheel and
publish to PyPI (trusted publishing) on tags. Existing `pyodide.yml`
unchanged.
## Verification
Built with `cibuildwheel 4.1.0` / `pyodide-build 0.35.0` / `emscripten
5.0.3`,
CPython 3.14 / Pyodide 314:
- Produces `z3_solver-4.17.0.0-py3-none-pyemscripten_2026_0_wasm32.whl`.
- `z3test.py` passes in the Pyodide runtime (node + wasm32).
- Installed via `micropip` and solves SMT problems both under node and
in a
browser (`sat` with a model, `unsat` for a contradiction).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
🕵️♂️ Reviewed by [Alcides Fonseca](https://wiki.alcidesfonseca.com)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Initial plan
* Add proper pyproject.toml metadata for dist-info creation
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
* Clean up setup.py and add comprehensive test for dist-info fix
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
* Fix build errors in setup.py and pyproject.toml
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
* Fix ModuleNotFoundError by removing dynamic version loading from pyproject.toml
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
* Remove hardcoded version from pyproject.toml, use dynamic version from setup.py
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
Update setup.py so that we copy LICENSE.TXT to src/api/python before
creating the sdist. Any wheels built from this sdist will now
contain the LICENSE.txt file.
Fixes#7604
Rather than pulling `cmake` from PyPI unconditionally, add it to build
dependencies only if the system `cmake` executable cannot be found.
This eliminates an unnecessary dependency on systems featuring CMake,
and ensures that whenever possible, a downstream patched CMake version
is used that is more compatible with the system in question.
The scoped_timer uses a std::therad. Spawning this thread fails in cases of WASM.
Instead of adapting builds and using async features at the level of WASM and the client, we expose a specialized version of z3 that doesn't use threads at all, neither for solvers nor for timers.
The tradeoff is that the periodic poll that checks for timeout directly queries the global clock each time.
We characterize it as based on polling.
- add some logic to setup.py to handle cross platform tagging correctly
this adds a dependency on setuptools>=70
- rearrange the nightly CI to use these new builds correctly