PHP extension dependencies
Custom PHP.wasm extensions can only link WebAssembly code built with the same
Emscripten toolchain and JSPI ABI as the PHP runtime. Native host libraries from
/usr/lib, Homebrew, apt, or npm packages cannot be linked into the final
.so.
Build dependencies as static WebAssembly archives and pass their headers and
archives to @php-wasm/compile-extension.
Playground-built dependencies
Some libraries already have recipes in packages/php-wasm/compile. Build the
JSPI target and pass the mounted path inside the helper container:
make -C packages/php-wasm/compile libz_jspi
npx @php-wasm/compile-extension \
--source ./zlib-probe \
--name zlib_probe \
--php-versions 8.4 \
--extra-cflags "-I/php-wasm-compile/libz/jspi/dist/root/lib/include" \
--extra-ldflags "/php-wasm-compile/libz/jspi/dist/root/lib/lib/libz.a"
Use the jspi archive for custom extension builds.
Vendored dependencies
If the dependency is not built by Playground, vendor the source under your
extension directory and build it with Emscripten before the extension is linked.
Paths from --source are available under /build inside the helper container:
npx @php-wasm/compile-extension \
--source ./external-lib-probe \
--name external_lib_probe \
--php-versions 8.4 \
--extra-cflags "-I/build/vendor/string-score/install/include" \
--extra-ldflags "/build/vendor/string-score/install/lib/libstring_score.a"
--extra-cflags is visible during ./configure. --extra-ldflags is applied
to the final side-module link so dependency archives do not break Autoconf's
compiler smoke tests.
Prebuilt static archives
Use --extra-ldflags to link prebuilt static archives such as a Rust
staticlib. The helper detects .a entries in --extra-ldflags and
force-links them into the final side module with --whole-archive.
npx @php-wasm/compile-extension \
--source ./my-rust-extension \
--name my_rust_extension \
--php-versions 8.4 \
--extra-cflags "-I/build/include" \
--extra-ldflags "/build/target/wasm32-unknown-emscripten/release/libmy_rust_extension.a"
The extension still needs a small phpize wrapper. A common Rust pattern is:
config.m4declares the PHP extension and builds a tiny C shim withPHP_NEW_EXTENSION._shim.cdefines the PHP module entry and calls exported Rust functions over C ABI.--extra-ldflagspoints at the Ruststaticlibarchive under/build.
Do not use PHP_ADD_LIBRARY_WITH_PATH for a sibling .a archive in
config.m4. PHP's libtool setup can look for a matching .so, fail to link
the archive into the side module, and still leave a build artifact behind. Pass
static archives through --extra-ldflags instead.
CMake dependencies
Build CMake dependencies as static archives with Emscripten and store the install tree under the extension source directory:
source /root/emsdk/emsdk_env.sh
emcmake cmake \
-S vendor/libfoo \
-B vendor/libfoo/build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$PWD/vendor/libfoo/install" \
-DBUILD_SHARED_LIBS=OFF
emmake cmake --build vendor/libfoo/build --target install
npx @php-wasm/compile-extension \
--source . \
--name my_extension \
--php-versions 8.4 \
--extra-cflags "-I/build/vendor/libfoo/install/include" \
--extra-ldflags "/build/vendor/libfoo/install/lib/libfoo.a"
Makefile dependencies
Force Makefile dependencies to use Emscripten tools:
source /root/emsdk/emsdk_env.sh
emmake make -C vendor/libfoo \
CC=emcc \
CXX=em++ \
AR=emar \
RANLIB=emranlib \
PREFIX="$PWD/vendor/libfoo/install" \
install
npx @php-wasm/compile-extension \
--source . \
--name my_extension \
--php-versions 8.4 \
--extra-cflags "-I/build/vendor/libfoo/install/include" \
--extra-ldflags "/build/vendor/libfoo/install/lib/libfoo.a"
The final PHP extension still needs a phpize build recipe. If the extension
itself is CMake-only or Makefile-only, add a thin config.m4 wrapper that
builds the PHP extension and treats the CMake or Make output as dependency
code.
Rust build systems
The helper image includes a host php CLI because ext-php-rs and similar
build systems often shell out to php for version detection. The host CLI is
only for build scripts. The compiled extension must still link against the
PHP.wasm headers and side-module ABI.
Rust extensions that use bindgen need the same target, sysroot, and Zend
defines as PHP.wasm. The helper exports BINDGEN_EXTRA_CLANG_ARGS with:
--target=wasm32-unknown-emscripten \
--sysroot=$EMSDK_SYSROOT \
-DZEND_ENABLE_ZVAL_LONG64 \
-D__x86_64__
If you build the Rust archive outside the helper container, set the same value
before running cargo.
Rust cc-rs build scripts also need position-independent objects for side
module linking. The helper exports target-specific compiler variables including
CFLAGS_wasm32_unknown_emscripten=-fPIC and
CXXFLAGS_wasm32_unknown_emscripten=-fPIC. Without -fPIC, the final link can
fail with an error like:
R_WASM_MEMORY_ADDR_SLEB cannot be used against symbol ...; recompile with -fPIC
Rust's precompiled std for wasm32-unknown-emscripten is built with
unwinding support. That imports a __cpp_exception WebAssembly tag that
PHP.wasm does not export. Build Rust static libraries with panic=abort and a
nightly rebuilt standard library:
RUSTFLAGS="-C panic=abort" cargo +nightly build \
--release \
--target wasm32-unknown-emscripten \
-Zbuild-std=std,panic_abort
Link the resulting lib*.a with --extra-ldflags.
PHP version support for ext-php-rs
ext-php-rs 0.15 depends on PHP 8 Zend APIs and does not compile against
PHP 7.4 headers. Rust extensions built on top of ext-php-rs 0.15 should
restrict the PHP matrix to 8.0 through 8.5:
npx @php-wasm/compile-extension \
--source ./my-rust-extension \
--name my_rust_extension \
--php-versions 8.0,8.1,8.2,8.3,8.4,8.5 \
--extra-ldflags "/build/target/wasm32-unknown-emscripten/release/libmy_rust_extension.a"
The helper itself still supports PHP 7.4. The limitation is in the Rust
binding layer, not in the WebAssembly side-module ABI. Hand-written Rust
extensions that bind Zend directly through bindgen can target PHP 7.4 if
the C ABI shim does the same.
ext-php-rs also generates bindings for one PHP version at a time. When you
build the same Rust crate for several PHP versions, rebuild the staticlib for
each PHP version with that version's Zend API number visible to the build
script, and link the matching archive into each --php-versions lane.
Troubleshooting
configure: error: ... not found
The dependency headers or libraries are not visible inside the container. Use
paths under /build for files copied from --source, or
/php-wasm-compile/<dependency>/<mode>/dist/root/lib for Playground-built
dependencies.
undefined symbol when loading the extension
The extension references a function that is not exported by the PHP main module
or was not linked from a WebAssembly dependency archive. Add the dependency
archive to --extra-ldflags, or rebuild the main PHP.wasm runtime if the
symbol must come from PHP core.
WebAssembly.LinkError or startup crashes
Check that the extension loads in a JSPI runtime. The custom extension helper does not build Asyncify artifacts.
wasm-ld: unknown file type or file not recognized
One of the linked libraries is a native host library. Rebuild that dependency
with Emscripten and link the resulting .a file.
R_WASM_MEMORY_ADDR_SLEB cannot be used against symbol
A C or C++ object in a static archive was not compiled as position-independent
code. Rebuild it with -fPIC, or make sure Rust cc-rs sees
CFLAGS_wasm32_unknown_emscripten=-fPIC.
__cpp_exception is undefined when loading a Rust extension
The Rust archive was built against an unwinding std. Rebuild it with
RUSTFLAGS="-C panic=abort" and
cargo +nightly build -Zbuild-std=std,panic_abort.
bad export type for 'stdin' or another C runtime global
The side module pulled in a dependency object that expects a mutable C runtime global the main PHP.wasm module does not export. Rebuild the dependency with the unused feature disabled, link a smaller archive that excludes that object, or move the dependency into the main PHP.wasm build so the global is provided by the runtime.
phpize cannot find headers
The helper image builds and installs a minimal matching PHP source tree before
running phpize. If an extension includes headers from optional PHP
extensions, copy or generate those headers in the Docker layer or include them
in the extension source.