build(wasm): update wit-bindgen dependency management

- Remove bundled PHPX wit-bindgen binary and switch to PATH-based discovery
- Require wit-bindgen-cli 0.60.0 to be installed separately in system PATH
- Update error messages to reflect standalone wit-bindgen installation requirement
- Exclude host build tools from package distribution, only include target SDK
- Add WIT_BINDGEN_VERSION constant and detection logic in WasiToolchain
- Implement wit-bindgen version validation in toolchain detection
- Add test cases for wit-bindgen presence and version compatibility checks
- Update documentation to reflect wit-bindgen-cli 0.60.0 installation requirement
- Modify build scripts to locate wit-bindgen through PATH instead of PHPX bundle
master
韩天峰 2 weeks ago
parent 96960e9da1
commit e4f6475452
  1. 5
      docs/WASI_BUILD.md
  2. 4
      package.php
  3. 23
      phpunit/src/Build/WasiToolchainTest.php
  4. 22
      src/Build/WasiToolchain.php
  5. 2
      src/Build/WasmInterfaceGenerator.php
  6. 9
      wasm/README.md
  7. 13
      wasm/build-program.sh

@ -8,6 +8,7 @@ TypePHP 使用稳定的 WASI 0.2(Preview 2)和 Component Model。TypePHP 生
- PHP 8.4 或更高版本,用于运行 TypePHP 编译器
- Wasmtime 47 或更高版本,用于运行和测试产物
- Jco 1 或更高版本,用于 browser profile;component profile 不需要 Jco
- wit-bindgen-cli 0.60.0,用于 library/WasmExport 模式;command 模式不需要
- 与当前 TypePHP 版本绑定的 `wasm32-wasip2` 集成 SDK
WASI SDK 的 `bin` 目录和 Wasmtime 必须加入系统 `PATH`。编译器不会探测或使用 `/opt` 等约定安装目录,也不接受专用的工具目录配置。WASI 静态库和头文件统一安装到 PHPX 的 `wasm/wasm32-wasip2/`
@ -18,7 +19,7 @@ export PATH="<wasi-sdk-bin>:<wasmtime-bin>:$PATH"
TypePHP 使用现有的 PHPX 定位规则:优先读取 `PHPX_HOME`,其次读取 Composer 的 `swoole/phpx` 安装位置,最后使用 `vendor/swoole/phpx`。不新增 WASI 专用环境变量。
WASI 构建会检查 `wasm32-wasip2-clang`、`wasm32-wasip2-clang++`、`llvm-ar`、`llvm-ranlib`、`llvm-nm`、`wasm-component-ld` 和 `wasmtime`,并确认目标是 `wasm32-unknown-wasip2`。browser profile 另外检查 `jco`。所有工具只从 `PATH` 查找;npm script 会自动将项目本地的 `node_modules/.bin` 加入 `PATH`
WASI 构建会检查 `wasm32-wasip2-clang`、`wasm32-wasip2-clang++`、`llvm-ar`、`llvm-ranlib`、`llvm-nm`、`wasm-component-ld` 和 `wasmtime`,并确认目标是 `wasm32-unknown-wasip2`。browser profile 另外检查 `jco`,library 模式另外检查固定版本的 `wit-bindgen`。所有工具只从 `PATH` 查找;npm script 会自动将项目本地的 `node_modules/.bin` 加入 `PATH`
## 一条命令构建
@ -63,7 +64,7 @@ sources:
路径、sources 等详细配置继续放在 `project.yml`,不通过 `--wasm=` 传递。
PHP、PHPX、TypePHP runtime、GMP、MPFR 和 mpdecimal 由 SDK 发布阶段预编译为 WASI 静态库。应用构建只编译 TypePHP 为当前程序生成的 C++,然后链接这些 `.a`。`tpc --wasm` 不会下载源码,也不会调用 PHP、PHPX 或高精度库的构建脚本。library 模式会调用 PHPX 包内固定版本的 `wit-bindgen` 生成当前应用的 Canonical ABI 绑定;普通用户不需要从 `PATH` 安装它
PHP、PHPX、TypePHP runtime、GMP、MPFR 和 mpdecimal 由 SDK 发布阶段预编译为 WASI 静态库。应用构建只编译 TypePHP 为当前程序生成的 C++,然后链接这些 `.a`。`tpc --wasm` 不会下载源码,也不会调用 PHP、PHPX 或高精度库的构建脚本。library 模式会调用 `PATH` 中的 `wit-bindgen-cli 0.60.0` 生成当前应用的 Canonical ABI 绑定。
PHP/WASI 当前静态内建 `date`、`pcre`、`hash`、`json`、`lexbor`、`random`、`Reflection`、`SPL`、`standard`、`uri`、`ctype`、`calendar`、`bcmath`、`filter`、`tokenizer`、`mbstring`、`zlib`、`fileinfo`、`sodium`、`openssl`、`libxml`、`dom`、`SimpleXML`、`xml`、`xmlreader`、`xmlwriter`、`PDO`、`pdo_sqlite`、`zip`、`bz2` 和 `exif` 扩展。OpenSSL 采用 crypto-only 构建,不包含 TLS stream transport;HTTP/HTTPS 仍由 WASI HTTP Component 提供。

