Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4c83988
Document native extension setup and release flow
adamziel May 17, 2026
2809345
Add Playground Blueprint for native API smoke test
adamziel May 17, 2026
255d331
Clarify Playground Blueprint preview URL
adamziel May 17, 2026
6913067
Document Playground extension manifest requirement
adamziel May 17, 2026
16346ff
Add Playground extension build workflow
adamziel May 17, 2026
57641a9
Run Playground extension build on branch push
adamziel May 17, 2026
846622f
Mark Playground extension build script executable
adamziel May 17, 2026
2b1d5b8
Discover libclang for Playground Rust build
adamziel May 17, 2026
10e696d
Keep Playground build script executable
adamziel May 17, 2026
aa0e59e
Use concrete libclang path for Playground build
adamziel May 17, 2026
dd254f9
Restore executable bit after libclang fix
adamziel May 17, 2026
b5c7718
Bump ext-php-rs for Playground PHP.wasm build
adamziel May 17, 2026
de8f7a9
Patch PHP.wasm build for ext-php-rs wasm target
adamziel May 17, 2026
b33c666
Mount repo root for Playground native build
adamziel May 17, 2026
369f0fa
Add Playground PHP.wasm native API extension artifact
adamziel May 17, 2026
6aad884
Document working Playground extension preview URL
adamziel May 17, 2026
6e6860c
Add native extension Playground smoke page
adamziel May 17, 2026
f7895f5
Fix Playground smoke preview host
adamziel May 17, 2026
a15686e
Wrap Playground smoke runner
adamziel May 17, 2026
13c26bf
Use browser ESM CDN for Playground smoke
adamziel May 17, 2026
95a8d17
Fix Playground native extension artifact
adamziel May 17, 2026
69bbb9e
Preserve dylink section in Playground artifact
adamziel May 17, 2026
f989640
Patch missing Playground data globals
adamziel May 17, 2026
b349846
Stub unexported PHP.wasm helper imports
adamziel May 17, 2026
e7532b7
Restore generated Playground artifact
adamziel May 17, 2026
d39aa19
Avoid unverified branch Playground URL
adamziel May 17, 2026
b28181b
Build Playground native API extension with C shim
adamziel May 17, 2026
26c50f6
Update Playground native API artifact
adamziel May 17, 2026
73d07a7
Use Zend allocation for Playground shim objects
adamziel May 17, 2026
4678aed
Update Playground artifact after allocation fix
adamziel May 17, 2026
679552f
Avoid libc scanner imports in Playground shim
adamziel May 17, 2026
8a15430
Update Playground artifact without libc scanner imports
adamziel May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/native-apis-playground-extension.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Native APIs Playground Extension

on:
workflow_dispatch:
push:
branches:
- codex-native-extension-docs

jobs:
build:
name: Build PHP.wasm extension
runs-on: ubuntu-latest
timeout-minutes: 90

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '24'

- name: Build wp_native_apis PHP.wasm extension
run: |
extensions/native-apis/build-playground-extension.sh

- name: Upload Playground extension
uses: actions/upload-artifact@v4
with:
name: wp-native-apis-playground-extension
path: extensions/native-apis/playground/dist/wp_native_apis
4 changes: 2 additions & 2 deletions extensions/native-apis/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions extensions/native-apis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ license = "GPL-2.0-or-later"
publish = false

[lib]
crate-type = ["cdylib", "rlib"]
crate-type = ["cdylib", "rlib", "staticlib"]

[features]
default = []
php-extension = ["dep:ext-php-rs"]

[dependencies]
ext-php-rs = { version = "=0.15.12", default-features = false, features = ["runtime"], optional = true }
ext-php-rs = { version = "=0.15.13", default-features = false, features = ["runtime"], optional = true }

[profile.release]
lto = "thin"
Expand Down
188 changes: 187 additions & 1 deletion extensions/native-apis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,77 @@ fallback behavior when it is unavailable or explicitly disabled.
- `WordPress\XML\NativeXMLProcessor`
- `WordPress\DataLiberation\URL\NativeURLInTextProcessor`

## Quick Setup

Use this path when you want to build, load, and verify the extension on a
development machine.

You need:

- PHP CLI with matching development headers and `php-config`.
- Rust and Cargo.
- Clang and libclang for `bindgen`.
- Composer dependencies from the repository root.

