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 编译器 - PHP 8.4 或更高版本,用于运行 TypePHP 编译器
- Wasmtime 47 或更高版本,用于运行和测试产物 - Wasmtime 47 或更高版本,用于运行和测试产物
- Jco 1 或更高版本,用于 browser profile;component profile 不需要 Jco - Jco 1 或更高版本,用于 browser profile;component profile 不需要 Jco
- wit-bindgen-cli 0.60.0,用于 library/WasmExport 模式;command 模式不需要
- 与当前 TypePHP 版本绑定的 `wasm32-wasip2` 集成 SDK - 与当前 TypePHP 版本绑定的 `wasm32-wasip2` 集成 SDK
WASI SDK 的 `bin` 目录和 Wasmtime 必须加入系统 `PATH`。编译器不会探测或使用 `/opt` 等约定安装目录,也不接受专用的工具目录配置。WASI 静态库和头文件统一安装到 PHPX 的 `wasm/wasm32-wasip2/` 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 专用环境变量。 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=` 传递。 路径、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 提供。 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"); mustCreateDirectory("{$topLevelDir}/completions");
mustCopy('completions/tpc.bash', "{$topLevelDir}/completions/tpc.bash"); mustCopy('completions/tpc.bash', "{$topLevelDir}/completions/tpc.bash");
copyDirectory('wasm', "{$topLevelDir}/wasm"); 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)) { if (is_file($outputFile) && !unlink($outputFile)) {
throw new RuntimeException("Unable to remove existing archive: {$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']); $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 public function testRejectsOldLlvm(): void
{ {
$this->installFakeTools(21, 47, 1, 'wasm32-unknown-wasip2'); $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('wasmtime', "#!/bin/sh\necho 'wasmtime {$wasmtimeMajor}.0.0'\n");
$this->writeExecutable('jco', "#!/bin/sh\necho 'jco {$jcoMajor}.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 private function writeExecutable(string $name, string $contents): void

@ -9,9 +9,10 @@ final class WasiToolchain
public const MIN_LLVM_MAJOR = 22; public const MIN_LLVM_MAJOR = 22;
public const MIN_WASMTIME_MAJOR = 47; public const MIN_WASMTIME_MAJOR = 47;
public const MIN_JCO_MAJOR = 1; public const MIN_JCO_MAJOR = 1;
public const WIT_BINDGEN_VERSION = '0.60.0';
/** @return array<string, string> */ /** @return array<string, string> */
public function detect(bool $requireBrowserTools = true): array public function detect(bool $requireBrowserTools = true, bool $requireWitBindgen = false): array
{ {
$tools = []; $tools = [];
$requiredTools = [ $requiredTools = [
@ -26,6 +27,9 @@ final class WasiToolchain
if ($requireBrowserTools) { if ($requireBrowserTools) {
$requiredTools[] = 'jco'; $requiredTools[] = 'jco';
} }
if ($requireWitBindgen) {
$requiredTools[] = 'wit-bindgen';
}
foreach ($requiredTools as $name) { foreach ($requiredTools as $name) {
$tools[$name] = $this->findExecutable($name); $tools[$name] = $this->findExecutable($name);
} }
@ -39,6 +43,19 @@ final class WasiToolchain
if ($requireBrowserTools) { if ($requireBrowserTools) {
$versions['jco'] = $this->requireVersion('jco', $tools['jco'], self::MIN_JCO_MAJOR); $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']); [$exitCode, $target, $error] = $this->run([$tools['wasm32-wasip2-clang++'], '--print-target-triple']);
$target = trim($target); $target = trim($target);
@ -59,6 +76,9 @@ final class WasiToolchain
if ($requireBrowserTools) { if ($requireBrowserTools) {
$tools['jco-version'] = $versions['jco']; $tools['jco-version'] = $versions['jco'];
} }
if ($requireWitBindgen) {
$tools['wit-bindgen-version'] = $versions['wit-bindgen'];
}
return $tools; return $tools;
} }

@ -151,7 +151,7 @@ final class WasmInterfaceGenerator
/** /**
* Render the small TypePHP-specific half of the C binding. The generic * Render the small TypePHP-specific half of the C binding. The generic
* Canonical ABI half and component type object are emitted by the pinned * 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 * @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 orchestrator validates the combined installation before writing
`.typephp-wasi-sdk-abi`. `.typephp-wasi-sdk-abi`.
Autoconf, Bison, re2c, Rust, upstream `wit-bindgen`, and the PHP/PHPX source Autoconf, Bison, re2c, and the PHP/PHPX source trees are SDK producer
trees are SDK producer dependencies only. They are never searched for by an dependencies only. They are never searched for by an application build.
application build. PHPX release packages include the pinned host-side Library components additionally require the pinned host-side
`phpx-wit-bindgen` needed for application-specific exports below `wit-bindgen-cli 0.60.0` in `PATH`; command components do not use it.
`<phpx>/wasm/bin/<host-os>-<host-arch>/`.
WASM integration checks live in `tests/wasm` and run through `run-tests.php`. WASM integration checks live in `tests/wasm` and run through `run-tests.php`.
The target-independent high-precision TypePHP example lives at The target-independent high-precision TypePHP example lives at

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

Loading…
Cancel
Save