@ -707,7 +707,9 @@ function packageUnixLike(): void
mustCreateDirectory("{$topLevelDir}/completions");
mustCopy('completions/tpc.bash', "{$topLevelDir}/completions/tpc.bash");
copyDirectory('wasm', "{$topLevelDir}/wasm");
copyDirectory("{$phpxSourceDir}/wasm", "{$topLevelDir}/vendor/swoole/phpx/wasm");
// Host build tools such as wit-bindgen are installed independently and
// discovered through PATH; only the target SDK belongs in the package.
copyDirectory("{$phpxSourceDir}/wasm", "{$topLevelDir}/vendor/swoole/phpx/wasm", ['bin']);
if (is_file($outputFile) && !unlink($outputFile)) {
throw new RuntimeException("Unable to remove existing archive: {$outputFile}");

@ -65,6 +65,28 @@ final class WasiToolchainTest extends TestCase
$this->assertSame('wasm32-unknown-wasip2', $tools['target']);
}
public function testLibraryModeRequiresPinnedWitBindgenFromPath(): void
{
$this->installFakeTools(22, 47, 1, 'wasm32-unknown-wasip2');
putenv('PATH=' . $this->directory);
$tools = (new WasiToolchain())->detect(false, true);
$this->assertSame($this->directory . '/wit-bindgen', $tools['wit-bindgen']);
$this->assertSame('0.60.0', $tools['wit-bindgen-version']);
}
public function testLibraryModeRejectsIncompatibleWitBindgen(): void
{
$this->installFakeTools(22, 47, 1, 'wasm32-unknown-wasip2');
$this->writeExecutable('wit-bindgen', "#!/bin/sh\necho 'wit-bindgen-cli 0.61.0'\n");
putenv('PATH=' . $this->directory);
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('wit-bindgen-cli 0.60.0 is required');
(new WasiToolchain())->detect(false, true);
}
public function testRejectsOldLlvm(): void
{
$this->installFakeTools(21, 47, 1, 'wasm32-unknown-wasip2');
@ -97,6 +119,7 @@ final class WasiToolchainTest extends TestCase
);
$this->writeExecutable('wasmtime', "#!/bin/sh\necho 'wasmtime {$wasmtimeMajor}.0.0'\n");
$this->writeExecutable('jco', "#!/bin/sh\necho 'jco {$jcoMajor}.0.0'\n");
$this->writeExecutable('wit-bindgen', "#!/bin/sh\necho 'wit-bindgen-cli 0.60.0'\n");
}
private function writeExecutable(string $name, string $contents): void