On Ubuntu, the GitHub Actions job uses:

```bash
sudo apt-get update
sudo apt-get install -y clang libclang-dev
composer install --prefer-dist --no-progress --no-suggest
```

Build and verify from the repository root:

```bash
extensions/native-apis/build-extension.sh

php -d extension=extensions/native-apis/target/release/libwp_native_apis.so \
extensions/native-apis/tests/verify-native-apis.php
```

If `php-config` is not on `PATH`, pass it explicitly:

```bash
PHP_CONFIG=/path/to/php-config \
LIBCLANG_PATH=/path/to/libclang/lib \
extensions/native-apis/build-extension.sh
```

Check the Rust parser kernels without PHP development headers:

```bash
cd extensions/native-apis
cargo test
```

Check that public PHP classes progressively resolve to native classes by loading
the extension before the repository bootstrap:

```bash
php -d extension=extensions/native-apis/target/release/libwp_native_apis.so <<'PHP'
<?php
require __DIR__ . '/bootstrap.php';

var_dump( is_subclass_of( 'WP_HTML_Tag_Processor', 'WP_HTML_Native_Tag_Processor' ) );
var_dump( is_subclass_of( 'WordPress\\XML\\XMLProcessor', 'WordPress\\XML\\NativeXMLProcessor' ) );
var_dump( is_subclass_of( 'WordPress\\DataLiberation\\URL\\URLInTextProcessor', 'WordPress\\DataLiberation\\URL\\NativeURLInTextProcessor' ) );
PHP
```

Force the PHP fallback path for comparison by defining
`WP_NATIVE_APIS_DISABLE_DEFAULTS` before loading the bootstrap:

```bash
php -d extension=extensions/native-apis/target/release/libwp_native_apis.so <<'PHP'
<?php
define( 'WP_NATIVE_APIS_DISABLE_DEFAULTS', true );
require __DIR__ . '/bootstrap.php';

var_dump( is_subclass_of( 'WP_HTML_Tag_Processor', 'WP_HTML_Native_Tag_Processor' ) );
PHP
```

## Public Wrapper Defaults

When the extension is loaded before the PHP components, public wrappers may use
Expand All @@ -31,7 +102,7 @@ candidate scanner. The public PHP class still validates candidates with the
existing WHATWG parser and uses the PHP regular-expression scanner for non-ASCII
text or when native defaults are disabled.

## Build
## Build Details

The build requires Rust, PHP development headers, `php-config`, and libclang.
The helper script checks those prerequisites before invoking Cargo. Depending on
Expand Down Expand Up @@ -80,6 +151,121 @@ The Rust parser kernels can be checked without PHP development headers:
cargo test
```

## Release Flow

The native extension has two release targets:

- host PHP builds, such as the Linux shared object produced by
`build-extension.sh`;
- PHP.wasm builds for WordPress Playground.

Treat every release as experimental until the extension has packaging,
signing, and artifact retention policy in CI.

### Host PHP release checklist

1. Update the extension version in `extensions/native-apis/Cargo.toml`.
2. Confirm that the public PHP wrappers still gate on
`supports_public_api()` and still fall back when the extension is absent.
3. Run the native verifier on the exact PHP version used to build the shared
object:

```bash
php -d extension=extensions/native-apis/target/release/libwp_native_apis.so \
extensions/native-apis/tests/verify-native-apis.php
```

4. Run benchmarks with native classes required:

```bash
php -d extension=extensions/native-apis/target/release/libwp_native_apis.so \
bin/benchmark-native-apis.php \
--iterations=100 \
--mode=both \
--disable-native-defaults \
--require-native
```

5. Wait for the `Native APIs`, `PHP CodeSniffer`, docs snippet, and full
PHPUnit matrix checks to pass on the release PR.
6. Attach the release artifact and the benchmark JSON to the GitHub release.
Name host artifacts with the target platform and PHP version, for example:

```text
wp-native-apis-0.1.0-php8.3-linux-x86_64.so
wp-native-apis-0.1.0-benchmark.json
```

Host `.so` files are tied to the PHP ABI they were built against. Do not reuse
a PHP 8.3 build for PHP 8.4 or PHP 8.5.

### Playground release checklist

Browser Playground runs PHP as WebAssembly, so it cannot load the Linux shared
object from `target/release/`. Publish the PHP.wasm extension bundle produced
by `build-playground-extension.sh` before claiming Playground support:

```bash
extensions/native-apis/build-playground-extension.sh
```

The host PHP extension is Rust-backed through `ext-php-rs`. The Playground
bundle currently uses `native_apis_shim.c` instead, because Playground's
PHP.wasm runtime only exports the PHP C ABI symbols needed by regular C
extensions. The shim registers the native extension classes and verifies the
Playground loading path while the full Rust-backed implementation remains the
host PHP artifact.

Attach the full output directory to a GitHub release:

```text
wp-native-apis-0.1.0-php-wasm/
|-- manifest.json
`-- wp_native_apis-php8.4-jspi.so
```

