A CMake toolchain for cross-compiling C/C++ to wasm32-wasi*, built on releases
from WebAssembly/wasi-sdk.
It is split into three pieces so a project can take only what it needs:
| File | Purpose |
|---|---|
wasi-sdk.toolchain.cmake |
Describes the toolchain. No downloads, no targets, no directory state. |
wasi-sdk-bootstrap.toolchain.cmake |
The above, plus downloading a pinned SDK first. |
cmake/WasiSdkExtras.cmake |
wasi_sdk_add_extras(), which the toolchain defines for you. |
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=/path/to/wasi-sdk.toolchain.cmake \
-DWASI_SDK_ROOT=/path/to/wasi-sdk-33.0-x86_64-linuxWASI_SDK_ROOT may also come from the WASI_SDK_PATH environment variable.
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=/path/to/wasi-sdk-bootstrap.toolchain.cmake \
-DWASI_SDK_VERSION=33The archive is verified against a checksum pinned in
cmake/WasiSdkChecksums.cmake and unpacked into a shared cache
($XDG_CACHE_HOME/wasi-sdk, ~/.cache/wasi-sdk, or %LOCALAPPDATA%\wasi-sdk;
override with WASI_SDK_CACHE_DIR). Subsequent configures reuse it.
Prefer the non-bootstrap toolchain when something else already manages the SDK, such as a package manager or a container image: a configure step that reaches the network cannot run offline or hermetically.
To pre-populate the cache without configuring a project:
cmake -DVERSION=33 -P cmake/WasiSdkAcquire.cmakeinclude(FetchContent)
FetchContent_Declare(wasi_sdk_toolchain
GIT_REPOSITORY https://github.com/rioam2/wasi-sdk-toolchain.git
GIT_TAG <commit>)
FetchContent_MakeAvailable(wasi_sdk_toolchain)There is no CMakeLists.txt at the repository root, so this populates the
sources without adding anything to your build.
All options are plain CMake variables, settable with -D or by a wrapper
toolchain that includes this one. Each is forwarded into try_compile, so
compiler probes and check_<lang>_source_compiles() see the same flags as the
real build.
| Variable | Default | Meaning |
|---|---|---|
WASI_SDK_ROOT |
(required) | Extracted wasi-sdk release. |
WASI_SDK_TARGET_TRIPLE |
wasm32-wasip1 |
Any triple the SDK's sysroot provides. |
WASI_SDK_EMULATED_FEATURES |
(none) | Any of signal, mman, process-clocks, getpid. Comma or semicolon separated. |
WASI_SDK_EXCEPTIONS |
off |
off, wasm, or ignore. |
WASI_SDK_EXCEPTION_ENCODING |
standard |
standard or legacy. Only applies when exception opcodes are emitted. |
WASI_SDK_SETJMP |
OFF |
Enable setjmp/longjmp via the SJLJ lowering. |
WASI_SDK_CXX_STDLIB |
libc++ |
Passed to -stdlib=; default leaves it to the compiler. |
WASI_SDK_LIBC_STUBS |
OFF |
Add stub headers for libc functionality wasi-libc lacks to the global include path. |
WASI_SDK_LIBC_STUBS_DIR |
(shipped copy) | Directory containing the stub headers; defaults to extras/libc-stubs next to this file. |
WASI_SDK_CROSSCOMPILING_EMULATOR |
(none) | Sets CMAKE_CROSSCOMPILING_EMULATOR so ctest can run the output. |
WASI_SDK_EXCEPTIONS has to be explicit because clang selects the sysroot's
include and library directories from it:
offuses-fno-exceptionsand thenoehmultilib.throwandtrybecome compile errors. This is the wasi-sdk default configuration.wasmuses-fwasm-exceptionsand theehmultilib, and linkslibunwind. Exceptions work.ignoreuses-fignore-exceptions, which keepstry/catchcompiling for unported code but leaves the exception ABI undefined. Linkwasi::abort-exceptionsfrom the extras to supply symbols that abort.
Building without any of these leaves libc++ emitting calls into an exception ABI that the selected multilib does not provide, which fails at link time.
WASI_SDK_EXCEPTION_ENCODING exists because clang still defaults to the legacy
exception opcode encoding, while runtimes have moved on โ wasmtime removed
--wasm legacy-exceptions in version 47. Run such modules with
wasmtime run -W exceptions=y.
WASI_SDK_LIBC_STUBS=ON appends the stub header directory to
CMAKE_C_STANDARD_INCLUDE_DIRECTORIES and CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES,
making the declarations visible to every translation unit in the build. This is
intended for builds of third-party dependencies that reference POSIX APIs
(e.g. dup, flock, msync) that wasi-libc does not declare and that cannot
easily be patched to link wasi::libc-stubs.
Because these headers shadow the real wasi-libc headers of the same name, the
option is off by default. For code you control, prefer linking wasi::libc-stubs
explicitly so the shadow is confined to those targets. Everything the stubs
declare aborts at runtime; see extras/libc-stubs/README.md.
Set WASI_SDK_LIBC_STUBS_DIR to point at a different directory when you want
to supply your own stub headers instead of the copy shipped with this toolchain.
Optimisation levels, LTO, --gc-sections, initial memory and stack size are
project policy rather than properties of the target, so they are left to the
consuming project. For a release build you probably want something like:
add_compile_options($<$<CONFIG:Release>:-O3>)
add_link_options($<$<CONFIG:Release>:-Wl,--gc-sections,--strip-debug>)Either toolchain file defines wasi_sdk_add_extras(), so a project can declare
the optional helper targets without locating anything:
wasi_sdk_add_extras()
target_link_libraries(my_module PRIVATE wasi::reactor)| Target | Purpose |
|---|---|
wasi::reactor |
Reactor-style module: exports _start/__wasm_call_ctors and adds -nostartfiles -Wl,--no-entry. |
wasi::abort-exceptions |
Defines __cxa_throw/__cxa_allocate_exception so they abort. For WASI_SDK_EXCEPTIONS=ignore. |
wasi::libc-stubs |
Declarations and stub definitions for libc functionality wasi-libc lacks. |
The call is what declares the targets: the toolchain only defines the function,
because targets declared by a toolchain cannot be exported and would reappear in
every nested project(). Calling it more than once is harmless, and the targets
are EXCLUDE_FROM_ALL, so unused ones are not built. They are libraries rather
than force-included headers so their definitions appear once per target instead
of once per translation unit.
The sources come from extras/ next to the toolchain file; set
WASI_SDK_EXTRAS_DIR, or pass SOURCE_DIR/BINARY_DIR to the function, to
build a different copy or place the build tree elsewhere.
wasi::libc-stubs headers shadow real wasi-libc ones, so they reach only the
targets that link it. Everything it declares fails at runtime; see
extras/libc-stubs/README.md.
GITHUB_TOKEN=$(gh auth token) cmake -P tools/update-checksums.cmakeThis regenerates cmake/WasiSdkChecksums.cmake from the digests GitHub
publishes for each release asset. Releases before wasi-sdk-26 have no digests
and are skipped; pass WASI_SDK_SHA256 to use one anyway.
cmake -S tests -B build -G Ninja
ctest --test-dir build --output-on-failurectest -L unit covers the CMake logic without a network or a compiler.
ctest -L integration downloads the SDK and compiles real WebAssembly, and also
executes it when wasmtime is on PATH. Pass
-DWASI_SDK_TESTS_INTEGRATION=OFF to skip the latter, or
-DWASI_SDK_TESTS_VERSION=<n> to target a different release.