@ -9,9 +9,10 @@ final class WasiToolchain
public const MIN_LLVM_MAJOR = 22;
public const MIN_WASMTIME_MAJOR = 47;
public const MIN_JCO_MAJOR = 1;
public const WIT_BINDGEN_VERSION = '0.60.0';
/** @return array<string, string> */
public function detect(bool $requireBrowserTools = true): array
public function detect(bool $requireBrowserTools = true, bool $requireWitBindgen = false): array
{
$tools = [];
$requiredTools = [
@ -26,6 +27,9 @@ final class WasiToolchain
if ($requireBrowserTools) {
$requiredTools[] = 'jco';
}
if ($requireWitBindgen) {
$requiredTools[] = 'wit-bindgen';
}
foreach ($requiredTools as $name) {
$tools[$name] = $this->findExecutable($name);
}
@ -39,6 +43,19 @@ final class WasiToolchain
if ($requireBrowserTools) {
$versions['jco'] = $this->requireVersion('jco', $tools['jco'], self::MIN_JCO_MAJOR);
}
if ($requireWitBindgen) {
[$exitCode, $output, $error] = $this->run([$tools['wit-bindgen'], '--version']);
$version = trim($output . "\n" . $error);
$expectedVersion = 'wit-bindgen-cli ' . self::WIT_BINDGEN_VERSION;
if ($exitCode !== 0 || $version !== $expectedVersion) {
throw new RuntimeException(
"WASI tool `wit-bindgen` has an incompatible version: "
. ($version !== '' ? $version : 'unknown')
. "; {$expectedVersion} is required",
);
}
$versions['wit-bindgen'] = self::WIT_BINDGEN_VERSION;
}
[$exitCode, $target, $error] = $this->run([$tools['wasm32-wasip2-clang++'], '--print-target-triple']);
$target = trim($target);
@ -59,6 +76,9 @@ final class WasiToolchain
if ($requireBrowserTools) {
$tools['jco-version'] = $versions['jco'];
}
if ($requireWitBindgen) {
$tools['wit-bindgen-version'] = $versions['wit-bindgen'];
}
return $tools;
}

@ -151,7 +151,7 @@ final class WasmInterfaceGenerator
/**
* Render the small TypePHP-specific half of the C binding. The generic
* Canonical ABI half and component type object are emitted by the pinned
* wit-bindgen binary shipped with PHPX.
* wit-bindgen version detected in PATH.
*
* @param array<string, mixed> $manifest
*/

@ -64,11 +64,10 @@ GMP, MPFR, the vendored mpdecimal, `libphpx.a`, and their headers. The
orchestrator validates the combined installation before writing
`.typephp-wasi-sdk-abi`.
Autoconf, Bison, re2c, Rust, upstream `wit-bindgen`, and the PHP/PHPX source
trees are SDK producer dependencies only. They are never searched for by an
application build. PHPX release packages include the pinned host-side
`phpx-wit-bindgen` needed for application-specific exports below
`<phpx>/wasm/bin/<host-os>-<host-arch>/`.
Autoconf, Bison, re2c, and the PHP/PHPX source trees are SDK producer
dependencies only. They are never searched for by an application build.
Library components additionally require the pinned host-side
`wit-bindgen-cli 0.60.0` in `PATH`; command components do not use it.
WASM integration checks live in `tests/wasm` and run through `run-tests.php`.
The target-independent high-precision TypePHP example lives at

@ -158,22 +158,21 @@ include_flags=(
-I"${generated_dir}/include"
)
# WIT is application-specific, but its generator is a host build tool. PHPX
# packages a pinned wit-bindgen binary per host so users never install it or a
# Rust toolchain. It is not linked into PHPX or the resulting component.
# WIT is application-specific, and its generator is a host build tool found by
# tpc in PATH. It is not linked into PHPX or the resulting component.
binding_objects=()
if [[ "${wasm_mode}" == library ]]; then
bindgen=${TYPEPHP_WIT_BINDGEN:?TYPEPHP_WIT_BINDGEN is required in library mode}
if [[ ! -x "${bindgen}" ]]; then
fatal_error \
"PHPX bundled WIT binding generator is missing: ${bindgen}" \
"Install the matching PHPX package; installing wit-bindgen separately is not required."
"WASI build tool wit-bindgen is unavailable: ${bindgen}" \
"Install wit-bindgen-cli 0.60.0 and add it to PATH."
fi
bindgen_version=$("${bindgen}" --version 2>/dev/null || true)
if [[ "${bindgen_version}" != 'wit-bindgen-cli 0.60.0' ]]; then
fatal_error \
"PHPX bundled WIT binding generator has an incompatible version: ${bindgen_version:-unknown}" \
"Expected wit-bindgen-cli 0.60.0 from the matching PHPX package."
"WASI build tool wit-bindgen has an incompatible version: ${bindgen_version:-unknown}" \
"Install wit-bindgen-cli 0.60.0 and add it to PATH."
fi
binding_world=${TYPEPHP_WASM_WORLD//-/_}
"${bindgen}" c \

Loading…
Cancel
Save