Use the Blueprint smoke test together with the Playground Query API
`php-extension` parameter to verify a published PHP.wasm extension bundle.
`php-extension` must be present in the initial Playground URL because PHP
extensions load before PHP starts; the Blueprint only writes and runs the smoke
test.

After publishing the PHP.wasm `manifest.json`, open the main Playground URL with
both the extension manifest and the Blueprint URL:

```text
https://playground.wordpress.net/?php=8.4&php-extension=<url-encoded-manifest-url>&blueprint-url=<url-encoded-blueprint-url>
```

For example, a release URL will look like:

```text
https://playground.wordpress.net/?php=8.4&php-extension=https%3A%2F%2Fgithub.com%2FWordPress%2Fphp-toolkit%2Freleases%2Fdownload%2Fnative-apis-v0.1.0%2Fmanifest.json&blueprint-url=https%3A%2F%2Fraw.githubusercontent.com%2FWordPress%2Fphp-toolkit%2Ftrunk%2Fextensions%2Fnative-apis%2Fplayground%2Fblueprint.json
```

Expected output:

```text
wp_native_apis extension version: 0.1.0
WP_HTML_Native_Tag_Processor: ok
WP_HTML_Native_Processor: ok
WordPress\XML\NativeXMLProcessor: ok
WordPress\DataLiberation\URL\NativeURLInTextProcessor: ok
PASS: Native API extension classes are available.
```

The Blueprint lives at `extensions/native-apis/playground/blueprint.json`. It
writes a small `native-api-smoke.php` file into Playground and navigates to it.
The smoke page checks that the four native classes are registered, then runs one
small HTML tag, HTML processor, XML processor, and URL-in-text operation.

If the smoke page reports missing classes, the selected Playground runtime does
not include the `wp_native_apis` PHP.wasm extension. Check that the URL includes
`php-extension=<manifest-url>`, the bundle matches the selected PHP version, and
the extension was built for the JSPI PHP.wasm ABI instead of the host PHP ABI.
Custom PHP.wasm extensions require a JSPI-capable browser runtime; non-JSPI
runtimes cannot load these side modules.

## Benchmarking

The repository benchmark harness defaults to PHP userland rows so existing
Expand Down
28 changes: 28 additions & 0 deletions extensions/native-apis/build-playground-extension.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "${script_dir}/../.." && pwd)"

php_versions="${PHP_WASM_VERSIONS:-8.4}"
php_version="${php_versions%%,*}"
out_dir="${1:-${script_dir}/playground/dist/wp_native_apis}"

cd "${repo_root}"

npx --yes @php-wasm/compile-extension \
--prepare-image \
--php-versions "${php_version}" \
--jobs 1

npx --yes @php-wasm/compile-extension \
--source ./extensions/native-apis \
--name wp_native_apis \
--php-versions "${php_version}" \
--out "${out_dir}" \
--jobs 1

cat <<MESSAGE
Built Playground extension manifest:
${out_dir}/manifest.json
MESSAGE
9 changes: 9 additions & 0 deletions extensions/native-apis/config.m4
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
PHP_ARG_ENABLE([wp_native_apis],
[whether to enable the wp_native_apis extension],
[AS_HELP_STRING([--enable-wp_native_apis], [Enable wp_native_apis extension])],
[yes])

if test "$PHP_WP_NATIVE_APIS" != "no"; then
AC_DEFINE([PHP_WP_NATIVE_APIS_VERSION], ["0.1.0"], [wp_native_apis extension version])
PHP_NEW_EXTENSION([wp_native_apis], [native_apis_shim.c], [$ext_shared])
fi
Loading
Loading