Pular para o conteúdo principal

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.m4 declares the PHP extension and builds a tiny C shim with PHP_NEW_EXTENSION.
  • _shim.c defines the PHP module entry and calls exported Rust functions over C ABI.
  • --extra-ldflags points at the Rust staticlib archive 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.