From 76d104b8484594edb7a05c4abdd14c1ee3313ddb Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 08:54:26 +0100
Subject: [PATCH 01/41] chore(release): prepare 1.0.35 macOS service release
---
.github/workflows/native-runtime-packages.yml | 20 +++++++++++++++++--
Directory.Build.props | 2 +-
Directory.Build.targets | 2 +-
.../WebScene.NativeEngine.Runtime/README.md | 2 ++
.../WebScene.NativeEngine.Runtime.csproj | 1 +
scripts/build-native-engine-runtime.sh | 17 +++++++++++++---
.../test_verify_cross_rid_compatibility.py | 2 +-
scripts/verify-release-packages.py | 5 +++--
8 files changed, 41 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index f02e7f214..416406f43 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -162,6 +162,17 @@ jobs:
v8_cache_generation: v9-v8-15.3.10-pa-no-process-shim-macos-inspector
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
+ - os: macos-15-intel
+ rid: osx-x64
+ cpu: x64
+ monolith: libv8_monolith.a
+ script: unix
+ v8_revision: 15.3.10
+ partition_alloc: true
+ v8_configuration: ReleasePartitionAlloc
+ v8_cache_generation: v9-v8-15.3.10-pa-no-process-shim-macos-inspector
+ v8_cache_script: scripts/build-native-engine-runtime.sh
+ v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
- os: ubuntu-latest
rid: linux-x64
cpu: x64
@@ -268,7 +279,7 @@ jobs:
fi
fi
- name: Build, pack, and test macOS runtime
- if: matrix.rid == 'osx-arm64'
+ if: startsWith(matrix.rid, 'osx-')
shell: bash
run: |
v8_root=
@@ -336,7 +347,7 @@ jobs:
shell: bash
run: |
case '${{ matrix.rid }}' in
- osx-arm64) native_name=libwebscene_native_engine.dylib ;;
+ osx-arm64|osx-x64) native_name=libwebscene_native_engine.dylib ;;
linux-x64) native_name=libwebscene_native_engine.so ;;
win-x64) native_name=webscene_native_engine.dll ;;
*) echo "Unsupported discovery RID '${{ matrix.rid }}'." >&2; exit 1 ;;
@@ -456,6 +467,7 @@ jobs:
--profile tests/WebPlatformSubset/webscene-component-profile.json \
--selection required \
--expected-rid osx-arm64 \
+ --expected-rid osx-x64 \
--expected-rid linux-x64 \
--expected-rid win-x64 \
--output artifacts/required-compatibility/cross-rid-summary.json
@@ -490,6 +502,7 @@ jobs:
artifacts/nuget-packages \
--version '${{ needs.metadata.outputs.package-version }}' \
--native-rid osx-arm64 \
+ --native-rid osx-x64 \
--native-rid linux-x64 \
--native-rid win-x64 \
--output artifacts/nuget-packages/release-packages.json
@@ -530,6 +543,7 @@ jobs:
--profile tests/WebPlatformSubset/webscene-component-profile.json \
--selection candidate \
--expected-rid osx-arm64 \
+ --expected-rid osx-x64 \
--expected-rid linux-x64 \
--expected-rid win-x64 \
--output artifacts/candidate-compatibility/cross-rid-summary.json
@@ -550,6 +564,8 @@ jobs:
include:
- os: macos-latest
rid: osx-arm64
+ - os: macos-15-intel
+ rid: osx-x64
- os: ubuntu-latest
rid: linux-x64
- os: windows-2022
diff --git a/Directory.Build.props b/Directory.Build.props
index b55b6501f..79f528cea 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -3,7 +3,7 @@
true
true
$(NoWarn);NU1507
- 1.0.34
+ 1.0.35
Wiesław Šoltés
Wiesław Šoltés
Copyright © Wiesław Šoltés 2025
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 29c3122fa..3fa3a481e 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -4,7 +4,7 @@
$(PackageTags);webscene
$(PackageTags);web-ui
$(PackageTags);native-ui
- Adds generated ABI 3 codecs for external models, improves cross-platform web-font decoding, caches system-font probes and text shaping, and reduces CSS-variable resize recalculation while preserving dimension inheritance. Fixes detached DOM cleanup after memory-pressure collection and includes cross-platform runtime fixes.
+ Service release of the 1.0.34 codebase. Adds native runtime packages for Apple silicon and Intel Macs with a minimum supported macOS version of 14. No product code changes.
README.md
diff --git a/packaging/WebScene.NativeEngine.Runtime/README.md b/packaging/WebScene.NativeEngine.Runtime/README.md
index 40bee3e90..b1be9b664 100644
--- a/packaging/WebScene.NativeEngine.Runtime/README.md
+++ b/packaging/WebScene.NativeEngine.Runtime/README.md
@@ -39,6 +39,7 @@ Install the package matching the application's deployment RID:
```xml
+
```
@@ -46,6 +47,7 @@ Install the package matching the application's deployment RID:
| Target platform | Runtime identifier | Package |
| --- | --- | --- |
| macOS on Apple silicon | `osx-arm64` | [`WebScene.NativeEngine.Runtime.osx-arm64`](https://www.nuget.org/packages/WebScene.NativeEngine.Runtime.osx-arm64/) |
+| macOS on Intel | `osx-x64` | [`WebScene.NativeEngine.Runtime.osx-x64`](https://www.nuget.org/packages/WebScene.NativeEngine.Runtime.osx-x64/) |
| Linux x64 | `linux-x64` | [`WebScene.NativeEngine.Runtime.linux-x64`](https://www.nuget.org/packages/WebScene.NativeEngine.Runtime.linux-x64/) |
| Windows x64 | `win-x64` | [`WebScene.NativeEngine.Runtime.win-x64`](https://www.nuget.org/packages/WebScene.NativeEngine.Runtime.win-x64/) |
diff --git a/packaging/WebScene.NativeEngine.Runtime/WebScene.NativeEngine.Runtime.csproj b/packaging/WebScene.NativeEngine.Runtime/WebScene.NativeEngine.Runtime.csproj
index 1d5d7f405..bf636d0b0 100644
--- a/packaging/WebScene.NativeEngine.Runtime/WebScene.NativeEngine.Runtime.csproj
+++ b/packaging/WebScene.NativeEngine.Runtime/WebScene.NativeEngine.Runtime.csproj
@@ -8,6 +8,7 @@
WebScene.NativeEngine.Runtime.Template
$(WebSceneNativeEngineRid)
macOS on Apple silicon
+ macOS on Intel
Linux x64
Windows x64
WebScene Native Engine Runtime for $(WebSceneNativeEnginePlatformName)
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 218fd2fda..5e3fadd72 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -314,6 +314,10 @@ cmake_args=(
-DWEBSCENE_V8_ROOT="$v8_root"
-DWEBSCENE_V8_OUTPUT_ROOT="$v8_output_root"
)
+macos_deployment_target=14.0
+if [[ "$expected_kernel" == Darwin ]]; then
+ cmake_args+=(-DCMAKE_OSX_DEPLOYMENT_TARGET="$macos_deployment_target")
+fi
if [[ "$thin_lto" == true ]]; then
v8_llvm_bin="$v8_root/third_party/llvm-build/Release+Asserts/bin"
for llvm_tool in clang clang++ llvm-ar lld; do
@@ -346,9 +350,6 @@ if [[ "$thin_lto" == true ]]; then
-DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld
-DCMAKE_MODULE_LINKER_FLAGS=-fuse-ld=lld
)
- if [[ "$expected_kernel" == Darwin ]]; then
- cmake_args+=(-DCMAKE_OSX_DEPLOYMENT_TARGET=12.0)
- fi
elif [[ "$expected_kernel" == Linux ]]; then
# V8's Linux archive must be linked with LLD. The compiler is selectable so
# the Ubuntu 22.04 compatibility image can use GCC 11's complete C++20
@@ -374,6 +375,16 @@ if [[ ! -f "$native_path" ]]; then
echo "Native engine build did not produce '$native_path'." >&2
exit 1
fi
+if [[ "$expected_kernel" == Darwin ]]; then
+ actual_macos_deployment_target="$(
+ xcrun vtool -show-build "$native_path" |
+ awk '$1 == "minos" { print $2; exit }'
+ )"
+ if [[ "$actual_macos_deployment_target" != "$macos_deployment_target" ]]; then
+ echo "Native engine deployment target is '$actual_macos_deployment_target'; expected '$macos_deployment_target'." >&2
+ exit 1
+ fi
+fi
if [[ "$expected_kernel" == Darwin && "$cmake_build_type" == RelWithDebInfo ]]; then
native_dsym_path="$native_path.dSYM"
cmake -E remove_directory "$native_dsym_path"
diff --git a/scripts/tests/test_verify_cross_rid_compatibility.py b/scripts/tests/test_verify_cross_rid_compatibility.py
index 6de613b87..d25df68c7 100644
--- a/scripts/tests/test_verify_cross_rid_compatibility.py
+++ b/scripts/tests/test_verify_cross_rid_compatibility.py
@@ -11,7 +11,7 @@
REPOSITORY_ROOT = pathlib.Path(__file__).resolve().parents[2]
VERIFIER = REPOSITORY_ROOT / "scripts" / "verify-cross-rid-compatibility.py"
-RIDS = ("osx-arm64", "linux-x64", "win-x64")
+RIDS = ("osx-arm64", "osx-x64", "linux-x64", "win-x64")
class CrossRidCompatibilityVerifierTests(unittest.TestCase):
diff --git a/scripts/verify-release-packages.py b/scripts/verify-release-packages.py
index f72cf4c4d..c03eacb7a 100755
--- a/scripts/verify-release-packages.py
+++ b/scripts/verify-release-packages.py
@@ -27,13 +27,14 @@
"WebScene.Sdk.Avalonia",
"WebScene.Sdk.Uno",
}
-DEFAULT_NATIVE_RIDS = {"osx-arm64", "linux-x64", "win-x64"}
+DEFAULT_NATIVE_RIDS = {"osx-arm64", "osx-x64", "linux-x64", "win-x64"}
NATIVE_V8_REVISIONS = {
"osx-arm64": "15.3.10",
+ "osx-x64": "15.3.10",
"linux-x64": "15.3.10",
"win-x64": "15.3.10",
}
-PARTITION_ALLOC_NATIVE_RIDS = {"osx-arm64", "linux-x64", "win-x64"}
+PARTITION_ALLOC_NATIVE_RIDS = {"osx-arm64", "osx-x64", "linux-x64", "win-x64"}
REPOSITORY_URL = "https://github.com/wieslawsoltes/WebScene"
REQUIRED_PACKAGE_TAGS = {"webscene", "web-ui", "native-ui"}
From 0162cd244345e9d744c0608a8d96f5661e6104dc Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 09:57:03 +0100
Subject: [PATCH 02/41] build(linux): add reproducible glibc cross-builds
---
.github/workflows/native-runtime-packages.yml | 326 +++++++++++++++---
Directory.Build.targets | 2 +-
.../CMakeLists.txt | 44 ++-
.../native/webscene_v8_runtime_support.inc | 4 +
.../tests/native_v8_runtime_input_tests.inc | 4 +
.../Dockerfile.linux-glibc | 40 +++
.../Dockerfile.linux-x64 | 58 ----
.../WebScene.NativeEngine.Runtime/README.md | 19 +
.../WebScene.NativeEngine.Runtime.csproj | 6 +-
.../linux-build-lock.json | 39 +++
scripts/build-linux-native-runtime.sh | 133 +++++++
...d-native-engine-runtime-linux-container.sh | 68 ----
scripts/build-native-engine-runtime.sh | 103 +++++-
scripts/linux-glibc-toolchain.cmake | 27 ++
scripts/tests/test_linux_build_policy.py | 49 +++
.../test_verify_cross_rid_compatibility.py | 2 +-
scripts/tests/test_verify_linux_native_abi.py | 61 ++++
...t_verify_native_payload_reproducibility.py | 41 +++
scripts/verify-linux-native-abi.py | 141 ++++++++
.../verify-native-payload-reproducibility.py | 48 +++
scripts/verify-release-packages.py | 17 +-
21 files changed, 1021 insertions(+), 211 deletions(-)
create mode 100644 packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
delete mode 100644 packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-x64
create mode 100644 packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
create mode 100755 scripts/build-linux-native-runtime.sh
delete mode 100755 scripts/build-native-engine-runtime-linux-container.sh
create mode 100644 scripts/linux-glibc-toolchain.cmake
create mode 100644 scripts/tests/test_linux_build_policy.py
create mode 100644 scripts/tests/test_verify_linux_native_abi.py
create mode 100644 scripts/tests/test_verify_native_payload_reproducibility.py
create mode 100755 scripts/verify-linux-native-abi.py
create mode 100755 scripts/verify-native-payload-reproducibility.py
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 416406f43..04bddb93b 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -22,7 +22,9 @@ on:
- 'experiments/WebScene.NativeEngine.Probe/**'
- 'packaging/WebScene.NativeEngine.Runtime/**'
- 'scripts/build-native-engine-runtime.sh'
- - 'scripts/build-native-engine-runtime-linux-container.sh'
+ - 'scripts/build-linux-native-runtime.sh'
+ - 'scripts/linux-glibc-toolchain.cmake'
+ - 'scripts/verify-linux-native-abi.py'
- 'scripts/build-native-engine-runtime.ps1'
- 'scripts/pack-packages.sh'
- 'scripts/verify-cross-rid-compatibility.py'
@@ -56,6 +58,9 @@ jobs:
- name: Test cross-RID evidence verifier
run: |
python3 -m unittest \
+ scripts/tests/test_linux_build_policy.py \
+ scripts/tests/test_verify_linux_native_abi.py \
+ scripts/tests/test_verify_native_payload_reproducibility.py \
scripts/tests/test_verify_cross_rid_compatibility.py
- name: Setup .NET
uses: actions/setup-dotnet@v5
@@ -144,9 +149,47 @@ jobs:
artifacts/nuget-packages/packages.json
if-no-files-found: error
+ linux-builder:
+ name: Build immutable Linux cross-builder
+ needs: metadata
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: read
+ packages: write
+ outputs:
+ image: ${{ steps.reference.outputs.image }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker/setup-buildx-action@v3
+ - name: Log in to GitHub Container Registry
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - id: build
+ name: Build pinned Linux cross-builder
+ uses: docker/build-push-action@v6
+ with:
+ context: packaging/WebScene.NativeEngine.Runtime
+ file: packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
+ platforms: linux/amd64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ghcr.io/wieslawsoltes/webscene-linux-builder:webscene-linux-glibc-v1
+ - id: reference
+ name: Resolve immutable builder reference
+ if: github.event_name != 'pull_request'
+ shell: bash
+ run: echo "image=ghcr.io/wieslawsoltes/webscene-linux-builder@${{ steps.build.outputs.digest }}" >> "$GITHUB_OUTPUT"
+
native:
name: Build ${{ matrix.rid }}
- needs: metadata
+ needs: [metadata, linux-builder]
+ permissions:
+ actions: read
+ contents: read
+ packages: read
strategy:
fail-fast: false
matrix:
@@ -181,7 +224,18 @@ jobs:
v8_revision: 15.3.10
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
- v8_cache_generation: v12-v8-15.3.10-pa-no-process-shim-ubuntu22-gcc12-lld-no-crel-shared-inspector
+ v8_cache_generation: v1-v8-15.3.10-glibc227-cross-x64
+ v8_cache_script: scripts/build-native-engine-runtime.sh
+ v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
+ - os: ubuntu-24.04
+ rid: linux-arm64
+ cpu: arm64
+ monolith: libv8_monolith.a
+ script: unix
+ v8_revision: 15.3.10
+ partition_alloc: true
+ v8_configuration: ReleasePartitionAlloc
+ v8_cache_generation: v1-v8-15.3.10-glibc227-cross-arm64
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- os: windows-2022
@@ -204,15 +258,6 @@ jobs:
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- - name: Build Ubuntu 22.04 Linux runtime image
- if: matrix.rid == 'linux-x64'
- shell: bash
- run: |
- docker build \
- --platform linux/amd64 \
- --file packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-x64 \
- --tag webscene-native-linux-builder:ubuntu-22.04 \
- packaging/WebScene.NativeEngine.Runtime
- id: v8-cache-key
name: Resolve pinned V8 SDK cache identity
shell: bash
@@ -220,6 +265,7 @@ jobs:
echo "image-version=${ImageVersion:-unknown}" >> "$GITHUB_OUTPUT"
- id: v8-cache
name: Restore pinned V8 SDK
+ if: github.ref_type != 'tag'
uses: actions/cache/restore@v4
with:
path: |
@@ -231,7 +277,7 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/icu/LICENSE
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/partition_alloc/src
- key: webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-${{ steps.v8-cache-key.outputs.image-version }}-${{ hashFiles(matrix.v8_cache_script, matrix.v8_cache_patch) }}
+ key: webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-${{ steps.v8-cache-key.outputs.image-version }}-${{ hashFiles(matrix.v8_cache_script, matrix.v8_cache_patch, 'packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc', 'packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json', 'scripts/linux-glibc-toolchain.cmake') }}
restore-keys: |
webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-
webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-
@@ -255,9 +301,9 @@ jobs:
|| [[ '${{ matrix.rid }}' == 'win-x64' ]] \
|| { grep -Eq '^use_allocator_shim *= *false$' "$args" \
&& grep -Eq '^use_partition_alloc_as_malloc *= *false$' "$args"; }; } \
- && { [[ '${{ matrix.rid }}' != 'linux-x64' ]] \
+ && { [[ '${{ matrix.rid }}' != linux-* ]] \
|| { grep -Eq '^use_lld *= *true$' "$args" \
- && grep -Eq '^use_sysroot *= *false$' "$args" \
+ && grep -Eq '^use_sysroot *= *true$' "$args" \
&& grep -Eq '^v8_monolithic_for_shared_library *= *true$' "$args"; }; }
}
if [[ -f "$root/include/v8.h" \
@@ -295,36 +341,21 @@ jobs:
--upstream-v8 \
--partition-alloc \
--output "$GITHUB_WORKSPACE/artifacts/nuget-packages"
- - name: Build, pack, and test Linux runtime
- if: matrix.rid == 'linux-x64'
+ - name: Build Linux runtime
+ if: startsWith(matrix.rid, 'linux-')
shell: bash
run: |
- v8_root=
- if [[ '${{ steps.restored-v8-sdk.outputs.ready }}' == 'true' ]]; then
- v8_root="/workspace/artifacts/native-engine-v8/${{ matrix.rid }}/v8"
+ builder_args=()
+ if [[ -n '${{ needs.linux-builder.outputs.image }}' ]]; then
+ builder_args+=(--builder-image '${{ needs.linux-builder.outputs.image }}')
+ echo '${{ secrets.GITHUB_TOKEN }}' | docker login ghcr.io -u '${{ github.actor }}' --password-stdin
fi
- docker run --rm \
- --platform linux/amd64 \
- --user "$(id -u):$(id -g)" \
- --env HOME=/tmp/webscene-home \
- --env DOTNET_CLI_HOME=/tmp/webscene-home \
- --env CARGO_HOME=/tmp/webscene-home/.cargo \
- --env NUGET_PACKAGES=/tmp/webscene-home/.nuget/packages \
- --volume "$GITHUB_WORKSPACE:/workspace" \
- --workdir /workspace \
- webscene-native-linux-builder:ubuntu-22.04 \
- bash -lc "
- mkdir -p \"\$HOME\"
- mkdir -p \"\$CARGO_HOME\"
- scripts/build-native-engine-runtime-linux-container.sh \
- --rid '${{ matrix.rid }}' \
- --package-version '${{ needs.metadata.outputs.package-version }}' \
- --v8-root '$v8_root' \
- --v8-revision '${{ matrix.v8_revision }}' \
- --upstream-v8 \
- --partition-alloc \
- --output /workspace/artifacts/nuget-packages
- "
+ scripts/build-linux-native-runtime.sh \
+ --rid '${{ matrix.rid }}' \
+ --package-version '${{ needs.metadata.outputs.package-version }}' \
+ --output "$GITHUB_WORKSPACE/artifacts/nuget-packages" \
+ --stage build \
+ "${builder_args[@]}"
- name: Build, pack, and test Windows runtime
if: matrix.script == 'windows'
shell: pwsh
@@ -343,12 +374,13 @@ jobs:
-PartitionAlloc `
-Output "$env:GITHUB_WORKSPACE/artifacts/nuget-packages"
- name: Run candidate compatibility discovery
+ if: matrix.rid != 'linux-arm64'
continue-on-error: true
shell: bash
run: |
case '${{ matrix.rid }}' in
osx-arm64|osx-x64) native_name=libwebscene_native_engine.dylib ;;
- linux-x64) native_name=libwebscene_native_engine.so ;;
+ linux-x64|linux-arm64) native_name=libwebscene_native_engine.so ;;
win-x64) native_name=webscene_native_engine.dll ;;
*) echo "Unsupported discovery RID '${{ matrix.rid }}'." >&2; exit 1 ;;
esac
@@ -386,9 +418,9 @@ jobs:
grep -Eq '^v8_enable_pointer_compression *= *true$' "$args" \
&& grep -Eq '^v8_enable_pointer_compression_shared_cage *= *true$' "$args" \
&& grep -Eq '^v8_enable_partition_alloc *= *${{ matrix.partition_alloc }}$' "$args" \
- && { [[ '${{ matrix.rid }}' != 'linux-x64' ]] \
+ && { [[ '${{ matrix.rid }}' != linux-* ]] \
|| { grep -Eq '^use_lld *= *true$' "$args" \
- && grep -Eq '^use_sysroot *= *false$' "$args" \
+ && grep -Eq '^use_sysroot *= *true$' "$args" \
&& grep -Eq '^v8_monolithic_for_shared_library *= *true$' "$args" \
&& grep -Eq '^use_allocator_shim *= *false$' "$args" \
&& grep -Eq '^use_partition_alloc_as_malloc *= *false$' "$args"; }; }
@@ -408,7 +440,7 @@ jobs:
echo "ready=false" >> "$GITHUB_OUTPUT"
fi
- name: Save completed V8 SDK
- if: always() && steps.v8-sdk-ready.outputs.ready == 'true'
+ if: always() && github.ref_type != 'tag' && steps.v8-sdk-ready.outputs.ready == 'true'
uses: actions/cache/save@v4
with:
path: |
@@ -420,38 +452,130 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/icu/LICENSE
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/partition_alloc/src
- key: webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-${{ steps.v8-cache-key.outputs.image-version }}-${{ hashFiles(matrix.v8_cache_script, matrix.v8_cache_patch) }}
+ key: webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-${{ steps.v8-cache-key.outputs.image-version }}-${{ hashFiles(matrix.v8_cache_script, matrix.v8_cache_patch, 'packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc', 'packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json', 'scripts/linux-glibc-toolchain.cmake') }}
- name: Upload required compatibility evidence
- if: success()
+ if: success() && matrix.rid != 'linux-arm64'
uses: actions/upload-artifact@v4
with:
name: compatibility-required-${{ matrix.rid }}-${{ needs.metadata.outputs.package-version }}
path: artifacts/native-engine-runtime-build/**/wpt-results/**
if-no-files-found: error
- name: Upload failed compatibility evidence
- if: failure()
+ if: failure() && matrix.rid != 'linux-arm64'
uses: actions/upload-artifact@v4
with:
name: compatibility-required-${{ matrix.rid }}-${{ needs.metadata.outputs.package-version }}
path: artifacts/native-engine-runtime-build/**/wpt-results/**
if-no-files-found: warn
- name: Upload candidate compatibility evidence
- if: always()
+ if: always() && matrix.rid != 'linux-arm64'
uses: actions/upload-artifact@v4
with:
name: compatibility-candidate-${{ matrix.rid }}-${{ needs.metadata.outputs.package-version }}
path: artifacts/native-engine-runtime-build/**/wpt-candidate-results/**
if-no-files-found: warn
- name: Upload verified RID package
+ if: matrix.rid != 'linux-arm64'
uses: actions/upload-artifact@v4
with:
name: native-${{ matrix.rid }}-${{ needs.metadata.outputs.package-version }}
- path: artifacts/nuget-packages/*.nupkg
+ path: |
+ artifacts/nuget-packages/*.nupkg
+ artifacts/native-engine-runtime-build/**/*-abi.json
+ if-no-files-found: error
+ - name: Upload ARM64 cross-build stage
+ if: matrix.rid == 'linux-arm64'
+ uses: actions/upload-artifact@v4
+ with:
+ name: cross-stage-linux-arm64-${{ needs.metadata.outputs.package-version }}
+ path: |
+ artifacts/native-engine-runtime-build/linux-arm64-*/**
+ artifacts/native-engine-v8/linux-arm64/v8/include/**
+ artifacts/native-engine-v8/linux-arm64/v8/out/arm64/ReleasePartitionAlloc/**
+ artifacts/native-engine-v8/linux-arm64/v8/LICENSE
+ artifacts/native-engine-v8/linux-arm64/v8/third_party/icu/LICENSE
+ artifacts/native-engine-v8/linux-arm64/v8/third_party/partition_alloc/src/**
+ if-no-files-found: error
+ retention-days: 3
+
+ linux-arm64-finalize:
+ name: Finalize and test linux-arm64
+ needs: [metadata, native]
+ runs-on: ubuntu-24.04-arm
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5
+ with:
+ global-json-file: global.json
+ - name: Download ARM64 cross-build stage
+ uses: actions/download-artifact@v4
+ with:
+ name: cross-stage-linux-arm64-${{ needs.metadata.outputs.package-version }}
+ path: artifacts
+ - name: Restore executable permissions
+ shell: bash
+ run: |
+ find artifacts/native-engine-runtime-build/linux-arm64-* -type f \
+ \( -name 'webscene_*' -o -name '*_tests' \) -exec chmod +x {} +
+ - name: Generate snapshot, test, and package natively
+ shell: bash
+ run: |
+ if [[ ! -e /workspace ]]; then
+ sudo ln -s "$GITHUB_WORKSPACE" /workspace
+ fi
+ if [[ "$(realpath /workspace)" != "$(realpath "$GITHUB_WORKSPACE")" ]]; then
+ echo "/workspace must resolve to the checked-out repository for deterministic paths." >&2
+ exit 1
+ fi
+ scripts/build-linux-native-runtime.sh \
+ --rid linux-arm64 \
+ --package-version '${{ needs.metadata.outputs.package-version }}' \
+ --output "$GITHUB_WORKSPACE/artifacts/nuget-packages" \
+ --stage finalize
+ native_path="$(find artifacts/native-engine-runtime-build -path '*/linux-arm64-*/libwebscene_native_engine.so' -print -quit)"
+ python3 scripts/verify-linux-native-abi.py "$native_path" \
+ --rid linux-arm64 \
+ --output "${native_path%/*}/linux-arm64-abi.json"
+ - name: Run ARM64 candidate compatibility discovery
+ continue-on-error: true
+ shell: bash
+ run: |
+ native_path="$(find artifacts/native-engine-runtime-build -path '*/package-smoke/runtimes/linux-arm64/native/libwebscene_native_engine.so' -print -quit)"
+ build_dir="${native_path%%/package-smoke/runtimes/*}"
+ dotnet run \
+ --project tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj \
+ -c Release --no-build -- \
+ --selection candidate \
+ --native-library "$native_path" \
+ --native-cache-directory "$build_dir/code-cache" \
+ --output "$build_dir/wpt-candidate-results"
+ - name: Upload ARM64 required compatibility evidence
+ if: success()
+ uses: actions/upload-artifact@v4
+ with:
+ name: compatibility-required-linux-arm64-${{ needs.metadata.outputs.package-version }}
+ path: artifacts/native-engine-runtime-build/**/wpt-results/**
+ if-no-files-found: error
+ - name: Upload ARM64 candidate compatibility evidence
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: compatibility-candidate-linux-arm64-${{ needs.metadata.outputs.package-version }}
+ path: artifacts/native-engine-runtime-build/**/wpt-candidate-results/**
+ if-no-files-found: warn
+ - name: Upload verified ARM64 package and ABI evidence
+ uses: actions/upload-artifact@v4
+ with:
+ name: native-linux-arm64-${{ needs.metadata.outputs.package-version }}
+ path: |
+ artifacts/nuget-packages/*.nupkg
+ artifacts/native-engine-runtime-build/**/*-abi.json
if-no-files-found: error
required-evidence:
name: Verify cross-RID required evidence
- needs: [metadata, native]
+ needs: [metadata, native, linux-arm64-finalize]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -468,6 +592,7 @@ jobs:
--selection required \
--expected-rid osx-arm64 \
--expected-rid osx-x64 \
+ --expected-rid linux-arm64 \
--expected-rid linux-x64 \
--expected-rid win-x64 \
--output artifacts/required-compatibility/cross-rid-summary.json
@@ -480,7 +605,7 @@ jobs:
package-set:
name: Verify release package set
- needs: [metadata, packages, native, required-evidence]
+ needs: [metadata, packages, native, linux-arm64-finalize, required-evidence]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -503,6 +628,7 @@ jobs:
--version '${{ needs.metadata.outputs.package-version }}' \
--native-rid osx-arm64 \
--native-rid osx-x64 \
+ --native-rid linux-arm64 \
--native-rid linux-x64 \
--native-rid win-x64 \
--output artifacts/nuget-packages/release-packages.json
@@ -523,7 +649,7 @@ jobs:
candidate-evidence:
name: Verify cross-RID candidate evidence
- needs: [metadata, native]
+ needs: [metadata, native, linux-arm64-finalize]
if: always() && needs.metadata.result == 'success'
continue-on-error: true
runs-on: ubuntu-latest
@@ -544,6 +670,7 @@ jobs:
--selection candidate \
--expected-rid osx-arm64 \
--expected-rid osx-x64 \
+ --expected-rid linux-arm64 \
--expected-rid linux-x64 \
--expected-rid win-x64 \
--output artifacts/candidate-compatibility/cross-rid-summary.json
@@ -568,6 +695,8 @@ jobs:
rid: osx-x64
- os: ubuntu-latest
rid: linux-x64
+ - os: ubuntu-24.04-arm
+ rid: linux-arm64
- os: windows-2022
rid: win-x64
runs-on: ${{ matrix.os }}
@@ -609,9 +738,96 @@ jobs:
--no-restore \
-p:WebScenePackageVersion='${{ needs.metadata.outputs.package-version }}'
+ linux-floor-smoke:
+ name: Linux floor ${{ matrix.distribution }} ${{ matrix.rid }}
+ needs: [metadata, package-set]
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: ubuntu-24.04
+ rid: linux-x64
+ platform: linux/amd64
+ distribution: ubuntu-18.04
+ image: ubuntu:18.04
+ - os: ubuntu-24.04-arm
+ rid: linux-arm64
+ platform: linux/arm64
+ distribution: ubuntu-18.04
+ image: ubuntu:18.04
+ - os: ubuntu-24.04
+ rid: linux-x64
+ platform: linux/amd64
+ distribution: ubi-8.9
+ image: registry.access.redhat.com/ubi8/ubi:8.9
+ - os: ubuntu-24.04-arm
+ rid: linux-arm64
+ platform: linux/arm64
+ distribution: ubi-8.9
+ image: registry.access.redhat.com/ubi8/ubi:8.9
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/download-artifact@v4
+ with:
+ name: release-${{ needs.metadata.outputs.package-version }}
+ path: artifacts/nuget-packages
+ - name: Extract native package
+ shell: bash
+ run: |
+ mkdir -p artifacts/linux-floor-smoke
+ unzip -q \
+ "artifacts/nuget-packages/WebScene.NativeEngine.Runtime.${{ matrix.rid }}.${{ needs.metadata.outputs.package-version }}.nupkg" \
+ -d artifacts/linux-floor-smoke
+ - name: Load native runtime on support floor
+ shell: bash
+ run: |
+ docker run --rm \
+ --platform '${{ matrix.platform }}' \
+ --volume "$GITHUB_WORKSPACE:/workspace:ro" \
+ --workdir /workspace \
+ '${{ matrix.image }}' \
+ env LD_PRELOAD="/workspace/artifacts/linux-floor-smoke/runtimes/${{ matrix.rid }}/native/libwebscene_native_engine.so" \
+ /bin/true
+
+ linux-compatibility-matrix:
+ name: Linux advisory ${{ matrix.distribution }} ${{ matrix.rid }}
+ needs: [metadata, package-set]
+ continue-on-error: true
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - { os: ubuntu-24.04, rid: linux-x64, platform: linux/amd64, distribution: debian-10, image: 'debian:10' }
+ - { os: ubuntu-24.04-arm, rid: linux-arm64, platform: linux/arm64, distribution: debian-10, image: 'debian:10' }
+ - { os: ubuntu-24.04, rid: linux-x64, platform: linux/amd64, distribution: fedora-38, image: 'fedora:38' }
+ - { os: ubuntu-24.04-arm, rid: linux-arm64, platform: linux/arm64, distribution: fedora-38, image: 'fedora:38' }
+ - { os: ubuntu-24.04, rid: linux-x64, platform: linux/amd64, distribution: opensuse-15.5, image: 'opensuse/leap:15.5' }
+ - { os: ubuntu-24.04-arm, rid: linux-arm64, platform: linux/arm64, distribution: opensuse-15.5, image: 'opensuse/leap:15.5' }
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/download-artifact@v4
+ with:
+ name: release-${{ needs.metadata.outputs.package-version }}
+ path: artifacts/nuget-packages
+ - name: Extract and load native runtime
+ shell: bash
+ run: |
+ mkdir -p artifacts/linux-advisory
+ unzip -q \
+ "artifacts/nuget-packages/WebScene.NativeEngine.Runtime.${{ matrix.rid }}.${{ needs.metadata.outputs.package-version }}.nupkg" \
+ -d artifacts/linux-advisory
+ docker run --rm \
+ --platform '${{ matrix.platform }}' \
+ --volume "$GITHUB_WORKSPACE:/workspace:ro" \
+ --workdir /workspace \
+ '${{ matrix.image }}' \
+ env LD_PRELOAD="/workspace/artifacts/linux-advisory/runtimes/${{ matrix.rid }}/native/libwebscene_native_engine.so" \
+ /bin/true
publish:
name: Publish to NuGet.org
- needs: [metadata, consumer, release-ci-gate]
+ needs: [metadata, consumer, linux-floor-smoke, release-ci-gate]
if: needs.metadata.outputs.publish == 'true'
runs-on: ubuntu-latest
environment: nuget.org
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 3fa3a481e..0bbab6487 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -4,7 +4,7 @@
$(PackageTags);webscene
$(PackageTags);web-ui
$(PackageTags);native-ui
- Service release of the 1.0.34 codebase. Adds native runtime packages for Apple silicon and Intel Macs with a minimum supported macOS version of 14. No product code changes.
+ Service release of the 1.0.34 codebase. Adds macOS 14 native runtimes for Apple silicon and Intel, plus reproducible glibc 2.27 Linux runtimes for x64 and ARM64.
README.md
diff --git a/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt b/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
index 07bcb8d7a..dbf4c719f 100644
--- a/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
+++ b/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
@@ -52,6 +52,8 @@ option(WEBSCENE_NATIVE_ENGINE_DENSE_LINK
"Dead-strip unused native code and expose only the WebScene C ABI" OFF)
option(WEBSCENE_NATIVE_ENGINE_THIN_LTO
"Enable ThinLTO for the WebScene native engine and its V8 monolith link" OFF)
+option(WEBSCENE_NATIVE_ENGINE_DEFER_TARGET_EXECUTION
+ "Build target executables without running snapshot generation or tests" OFF)
option(WEBSCENE_NATIVE_ENGINE_CERTIFICATION
"Include certification telemetry, diagnostic snapshots, and native profiling hooks" OFF)
option(WEBSCENE_NATIVE_ENGINE_BUILD_HTML_PARSER_BENCHMARK
@@ -177,12 +179,19 @@ if(WEBSCENE_NATIVE_ENGINE_HTML_PARSER STREQUAL "html5ever")
"${CMAKE_CURRENT_SOURCE_DIR}/native/html_parser/Cargo.toml")
set(WEBSCENE_HTML_PARSER_TARGET_DIR
"${CMAKE_CURRENT_BINARY_DIR}/html-parser-target")
+ set(WEBSCENE_HTML_PARSER_LIBRARY_DIR
+ "${WEBSCENE_HTML_PARSER_TARGET_DIR}/release")
+ if(DEFINED WEBSCENE_RUST_TARGET_TRIPLE
+ AND NOT WEBSCENE_RUST_TARGET_TRIPLE STREQUAL "")
+ set(WEBSCENE_HTML_PARSER_LIBRARY_DIR
+ "${WEBSCENE_HTML_PARSER_TARGET_DIR}/${WEBSCENE_RUST_TARGET_TRIPLE}/release")
+ endif()
if(MSVC)
set(WEBSCENE_HTML_PARSER_LIBRARY
- "${WEBSCENE_HTML_PARSER_TARGET_DIR}/release/webscene_html_parser.lib")
+ "${WEBSCENE_HTML_PARSER_LIBRARY_DIR}/webscene_html_parser.lib")
else()
set(WEBSCENE_HTML_PARSER_LIBRARY
- "${WEBSCENE_HTML_PARSER_TARGET_DIR}/release/libwebscene_html_parser.a")
+ "${WEBSCENE_HTML_PARSER_LIBRARY_DIR}/libwebscene_html_parser.a")
endif()
add_custom_command(
OUTPUT "${WEBSCENE_HTML_PARSER_LIBRARY}"
@@ -523,19 +532,24 @@ if(WEBSCENE_NATIVE_ENGINE_ENABLE_V8)
"${CMAKE_CURRENT_BINARY_DIR}/webscene_bootstrap_snapshot.bin")
set(WEBSCENE_V8_SNAPSHOT_METADATA
"${CMAKE_CURRENT_BINARY_DIR}/webscene_bootstrap_snapshot.meta")
- add_custom_command(
- OUTPUT "${WEBSCENE_V8_SNAPSHOT_BLOB}" "${WEBSCENE_V8_SNAPSHOT_METADATA}"
- COMMAND "$"
- "${WEBSCENE_V8_ICU_DATA}"
- "${WEBSCENE_V8_BOOTSTRAP_SOURCE}"
- "${WEBSCENE_V8_SNAPSHOT_BLOB}"
- "${WEBSCENE_V8_SNAPSHOT_METADATA}"
- DEPENDS webscene_v8_snapshot_builder "${WEBSCENE_V8_BOOTSTRAP_SOURCE}"
- COMMENT "Creating the WebScene V8 bootstrap snapshot"
- VERBATIM)
- add_custom_target(webscene_v8_bootstrap_snapshot ALL
- DEPENDS "${WEBSCENE_V8_SNAPSHOT_BLOB}" "${WEBSCENE_V8_SNAPSHOT_METADATA}")
- add_dependencies(webscene_native_engine webscene_v8_bootstrap_snapshot)
+ if(NOT WEBSCENE_NATIVE_ENGINE_DEFER_TARGET_EXECUTION)
+ add_custom_command(
+ OUTPUT "${WEBSCENE_V8_SNAPSHOT_BLOB}" "${WEBSCENE_V8_SNAPSHOT_METADATA}"
+ COMMAND "$"
+ "${WEBSCENE_V8_ICU_DATA}"
+ "${WEBSCENE_V8_BOOTSTRAP_SOURCE}"
+ "${WEBSCENE_V8_SNAPSHOT_BLOB}"
+ "${WEBSCENE_V8_SNAPSHOT_METADATA}"
+ DEPENDS webscene_v8_snapshot_builder "${WEBSCENE_V8_BOOTSTRAP_SOURCE}"
+ COMMENT "Creating the WebScene V8 bootstrap snapshot"
+ VERBATIM)
+ add_custom_target(webscene_v8_bootstrap_snapshot ALL
+ DEPENDS "${WEBSCENE_V8_SNAPSHOT_BLOB}" "${WEBSCENE_V8_SNAPSHOT_METADATA}")
+ add_dependencies(webscene_native_engine webscene_v8_bootstrap_snapshot)
+ else()
+ message(STATUS
+ "WebScene native engine: target execution deferred for cross-build finalization")
+ endif()
target_compile_definitions(webscene_native_engine PRIVATE
WEBSCENE_V8_BOOTSTRAP_SNAPSHOT=1
WEBSCENE_V8_SNAPSHOT_FILENAME="webscene_bootstrap_snapshot.bin"
diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_support.inc b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_support.inc
index d66d2fcf7..53b445d48 100644
--- a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_support.inc
+++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_support.inc
@@ -1443,6 +1443,10 @@ void install_navigator(
constexpr auto platform = "Win32";
constexpr auto user_agent_platform = "Windows NT 10.0; Win64; x64";
constexpr auto client_platform = "Windows";
+#elif defined(__aarch64__)
+ constexpr auto platform = "Linux aarch64";
+ constexpr auto user_agent_platform = "X11; Linux aarch64";
+ constexpr auto client_platform = "Linux";
#else
constexpr auto platform = "Linux x86_64";
constexpr auto user_agent_platform = "X11; Linux x86_64";
diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_input_tests.inc b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_input_tests.inc
index c53727dc9..17538145e 100644
--- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_input_tests.inc
+++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_input_tests.inc
@@ -1036,6 +1036,10 @@ void test_navigator_platform_and_wheel_modifiers(webscene_engine* engine)
require(
navigator_result.find(R"("platform":"Win32")") != std::string::npos,
"native navigator did not expose Windows platform identity: " + navigator_result);
+#elif defined(__aarch64__)
+ require(
+ navigator_result.find(R"("platform":"Linux aarch64")") != std::string::npos,
+ "native navigator did not expose Linux ARM64 platform identity: " + navigator_result);
#else
require(
navigator_result.find(R"("platform":"Linux x86_64")") != std::string::npos,
diff --git a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
new file mode 100644
index 000000000..5809cb90a
--- /dev/null
+++ b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
@@ -0,0 +1,40 @@
+# syntax=docker/dockerfile:1
+
+FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64@sha256:2962cae8ca49b18fb533504513c89308927ed3721372a0cc58630c350a2936b8 AS x64-sysroot
+FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm64@sha256:619e1c013b88c504d34c8e064e0860313cebeb3ee1fc6e2e838406744c9857a8 AS arm64-sysroot
+FROM mcr.microsoft.com/dotnet/sdk:10.0.302-noble-amd64@sha256:7a91ccecc26d71bf7688c627a6b5eae2e27bb2cd1e37e8abe738348904245692 AS dotnet-sdk
+FROM x64-sysroot
+
+ARG RUST_VERSION=1.90.0
+ARG RUST_ARCHIVE_SHA256=bff8974f2d3ee6c0e6ac926b533f65bbdd3697d2c2b925bdae5f45b9eed10a67
+ARG RUST_ARM64_STD_SHA256=4952abb7d9d3ed7cea4f7ea44dcb23dc67631fae4ac44a5f059b90a4b5e9223f
+ARG DEPOT_TOOLS_COMMIT=ca054941f756b50e1a3d83727270d879bec1f331
+
+ENV DEBIAN_FRONTEND=noninteractive \
+ DOTNET_ROOT=/usr/share/dotnet \
+ DOTNET_CLI_TELEMETRY_OPTOUT=1 \
+ DOTNET_NOLOGO=1 \
+ NUGET_XMLDOC_MODE=skip \
+ DEPOT_TOOLS_UPDATE=0 \
+ PATH=/opt/depot_tools:/opt/rust/bin:/usr/share/dotnet:${PATH}
+
+COPY --from=arm64-sysroot /crossrootfs/arm64 /crossrootfs/arm64
+COPY --from=dotnet-sdk /usr/share/dotnet /usr/share/dotnet
+
+RUN set -eux; \
+ curl -fsSLO "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-x86_64-unknown-linux-gnu.tar.xz"; \
+ echo "${RUST_ARCHIVE_SHA256} rust-${RUST_VERSION}-x86_64-unknown-linux-gnu.tar.xz" | sha256sum -c -; \
+ tar -xf "rust-${RUST_VERSION}-x86_64-unknown-linux-gnu.tar.xz"; \
+ "rust-${RUST_VERSION}-x86_64-unknown-linux-gnu/install.sh" --prefix=/opt/rust --without=rust-docs; \
+ curl -fsSLO "https://static.rust-lang.org/dist/rust-std-${RUST_VERSION}-aarch64-unknown-linux-gnu.tar.xz"; \
+ echo "${RUST_ARM64_STD_SHA256} rust-std-${RUST_VERSION}-aarch64-unknown-linux-gnu.tar.xz" | sha256sum -c -; \
+ tar -xf "rust-std-${RUST_VERSION}-aarch64-unknown-linux-gnu.tar.xz"; \
+ "rust-std-${RUST_VERSION}-aarch64-unknown-linux-gnu/install.sh" --prefix=/opt/rust; \
+ rm -rf rust-*.tar.xz rust-*unknown-linux-gnu
+
+RUN git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git /opt/depot_tools \
+ && git -C /opt/depot_tools checkout --detach "$DEPOT_TOOLS_COMMIT" \
+ && test "$(git -C /opt/depot_tools rev-parse HEAD)" = "$DEPOT_TOOLS_COMMIT"
+
+COPY linux-build-lock.json /opt/webscene/linux-build-lock.json
+WORKDIR /workspace
diff --git a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-x64 b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-x64
deleted file mode 100644
index 7ef866999..000000000
--- a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-x64
+++ /dev/null
@@ -1,58 +0,0 @@
-# syntax=docker/dockerfile:1
-
-FROM mcr.microsoft.com/dotnet/sdk:10.0.302-noble-amd64 AS dotnet-sdk
-FROM mcr.microsoft.com/dotnet/runtime:8.0-jammy-amd64 AS dotnet8-runtime
-
-# Build the distributable runtime against Ubuntu 22.04's glibc 2.35 rather
-# than the newer libc provided by the current GitHub-hosted runner image.
-FROM ubuntu:22.04
-
-ENV DEBIAN_FRONTEND=noninteractive \
- DOTNET_ROOT=/usr/share/dotnet \
- PATH=/usr/share/dotnet:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
- CC=gcc-12 \
- CXX=g++-12 \
- DOTNET_CLI_TELEMETRY_OPTOUT=1 \
- DOTNET_NOLOGO=1 \
- NUGET_XMLDOC_MODE=skip
-
-RUN apt-get update \
- && apt-get install --yes --no-install-recommends \
- build-essential \
- ca-certificates \
- clang \
- cmake \
- curl \
- fonts-dejavu-core \
- gdb \
- gcc-12 \
- g++-12 \
- git \
- libfontconfig1 \
- libglib2.0-dev \
- libssl-dev \
- lld \
- ninja-build \
- pkg-config \
- python3 \
- unzip \
- xz-utils \
- zlib1g-dev \
- && rm -rf /var/lib/apt/lists/*
-
-# html5ever is compiled into the existing WebScene DSO. Keep the Rust compiler
-# pinned independently from Ubuntu's older distro package so native release
-# builds resolve the same Cargo.lock on every host. Install it in /opt because
-# CI deliberately runs this image as the host's non-root UID.
-ENV RUSTUP_HOME=/opt/rustup \
- CARGO_HOME=/opt/cargo \
- PATH=/opt/cargo/bin:${PATH}
-RUN mkdir -p "$RUSTUP_HOME" "$CARGO_HOME" \
- && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
- | sh -s -- -y --profile minimal --default-toolchain 1.90.0 \
- && chmod -R a+rX "$RUSTUP_HOME" "$CARGO_HOME"
-
-COPY --from=dotnet-sdk /usr/share/dotnet /usr/share/dotnet
-COPY --from=dotnet8-runtime /usr/share/dotnet/shared/Microsoft.NETCore.App /usr/share/dotnet/shared/Microsoft.NETCore.App
-
-WORKDIR /workspace
diff --git a/packaging/WebScene.NativeEngine.Runtime/README.md b/packaging/WebScene.NativeEngine.Runtime/README.md
index b1be9b664..867b03c0c 100644
--- a/packaging/WebScene.NativeEngine.Runtime/README.md
+++ b/packaging/WebScene.NativeEngine.Runtime/README.md
@@ -41,6 +41,7 @@ Install the package matching the application's deployment RID:
+
```
@@ -49,7 +50,25 @@ Install the package matching the application's deployment RID:
| macOS on Apple silicon | `osx-arm64` | [`WebScene.NativeEngine.Runtime.osx-arm64`](https://www.nuget.org/packages/WebScene.NativeEngine.Runtime.osx-arm64/) |
| macOS on Intel | `osx-x64` | [`WebScene.NativeEngine.Runtime.osx-x64`](https://www.nuget.org/packages/WebScene.NativeEngine.Runtime.osx-x64/) |
| Linux x64 | `linux-x64` | [`WebScene.NativeEngine.Runtime.linux-x64`](https://www.nuget.org/packages/WebScene.NativeEngine.Runtime.linux-x64/) |
+| Linux ARM64 | `linux-arm64` | [`WebScene.NativeEngine.Runtime.linux-arm64`](https://www.nuget.org/packages/WebScene.NativeEngine.Runtime.linux-arm64/) |
| Windows x64 | `win-x64` | [`WebScene.NativeEngine.Runtime.win-x64`](https://www.nuget.org/packages/WebScene.NativeEngine.Runtime.win-x64/) |
Additional RIDs listed by the package definition are reserved until their release
lanes are enabled.
+
+## Reproducible Linux builds
+
+Linux packages use the immutable inputs recorded in `linux-build-lock.json` and
+the .NET-style x64 cross-builder in `Dockerfile.linux-glibc`. Build either glibc
+RID locally with the same entry point used by CI:
+
+```bash
+scripts/build-linux-native-runtime.sh --rid linux-x64 --package-version VERSION
+scripts/build-linux-native-runtime.sh --rid linux-arm64 --package-version VERSION
+```
+
+The ARM64 command creates a cross-build stage. CI transfers that stage to a
+native ARM64 runner and calls the same command with `--stage finalize` to create
+the V8 bootstrap snapshot, execute tests, and pack the NuGet package. Published
+Linux binaries must pass `verify-linux-native-abi.py`, including the glibc 2.27,
+GLIBCXX, CXXABI, dependency, architecture, RPATH, and exported-ABI gates.
diff --git a/packaging/WebScene.NativeEngine.Runtime/WebScene.NativeEngine.Runtime.csproj b/packaging/WebScene.NativeEngine.Runtime/WebScene.NativeEngine.Runtime.csproj
index bf636d0b0..080f41bea 100644
--- a/packaging/WebScene.NativeEngine.Runtime/WebScene.NativeEngine.Runtime.csproj
+++ b/packaging/WebScene.NativeEngine.Runtime/WebScene.NativeEngine.Runtime.csproj
@@ -10,6 +10,7 @@
macOS on Apple silicon
macOS on Intel
Linux x64
+ Linux ARM64
Windows x64
WebScene Native Engine Runtime for $(WebSceneNativeEnginePlatformName)
Native WebScene V8, DOM, CSS, layout, Canvas, SVG, and immutable-scene runtime for $(WebSceneNativeEnginePlatformName) ($(WebSceneNativeEngineRid)), built to run trusted web-authored UI without a WebView or embedded browser.
@@ -34,6 +35,9 @@
generated
bootstrap
Release
+
+
+
osx-arm64;osx-x64;linux-arm64;linux-x64;win-arm64;win-x64
libwebscene_native_engine.dylib
libwebscene_native_engine.so
@@ -199,7 +203,7 @@
@(_WebSceneNativeEngineSnapshotMetadataHash->'%(FileHash)')
&2
+}
+
+while (($# > 0)); do
+ case "$1" in
+ --rid) rid="${2:-}"; shift 2 ;;
+ --package-version) package_version="${2:-}"; shift 2 ;;
+ --output) output_dir="${2:-}"; shift 2 ;;
+ --stage) stage="${2:-}"; shift 2 ;;
+ --builder-image) builder_image="${2:-}"; shift 2 ;;
+ -h|--help) usage; exit 0 ;;
+ *) echo "Unknown option: $1" >&2; usage; exit 1 ;;
+ esac
+done
+
+case "$rid" in
+ linux-x64|linux-arm64) ;;
+ *) usage; exit 1 ;;
+esac
+case "$stage" in
+ build|finalize) ;;
+ *) usage; exit 1 ;;
+esac
+
+IFS='|' read -r builder_identity target_triple rust_target_triple sysroot max_glibc max_glibcxx max_cxxabi <<< "$(python3 - "$lock_file" "$rid" <<'PY'
+import json, pathlib, sys
+lock = json.loads(pathlib.Path(sys.argv[1]).read_text())
+target = lock["sysroots"][sys.argv[2]]
+print("|".join((
+ lock["builderIdentity"], target["targetTriple"], target["rustTargetTriple"], target["path"],
+ lock["compatibility"]["maximumGlibc"],
+ lock["compatibility"]["maximumGlibcxx"],
+ lock["compatibility"]["maximumCxxabi"],
+)))
+PY
+)"
+cargo_target_key="$(printf '%s' "$rust_target_triple" | tr '[:lower:]-' '[:upper:]_')"
+cargo_linker_name="CARGO_TARGET_${cargo_target_key}_LINKER"
+cargo_rustflags_name="CARGO_TARGET_${cargo_target_key}_RUSTFLAGS"
+source_date_epoch="$(git -C "$repo_root" show -s --format=%ct HEAD)"
+
+if [[ "$stage" == finalize ]]; then
+ "$repo_root/scripts/build-native-engine-runtime.sh" \
+ --rid "$rid" \
+ --target-triple "$target_triple" \
+ --rust-target-triple "$rust_target_triple" \
+ --sysroot "$sysroot" \
+ --builder-identity "$builder_identity" \
+ --glibc-baseline "$max_glibc" \
+ --package-version "$package_version" \
+ --partition-alloc \
+ --upstream-v8 \
+ --output "$output_dir" \
+ --finalize-only
+ exit 0
+fi
+
+if [[ -z "$builder_image" ]]; then
+ builder_image="webscene-linux-builder:$builder_identity"
+ docker build \
+ --platform linux/amd64 \
+ --file "$dockerfile" \
+ --tag "$builder_image" \
+ "$repo_root/packaging/WebScene.NativeEngine.Runtime"
+elif [[ "$builder_image" != *@sha256:* ]]; then
+ echo "A prebuilt builder image must be pinned by digest: $builder_image" >&2
+ exit 1
+fi
+
+common_args=(
+ --rid "$rid"
+ --target-triple "$target_triple"
+ --rust-target-triple "$rust_target_triple"
+ --sysroot "$sysroot"
+ --builder-identity "$builder_identity"
+ --glibc-baseline "$max_glibc"
+ --package-version "$package_version"
+ --partition-alloc
+ --upstream-v8
+ --output /workspace/artifacts/nuget-packages
+)
+case "$rid" in
+ linux-x64) v8_cpu=x64 ;;
+ linux-arm64) v8_cpu=arm64 ;;
+esac
+v8_root_host="$repo_root/artifacts/native-engine-v8/$rid/v8"
+if [[ -f "$v8_root_host/out/$v8_cpu/ReleasePartitionAlloc/obj/libv8_monolith.a" ]]; then
+ common_args+=(--v8-root "/workspace/artifacts/native-engine-v8/$rid/v8")
+fi
+if [[ "$stage" == build && "$rid" == linux-arm64 ]]; then
+ common_args+=(--defer-target-execution)
+fi
+
+docker run --rm \
+ --platform linux/amd64 \
+ --user "$(id -u):$(id -g)" \
+ --env HOME=/tmp/webscene-home \
+ --env DOTNET_CLI_HOME=/tmp/webscene-home \
+ --env CARGO_HOME=/tmp/webscene-home/.cargo \
+ --env NUGET_PACKAGES=/tmp/webscene-home/.nuget/packages \
+ --env "SOURCE_DATE_EPOCH=$source_date_epoch" \
+ --env "CARGO_BUILD_TARGET=$rust_target_triple" \
+ --env "$cargo_linker_name=clang" \
+ --env "$cargo_rustflags_name=-C link-arg=--target=$target_triple -C link-arg=--sysroot=$sysroot --remap-path-prefix=/workspace=." \
+ --volume "$repo_root:/workspace" \
+ --workdir /workspace \
+ "$builder_image" \
+ scripts/build-native-engine-runtime.sh "${common_args[@]}"
+
+native_path="$(find "$repo_root/artifacts/native-engine-runtime-build" -path "*/$rid*/libwebscene_native_engine.so" -print -quit)"
+if [[ -z "$native_path" ]]; then
+ echo "Unable to locate the $rid native library for ABI verification." >&2
+ exit 1
+fi
+python3 "$repo_root/scripts/verify-linux-native-abi.py" "$native_path" \
+ --rid "$rid" \
+ --max-glibc "$max_glibc" \
+ --max-glibcxx "$max_glibcxx" \
+ --max-cxxabi "$max_cxxabi" \
+ --output "${native_path%/*}/$rid-abi.json"
diff --git a/scripts/build-native-engine-runtime-linux-container.sh b/scripts/build-native-engine-runtime-linux-container.sh
deleted file mode 100755
index 7976a7a6f..000000000
--- a/scripts/build-native-engine-runtime-linux-container.sh
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env bash
-set -uo pipefail
-
-repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-v8_root="$repo_root/artifacts/native-engine-v8/linux-x64/v8"
-thin_lto=false
-disable_wasm=false
-partition_alloc=false
-html_parser=legacy
-expect_html_parser_value=false
-for argument in "$@"; do
- if [[ "$expect_html_parser_value" == true ]]; then
- html_parser="$argument"
- expect_html_parser_value=false
- continue
- fi
- case "$argument" in
- --thin-lto) thin_lto=true ;;
- --disable-wasm) disable_wasm=true ;;
- --partition-alloc) partition_alloc=true ;;
- --html-parser) expect_html_parser_value=true ;;
- esac
-done
-build_variant=
-v8_configuration=Release
-if [[ "$thin_lto" == true ]]; then
- build_variant+=-thinlto-llvm
- v8_configuration=ReleaseThinLto
-fi
-if [[ "$disable_wasm" == true ]]; then
- build_variant+=-no-wasm
- v8_configuration+=NoWasm
-fi
-if [[ "$partition_alloc" == true ]]; then
- build_variant+=-partitionalloc
- v8_configuration+=PartitionAlloc
-fi
-build_dir="$repo_root/artifacts/native-engine-runtime-build/linux-x64$build_variant"
-if [[ "$html_parser" == html5ever ]]; then
- build_dir="$repo_root/artifacts/native-engine-runtime-build/linux-x64-html5ever$build_variant"
-fi
-
-set +e
-"$repo_root/scripts/build-native-engine-runtime.sh" "$@"
-package_status=$?
-
-native_test_status=0
-icu_data="$v8_root/out/x64/$v8_configuration/icudtl.dat"
-if [[ -f "$icu_data" && -d "$build_dir" ]]; then
- cmake -E copy_if_different "$icu_data" "$build_dir/icudtl.dat"
- ctest --test-dir "$build_dir" -C Release --output-on-failure
- native_test_status=$?
-
- if ((native_test_status != 0)) && [[ -x "$build_dir/webscene_native_engine_tests" ]]; then
- gdb \
- --batch \
- -ex "set pagination off" \
- -ex run \
- -ex "thread apply all bt" \
- --args "$build_dir/webscene_native_engine_tests" || true
- fi
-fi
-set -e
-
-if ((package_status != 0)); then
- exit "$package_status"
-fi
-exit "$native_test_status"
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 5e3fadd72..b021e1101 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -19,9 +19,17 @@ upstream_v8=false
disable_wasm=false
partition_alloc=false
cmake_build_type=Release
+target_triple=
+rust_target_triple=
+sysroot=
+builder_identity=
+glibc_baseline=
+depot_tools_commit=ca054941f756b50e1a3d83727270d879bec1f331
+defer_target_execution=false
+finalize_only=false
usage() {
- echo "Usage: $0 --rid osx-arm64|osx-x64|linux-arm64|linux-x64 [--output DIR] [--package-version VERSION] [--v8-root DIR] [--v8-output-root DIR] [--v8-workspace DIR] [--v8-revision REVISION] [--html-parser legacy|html5ever] [--css-parser legacy|cssparser] [--selector-parser legacy|servo] [--dom-bindings legacy|generated] [--v8-snapshot none|bootstrap] [--cmake-build-type Release|RelWithDebInfo] [--upstream-v8] [--thin-lto] [--disable-wasm] [--partition-alloc]" >&2
+ echo "Usage: $0 --rid osx-arm64|osx-x64|linux-arm64|linux-x64 [--output DIR] [--package-version VERSION] [--v8-root DIR] [--v8-output-root DIR] [--v8-workspace DIR] [--v8-revision REVISION] [--target-triple TRIPLE] [--rust-target-triple TRIPLE] [--sysroot DIR] [--builder-identity ID] [--glibc-baseline VERSION] [--depot-tools-commit SHA] [--defer-target-execution|--finalize-only] [--html-parser legacy|html5ever] [--css-parser legacy|cssparser] [--selector-parser legacy|servo] [--dom-bindings legacy|generated] [--v8-snapshot none|bootstrap] [--cmake-build-type Release|RelWithDebInfo] [--upstream-v8] [--thin-lto] [--disable-wasm] [--partition-alloc]" >&2
}
while (($# > 0)); do
@@ -33,6 +41,14 @@ while (($# > 0)); do
--v8-output-root) v8_output_root="${2:-}"; shift 2 ;;
--v8-workspace) v8_workspace="${2:-}"; shift 2 ;;
--v8-revision) v8_revision="${2:-}"; shift 2 ;;
+ --target-triple) target_triple="${2:-}"; shift 2 ;;
+ --rust-target-triple) rust_target_triple="${2:-}"; shift 2 ;;
+ --sysroot) sysroot="${2:-}"; shift 2 ;;
+ --builder-identity) builder_identity="${2:-}"; shift 2 ;;
+ --glibc-baseline) glibc_baseline="${2:-}"; shift 2 ;;
+ --depot-tools-commit) depot_tools_commit="${2:-}"; shift 2 ;;
+ --defer-target-execution) defer_target_execution=true; shift ;;
+ --finalize-only) finalize_only=true; shift ;;
--html-parser) html_parser="${2:-}"; shift 2 ;;
--css-parser) css_parser="${2:-}"; shift 2 ;;
--selector-parser) selector_parser="${2:-}"; shift 2 ;;
@@ -123,10 +139,34 @@ if [[ -z "$package_version" ]]; then
exit 1
fi
-if [[ "$(uname -s)" != "$expected_kernel" || "$(uname -m)" != "$expected_machine" ]]; then
+if [[ "$expected_kernel" == Darwin \
+ && ( "$(uname -s)" != "$expected_kernel" || "$(uname -m)" != "$expected_machine" ) ]]; then
echo "RID '$rid' must be built natively on $expected_kernel/$expected_machine; current host is $(uname -s)/$(uname -m)." >&2
exit 1
fi
+if [[ "$expected_kernel" == Linux ]]; then
+ case "$rid:$target_triple" in
+ linux-x64:x86_64-linux-gnu|linux-arm64:aarch64-linux-gnu) ;;
+ *) echo "RID '$rid' requires its locked Linux target triple, not '$target_triple'." >&2; exit 1 ;;
+ esac
+ case "$rid:$rust_target_triple" in
+ linux-x64:x86_64-unknown-linux-gnu|linux-arm64:aarch64-unknown-linux-gnu) ;;
+ *) echo "RID '$rid' requires its locked Rust target triple, not '$rust_target_triple'." >&2; exit 1 ;;
+ esac
+ if [[ "$finalize_only" == false && ! -d "$sysroot" ]]; then
+ echo "Linux cross-build sysroot is missing: $sysroot" >&2
+ exit 1
+ fi
+ if [[ -z "$builder_identity" || -z "$glibc_baseline" ]]; then
+ echo "Linux release builds require --builder-identity and --glibc-baseline." >&2
+ exit 1
+ fi
+fi
+
+if [[ "$finalize_only" == true && -z "$v8_root" ]]; then
+ v8_workspace="${v8_workspace:-$repo_root/artifacts/native-engine-v8/$rid}"
+ v8_root="$v8_workspace/v8"
+fi
if [[ -z "$v8_root" ]]; then
v8_workspace="${v8_workspace:-$repo_root/artifacts/native-engine-v8/$rid}"
@@ -134,7 +174,10 @@ if [[ -z "$v8_root" ]]; then
v8_root="$v8_workspace/v8"
mkdir -p "$v8_workspace"
- if [[ ! -d "$depot_tools/.git" ]]; then
+ if [[ ! -d "$depot_tools/.git" && -d /opt/depot_tools/.git ]]; then
+ git clone --no-checkout /opt/depot_tools "$depot_tools"
+ git -C "$depot_tools" checkout --detach "$depot_tools_commit"
+ elif [[ ! -d "$depot_tools/.git" ]]; then
clone_attempt=1
while ! git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git "$depot_tools"; do
if ((clone_attempt >= 3)); then
@@ -146,6 +189,10 @@ if [[ -z "$v8_root" ]]; then
clone_attempt=$((clone_attempt + 1))
done
fi
+ if [[ "$(git -C "$depot_tools" rev-parse HEAD)" != "$depot_tools_commit" ]]; then
+ git -C "$depot_tools" fetch origin "$depot_tools_commit"
+ git -C "$depot_tools" checkout --detach "$depot_tools_commit"
+ fi
export PATH="$depot_tools:$PATH"
if [[ ! -f "$depot_tools/python3_bin_reldir.txt" ]]; then
"$depot_tools/ensure_bootstrap"
@@ -201,7 +248,7 @@ if [[ -z "$v8_root" ]]; then
# against that image's libstdc++ and glibc 2.35 instead.
# Keep V8's bundled LLD for its host tools; the reviewed build patch above
# disables only CREL emission so Jammy can consume the archive.
- gn_args+=" use_lld=true use_sysroot=false v8_monolithic_for_shared_library=true"
+ gn_args+=" use_lld=true use_sysroot=true target_sysroot=\"$sysroot\" v8_monolithic_for_shared_library=true"
fi
if [[ "$partition_alloc" == true \
&& ( "$expected_kernel" == Linux || "$expected_kernel" == Darwin ) ]]; then
@@ -279,6 +326,12 @@ if [[ "$expected_kernel" == Linux ]] \
echo "The V8 SDK at '$v8_root' was not built with the required patched LLD configuration." >&2
exit 1
fi
+if [[ "$expected_kernel" == Linux ]] \
+ && { ! grep -Eq '^use_sysroot *= *true$' "$v8_args" \
+ || ! grep -Fq "target_sysroot = \"$sysroot\"" "$v8_args"; }; then
+ echo "The V8 SDK at '$v8_root' was not built against the locked target sysroot." >&2
+ exit 1
+fi
if [[ "$expected_kernel" == Linux ]] \
&& ! grep -Eq '^v8_monolithic_for_shared_library *= *true$' "$v8_args"; then
echo "The V8 SDK at '$v8_root' is not safe to link into a shared library." >&2
@@ -313,6 +366,7 @@ cmake_args=(
-DWEBSCENE_NATIVE_ENGINE_V8_SNAPSHOT="$v8_snapshot"
-DWEBSCENE_V8_ROOT="$v8_root"
-DWEBSCENE_V8_OUTPUT_ROOT="$v8_output_root"
+ -DWEBSCENE_NATIVE_ENGINE_DEFER_TARGET_EXECUTION="$defer_target_execution"
)
macos_deployment_target=14.0
if [[ "$expected_kernel" == Darwin ]]; then
@@ -360,21 +414,47 @@ elif [[ "$expected_kernel" == Linux ]]; then
exit 1
fi
cmake_args+=(
- -DCMAKE_CXX_COMPILER="$linux_cxx"
+ -DCMAKE_TOOLCHAIN_FILE="$repo_root/scripts/linux-glibc-toolchain.cmake"
+ -DCMAKE_SYSROOT="$sysroot"
+ -DWEBSCENE_LINUX_TARGET_TRIPLE="$target_triple"
+ -DWEBSCENE_RUST_TARGET_TRIPLE="$rust_target_triple"
+ "-DCMAKE_C_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
+ "-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld
- -DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld
+ "-DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld -Wl,--build-id=sha1"
)
fi
-cmake "${cmake_args[@]}"
-cmake --build "$build_dir" --config "$cmake_build_type" --parallel
-cmake -E copy_if_different "$icu_data" "$build_dir/icudtl.dat"
-ctest --test-dir "$build_dir" -C "$cmake_build_type" --output-on-failure
+if [[ "$finalize_only" == false ]]; then
+ cmake "${cmake_args[@]}"
+ cmake --build "$build_dir" --config "$cmake_build_type" --parallel
+ cmake -E copy_if_different "$icu_data" "$build_dir/icudtl.dat"
+fi
+
+if [[ "$finalize_only" == true ]]; then
+ snapshot_builder="$build_dir/webscene_v8_snapshot_builder"
+ if [[ ! -x "$snapshot_builder" ]]; then
+ echo "Cross-build output is missing its target snapshot builder: $snapshot_builder" >&2
+ exit 1
+ fi
+ "$snapshot_builder" \
+ "$icu_data" \
+ "$build_dir/webscene_v8_bootstrap.js" \
+ "$build_dir/webscene_bootstrap_snapshot.bin" \
+ "$build_dir/webscene_bootstrap_snapshot.meta"
+fi
+if [[ "$defer_target_execution" == false || "$finalize_only" == true ]]; then
+ ctest --test-dir "$build_dir" -C "$cmake_build_type" --output-on-failure
+fi
native_path="$build_dir/$native_name"
if [[ ! -f "$native_path" ]]; then
echo "Native engine build did not produce '$native_path'." >&2
exit 1
fi
+if [[ "$defer_target_execution" == true && "$finalize_only" == false ]]; then
+ echo "Cross-build staged for native finalization: $build_dir"
+ exit 0
+fi
if [[ "$expected_kernel" == Darwin ]]; then
actual_macos_deployment_target="$(
xcrun vtool -show-build "$native_path" |
@@ -438,6 +518,9 @@ pack_args=(
"-p:WebSceneNativeEngineDomBindings=$dom_bindings"
"-p:WebSceneNativeEngineV8Snapshot=$v8_snapshot"
"-p:WebSceneNativeEngineConfiguration=$cmake_build_type"
+ "-p:WebSceneNativeEngineBuilderIdentity=$builder_identity"
+ "-p:WebSceneNativeEngineTargetTriple=$target_triple"
+ "-p:WebSceneNativeEngineGlibcBaseline=$glibc_baseline"
)
if [[ "$v8_snapshot" == bootstrap ]]; then
pack_args+=(
diff --git a/scripts/linux-glibc-toolchain.cmake b/scripts/linux-glibc-toolchain.cmake
new file mode 100644
index 000000000..9e1d4d5b1
--- /dev/null
+++ b/scripts/linux-glibc-toolchain.cmake
@@ -0,0 +1,27 @@
+set(CMAKE_SYSTEM_NAME Linux)
+
+if(NOT DEFINED WEBSCENE_LINUX_TARGET_TRIPLE)
+ message(FATAL_ERROR "WEBSCENE_LINUX_TARGET_TRIPLE is required")
+endif()
+if(NOT DEFINED CMAKE_SYSROOT OR CMAKE_SYSROOT STREQUAL "")
+ message(FATAL_ERROR "CMAKE_SYSROOT is required")
+endif()
+
+if(WEBSCENE_LINUX_TARGET_TRIPLE STREQUAL "x86_64-linux-gnu")
+ set(CMAKE_SYSTEM_PROCESSOR x86_64)
+elseif(WEBSCENE_LINUX_TARGET_TRIPLE STREQUAL "aarch64-linux-gnu")
+ set(CMAKE_SYSTEM_PROCESSOR aarch64)
+else()
+ message(FATAL_ERROR "Unsupported Linux target triple: ${WEBSCENE_LINUX_TARGET_TRIPLE}")
+endif()
+
+set(CMAKE_C_COMPILER clang)
+set(CMAKE_CXX_COMPILER clang++)
+set(CMAKE_C_COMPILER_TARGET "${WEBSCENE_LINUX_TARGET_TRIPLE}")
+set(CMAKE_CXX_COMPILER_TARGET "${WEBSCENE_LINUX_TARGET_TRIPLE}")
+set(CMAKE_FIND_ROOT_PATH "${CMAKE_SYSROOT}")
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
new file mode 100644
index 000000000..e9df757a2
--- /dev/null
+++ b/scripts/tests/test_linux_build_policy.py
@@ -0,0 +1,49 @@
+from __future__ import annotations
+
+import json
+import pathlib
+import re
+import unittest
+
+
+ROOT = pathlib.Path(__file__).resolve().parents[2]
+PACKAGING = ROOT / "packaging" / "WebScene.NativeEngine.Runtime"
+
+
+class LinuxBuildPolicyTests(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls) -> None:
+ cls.lock = json.loads((PACKAGING / "linux-build-lock.json").read_text())
+ cls.dockerfile = (PACKAGING / "Dockerfile.linux-glibc").read_text()
+ cls.workflow = (ROOT / ".github/workflows/native-runtime-packages.yml").read_text()
+
+ def test_all_container_inputs_are_digest_pinned(self) -> None:
+ from_lines = re.findall(r"^FROM\s+(\S+)", self.dockerfile, re.MULTILINE)
+ external = [value for value in from_lines if value not in {"x64-sysroot"}]
+ self.assertTrue(external)
+ self.assertTrue(all("@sha256:" in value for value in external), external)
+ self.assertNotIn("apt-get", self.dockerfile)
+
+ def test_lock_and_dockerfile_are_synchronized(self) -> None:
+ expected = [self.lock["dotnetSdk"], *self.lock["sysroots"].values()]
+ for item in expected:
+ image = item.get("image", item.get("sourceImage"))
+ self.assertIsNotNone(image)
+ self.assertIn(f'{image}@{item["digest"]}', self.dockerfile)
+ toolchain = self.lock["toolchain"]
+ for value in (
+ toolchain["rust"], toolchain["rustArchiveSha256"],
+ toolchain["rustArm64StdSha256"], toolchain["depotToolsCommit"],
+ ):
+ self.assertIn(value, self.dockerfile)
+
+ def test_release_matrix_contains_both_glibc_rids(self) -> None:
+ for rid in ("linux-x64", "linux-arm64"):
+ self.assertIn(f"rid: {rid}", self.workflow)
+ self.assertIn(f"--expected-rid {rid}", self.workflow)
+ self.assertIn(f"--native-rid {rid}", self.workflow)
+ self.assertIn("github.ref_type != 'tag'", self.workflow)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/scripts/tests/test_verify_cross_rid_compatibility.py b/scripts/tests/test_verify_cross_rid_compatibility.py
index d25df68c7..f907f8c41 100644
--- a/scripts/tests/test_verify_cross_rid_compatibility.py
+++ b/scripts/tests/test_verify_cross_rid_compatibility.py
@@ -11,7 +11,7 @@
REPOSITORY_ROOT = pathlib.Path(__file__).resolve().parents[2]
VERIFIER = REPOSITORY_ROOT / "scripts" / "verify-cross-rid-compatibility.py"
-RIDS = ("osx-arm64", "osx-x64", "linux-x64", "win-x64")
+RIDS = ("osx-arm64", "osx-x64", "linux-arm64", "linux-x64", "win-x64")
class CrossRidCompatibilityVerifierTests(unittest.TestCase):
diff --git a/scripts/tests/test_verify_linux_native_abi.py b/scripts/tests/test_verify_linux_native_abi.py
new file mode 100644
index 000000000..7ce071822
--- /dev/null
+++ b/scripts/tests/test_verify_linux_native_abi.py
@@ -0,0 +1,61 @@
+from __future__ import annotations
+
+import importlib.util
+import pathlib
+import unittest
+
+
+SCRIPT = pathlib.Path(__file__).resolve().parents[1] / "verify-linux-native-abi.py"
+SPEC = importlib.util.spec_from_file_location("verify_linux_native_abi", SCRIPT)
+assert SPEC and SPEC.loader
+MODULE = importlib.util.module_from_spec(SPEC)
+SPEC.loader.exec_module(MODULE)
+
+
+def elf_text(machine: str = "AArch64", glibc: str = "2.27", *, runpath: bool = False) -> str:
+ path_line = " 0x0 (RUNPATH) Library runpath: [/workspace/out]" if runpath else ""
+ return f"""
+ Machine: {machine}
+ 0x0 (NEEDED) Shared library: [libc.so.6]
+ 0x0 (NEEDED) Shared library: [libstdc++.so.6]
+ {path_line}
+ Name: GLIBC_{glibc}
+ Name: GLIBCXX_3.4.24
+ Name: CXXABI_1.3.11
+ 42: 0 8 FUNC GLOBAL DEFAULT 12 webscene_engine_get_abi_version
+"""
+
+
+class LinuxNativeAbiVerifierTests(unittest.TestCase):
+ def test_accepts_arm64_at_contract_ceiling(self) -> None:
+ report = MODULE.verify_text(elf_text(), "linux-arm64", "2.27", "3.4.24", "1.3.11")
+ self.assertEqual("pass", report["status"], report)
+
+ def test_rejects_newer_glibc(self) -> None:
+ report = MODULE.verify_text(elf_text(glibc="2.28"), "linux-arm64", "2.27", "3.4.24", "1.3.11")
+ self.assertEqual("fail", report["status"])
+ self.assertTrue(any("GLIBC requires 2.28" in issue for issue in report["issues"]))
+
+ def test_rejects_wrong_architecture_and_runpath(self) -> None:
+ report = MODULE.verify_text(elf_text(machine="Advanced Micro Devices X86-64", runpath=True), "linux-arm64", "2.27", "3.4.24", "1.3.11")
+ self.assertEqual("fail", report["status"])
+ self.assertTrue(any("ELF machine" in issue for issue in report["issues"]))
+ self.assertTrue(any("RPATH/RUNPATH" in issue for issue in report["issues"]))
+
+ def test_rejects_missing_contract_export(self) -> None:
+ report = MODULE.verify_text(
+ elf_text(), "linux-arm64", "2.27", "3.4.24", "1.3.11",
+ {"webscene_engine_get_abi_version", "webscene_engine_create"},
+ )
+ self.assertEqual("fail", report["status"])
+ self.assertTrue(any("webscene_engine_create" in issue for issue in report["issues"]))
+
+ def test_rejects_interpreter_on_shared_library(self) -> None:
+ text = elf_text() + "\n[Requesting program interpreter: /lib/ld-linux-aarch64.so.1]\n"
+ report = MODULE.verify_text(text, "linux-arm64", "2.27", "3.4.24", "1.3.11")
+ self.assertEqual("fail", report["status"])
+ self.assertTrue(any("ELF interpreter" in issue for issue in report["issues"]))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/scripts/tests/test_verify_native_payload_reproducibility.py b/scripts/tests/test_verify_native_payload_reproducibility.py
new file mode 100644
index 000000000..49e9b0bcc
--- /dev/null
+++ b/scripts/tests/test_verify_native_payload_reproducibility.py
@@ -0,0 +1,41 @@
+from __future__ import annotations
+
+import importlib.util
+import pathlib
+import tempfile
+import unittest
+import zipfile
+
+
+SCRIPT = pathlib.Path(__file__).resolve().parents[1] / "verify-native-payload-reproducibility.py"
+SPEC = importlib.util.spec_from_file_location("verify_native_payload_reproducibility", SCRIPT)
+assert SPEC and SPEC.loader
+MODULE = importlib.util.module_from_spec(SPEC)
+SPEC.loader.exec_module(MODULE)
+
+
+class NativePayloadReproducibilityTests(unittest.TestCase):
+ def package(self, root: pathlib.Path, name: str, payload: bytes) -> pathlib.Path:
+ package = root / name
+ with zipfile.ZipFile(package, "w") as archive:
+ archive.writestr("runtimes/linux-x64/native/libwebscene_native_engine.so", payload)
+ archive.writestr("metadata.txt", name)
+ return package
+
+ def test_ignores_package_container_metadata(self) -> None:
+ with tempfile.TemporaryDirectory() as temporary:
+ root = pathlib.Path(temporary)
+ first = self.package(root, "first.nupkg", b"same")
+ second = self.package(root, "second.nupkg", b"same")
+ self.assertEqual(MODULE.payload_hashes(first, "linux-x64"), MODULE.payload_hashes(second, "linux-x64"))
+
+ def test_detects_payload_change(self) -> None:
+ with tempfile.TemporaryDirectory() as temporary:
+ root = pathlib.Path(temporary)
+ first = self.package(root, "first.nupkg", b"first")
+ second = self.package(root, "second.nupkg", b"second")
+ self.assertNotEqual(MODULE.payload_hashes(first, "linux-x64"), MODULE.payload_hashes(second, "linux-x64"))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/scripts/verify-linux-native-abi.py b/scripts/verify-linux-native-abi.py
new file mode 100755
index 000000000..df2c87cc5
--- /dev/null
+++ b/scripts/verify-linux-native-abi.py
@@ -0,0 +1,141 @@
+#!/usr/bin/env python3
+"""Verify the architecture, dependency, symbol-version, and export contract of a Linux DSO."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import pathlib
+import re
+import subprocess
+
+
+ALLOWED_NEEDED = {
+ "libc.so.6", "libdl.so.2", "libgcc_s.so.1", "libm.so.6",
+ "libpthread.so.0", "librt.so.1", "libstdc++.so.6", "libutil.so.1",
+}
+EXPECTED_MACHINES = {
+ "linux-x64": "Advanced Micro Devices X86-64",
+ "linux-arm64": "AArch64",
+}
+EXPECTED_INTERPRETERS = {
+ # Runtime payloads are shared libraries, not PIE executables. A PT_INTERP
+ # segment would make the payload directly executable and is never valid.
+ "linux-x64": "",
+ "linux-arm64": "",
+}
+
+
+def version_tuple(value: str) -> tuple[int, ...]:
+ return tuple(int(part) for part in value.split("."))
+
+
+def collect_versions(text: str, namespace: str) -> set[str]:
+ return set(re.findall(rf"\b{re.escape(namespace)}_([0-9]+(?:\.[0-9]+)+)\b", text))
+
+
+def verify_text(
+ text: str,
+ rid: str,
+ max_glibc: str,
+ max_glibcxx: str,
+ max_cxxabi: str,
+ required_exports: set[str] | None = None,
+) -> dict[str, object]:
+ issues: list[str] = []
+ machine_match = re.search(r"^\s*Machine:\s*(.+?)\s*$", text, re.MULTILINE)
+ machine = machine_match.group(1) if machine_match else ""
+ if machine != EXPECTED_MACHINES[rid]:
+ issues.append(f"ELF machine is {machine!r}; expected {EXPECTED_MACHINES[rid]!r}")
+
+ interpreter_match = re.search(r"Requesting program interpreter:\s*([^\]]+)\]", text)
+ interpreter = interpreter_match.group(1) if interpreter_match else ""
+ if interpreter != EXPECTED_INTERPRETERS[rid]:
+ issues.append(
+ f"ELF interpreter is {interpreter!r}; expected {EXPECTED_INTERPRETERS[rid]!r}"
+ )
+
+ needed = set(re.findall(r"\(NEEDED\).*?\[(.+?)\]", text))
+ unexpected_needed = sorted(needed - ALLOWED_NEEDED)
+ if unexpected_needed:
+ issues.append("unexpected DT_NEEDED libraries: " + ", ".join(unexpected_needed))
+ if re.search(r"\((?:RPATH|RUNPATH)\)", text):
+ issues.append("RPATH/RUNPATH is not permitted")
+ if "/crossrootfs/" in text or "/workspace/" in text:
+ issues.append("build or sysroot path leaked into ELF metadata")
+
+ ceilings = {"GLIBC": max_glibc, "GLIBCXX": max_glibcxx, "CXXABI": max_cxxabi}
+ observed: dict[str, list[str]] = {}
+ for namespace, ceiling in ceilings.items():
+ versions = sorted(collect_versions(text, namespace), key=version_tuple)
+ observed[namespace] = versions
+ too_new = [value for value in versions if version_tuple(value) > version_tuple(ceiling)]
+ if too_new:
+ issues.append(f"{namespace} requires {too_new[-1]}; maximum is {ceiling}")
+
+ exports = set(re.findall(r"\bGLOBAL\s+DEFAULT\s+\d+\s+(webscene_[A-Za-z0-9_]+)\b", text))
+ missing_exports = sorted((required_exports or {"webscene_engine_get_abi_version"}) - exports)
+ if missing_exports:
+ issues.append("missing required exports: " + ", ".join(missing_exports))
+
+ return {
+ "schemaVersion": 1,
+ "status": "pass" if not issues else "fail",
+ "runtimeIdentifier": rid,
+ "machine": machine,
+ "interpreter": interpreter,
+ "needed": sorted(needed),
+ "symbolVersions": observed,
+ "limits": ceilings,
+ "issues": issues,
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("library", type=pathlib.Path)
+ parser.add_argument("--rid", choices=sorted(EXPECTED_MACHINES), required=True)
+ parser.add_argument("--max-glibc", default="2.27")
+ parser.add_argument("--max-glibcxx", default="3.4.24")
+ parser.add_argument("--max-cxxabi", default="1.3.11")
+ parser.add_argument(
+ "--exports-file",
+ type=pathlib.Path,
+ default=pathlib.Path(__file__).resolve().parents[1]
+ / "experiments/WebScene.NativeEngine.Probe/native/webscene_native_engine.exports",
+ )
+ parser.add_argument("--output", type=pathlib.Path)
+ args = parser.parse_args()
+ if not args.library.is_file():
+ parser.error(f"library does not exist: {args.library}")
+ if not args.exports_file.is_file():
+ parser.error(f"exports file does not exist: {args.exports_file}")
+ required_exports = {
+ line.strip().removeprefix("_")
+ for line in args.exports_file.read_text(encoding="utf-8").splitlines()
+ if line.strip() and not line.lstrip().startswith("#")
+ }
+ completed = subprocess.run(
+ ["readelf", "-h", "-l", "-d", "--version-info", "--dyn-syms", str(args.library)],
+ check=False, capture_output=True, text=True,
+ )
+ if completed.returncode:
+ raise RuntimeError(completed.stderr.strip() or "readelf failed")
+ report = verify_text(
+ completed.stdout,
+ args.rid,
+ args.max_glibc,
+ args.max_glibcxx,
+ args.max_cxxabi,
+ required_exports,
+ )
+ rendered = json.dumps(report, indent=2) + "\n"
+ if args.output:
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ args.output.write_text(rendered, encoding="utf-8")
+ print(rendered, end="")
+ return 0 if report["status"] == "pass" else 1
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/verify-native-payload-reproducibility.py b/scripts/verify-native-payload-reproducibility.py
new file mode 100755
index 000000000..2d583c7da
--- /dev/null
+++ b/scripts/verify-native-payload-reproducibility.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+"""Compare runtime payload bytes from two independently produced NuGet packages."""
+
+from __future__ import annotations
+
+import argparse
+import hashlib
+import json
+import pathlib
+import zipfile
+
+
+def payload_hashes(package: pathlib.Path, rid: str) -> dict[str, str]:
+ prefix = f"runtimes/{rid}/native/"
+ with zipfile.ZipFile(package) as archive:
+ return {
+ name.removeprefix(prefix): hashlib.sha256(archive.read(name)).hexdigest()
+ for name in sorted(archive.namelist())
+ if name.startswith(prefix) and not name.endswith("/")
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("first", type=pathlib.Path)
+ parser.add_argument("second", type=pathlib.Path)
+ parser.add_argument("--rid", choices=("linux-x64", "linux-arm64"), required=True)
+ parser.add_argument("--output", type=pathlib.Path)
+ args = parser.parse_args()
+ first = payload_hashes(args.first, args.rid)
+ second = payload_hashes(args.second, args.rid)
+ report = {
+ "schemaVersion": 1,
+ "status": "pass" if first == second else "fail",
+ "runtimeIdentifier": args.rid,
+ "first": first,
+ "second": second,
+ }
+ rendered = json.dumps(report, indent=2) + "\n"
+ if args.output:
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ args.output.write_text(rendered, encoding="utf-8")
+ print(rendered, end="")
+ return 0 if report["status"] == "pass" else 1
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/verify-release-packages.py b/scripts/verify-release-packages.py
index c03eacb7a..a9597951d 100755
--- a/scripts/verify-release-packages.py
+++ b/scripts/verify-release-packages.py
@@ -27,14 +27,15 @@
"WebScene.Sdk.Avalonia",
"WebScene.Sdk.Uno",
}
-DEFAULT_NATIVE_RIDS = {"osx-arm64", "osx-x64", "linux-x64", "win-x64"}
+DEFAULT_NATIVE_RIDS = {"osx-arm64", "osx-x64", "linux-arm64", "linux-x64", "win-x64"}
NATIVE_V8_REVISIONS = {
"osx-arm64": "15.3.10",
"osx-x64": "15.3.10",
+ "linux-arm64": "15.3.10",
"linux-x64": "15.3.10",
"win-x64": "15.3.10",
}
-PARTITION_ALLOC_NATIVE_RIDS = {"osx-arm64", "osx-x64", "linux-x64", "win-x64"}
+PARTITION_ALLOC_NATIVE_RIDS = {"osx-arm64", "osx-x64", "linux-arm64", "linux-x64", "win-x64"}
REPOSITORY_URL = "https://github.com/wieslawsoltes/WebScene"
REQUIRED_PACKAGE_TAGS = {"webscene", "web-ui", "native-ui"}
@@ -187,6 +188,18 @@ def validate_native_runtime(
"thinLto": False,
"certificationTelemetry": False,
}
+ if runtime_identifier == "linux-x64":
+ expected.update({
+ "builderIdentity": "webscene-linux-glibc-v1",
+ "targetTriple": "x86_64-linux-gnu",
+ "glibcBaseline": "2.27",
+ })
+ elif runtime_identifier == "linux-arm64":
+ expected.update({
+ "builderIdentity": "webscene-linux-glibc-v1",
+ "targetTriple": "aarch64-linux-gnu",
+ "glibcBaseline": "2.27",
+ })
for name, value in expected.items():
if manifest.get(name) != value:
raise RuntimeError(
From fff06227f36d9e52f5d6b516c48365115749a252 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 11:10:38 +0100
Subject: [PATCH 03/41] fix(linux): harden reproducible builder execution
---
.../Dockerfile.linux-glibc | 7 ++++---
scripts/build-linux-native-runtime.sh | 7 ++++++-
scripts/build-native-engine-runtime.sh | 16 ++++++++++------
3 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
index 5809cb90a..b19ade491 100644
--- a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
+++ b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
@@ -1,5 +1,3 @@
-# syntax=docker/dockerfile:1
-
FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64@sha256:2962cae8ca49b18fb533504513c89308927ed3721372a0cc58630c350a2936b8 AS x64-sysroot
FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm64@sha256:619e1c013b88c504d34c8e064e0860313cebeb3ee1fc6e2e838406744c9857a8 AS arm64-sysroot
FROM mcr.microsoft.com/dotnet/sdk:10.0.302-noble-amd64@sha256:7a91ccecc26d71bf7688c627a6b5eae2e27bb2cd1e37e8abe738348904245692 AS dotnet-sdk
@@ -34,7 +32,10 @@ RUN set -eux; \
RUN git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git /opt/depot_tools \
&& git -C /opt/depot_tools checkout --detach "$DEPOT_TOOLS_COMMIT" \
- && test "$(git -C /opt/depot_tools rev-parse HEAD)" = "$DEPOT_TOOLS_COMMIT"
+ && test "$(git -C /opt/depot_tools rev-parse HEAD)" = "$DEPOT_TOOLS_COMMIT" \
+ && /opt/depot_tools/ensure_bootstrap \
+ && git config --system --add safe.directory /opt/depot_tools \
+ && git config --system --add safe.directory /opt/depot_tools/.git
COPY linux-build-lock.json /opt/webscene/linux-build-lock.json
WORKDIR /workspace
diff --git a/scripts/build-linux-native-runtime.sh b/scripts/build-linux-native-runtime.sh
index be8b6c031..c8ae9d0a8 100755
--- a/scripts/build-linux-native-runtime.sh
+++ b/scripts/build-linux-native-runtime.sh
@@ -51,6 +51,11 @@ cargo_target_key="$(printf '%s' "$rust_target_triple" | tr '[:lower:]-' '[:upper
cargo_linker_name="CARGO_TARGET_${cargo_target_key}_LINKER"
cargo_rustflags_name="CARGO_TARGET_${cargo_target_key}_RUSTFLAGS"
source_date_epoch="$(git -C "$repo_root" show -s --format=%ct HEAD)"
+git_common_dir="$(git -C "$repo_root" rev-parse --path-format=absolute --git-common-dir)"
+docker_mount_args=(--volume "$repo_root:/workspace")
+if [[ "$git_common_dir" != "$repo_root/.git" ]]; then
+ docker_mount_args+=(--volume "$git_common_dir:$git_common_dir:ro")
+fi
if [[ "$stage" == finalize ]]; then
"$repo_root/scripts/build-native-engine-runtime.sh" \
@@ -115,7 +120,7 @@ docker run --rm \
--env "CARGO_BUILD_TARGET=$rust_target_triple" \
--env "$cargo_linker_name=clang" \
--env "$cargo_rustflags_name=-C link-arg=--target=$target_triple -C link-arg=--sysroot=$sysroot --remap-path-prefix=/workspace=." \
- --volume "$repo_root:/workspace" \
+ "${docker_mount_args[@]}" \
--workdir /workspace \
"$builder_image" \
scripts/build-native-engine-runtime.sh "${common_args[@]}"
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index b021e1101..3adb17735 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -205,10 +205,14 @@ if [[ -z "$v8_root" ]]; then
gclient config https://chromium.googlesource.com/v8/v8
)
fi
- (
- cd "$v8_workspace"
- gclient sync --no-history -r "$v8_revision"
- )
+ v8_sync_marker="$v8_workspace/.gclient-sync-$v8_revision"
+ if [[ ! -f "$v8_sync_marker" ]]; then
+ (
+ cd "$v8_workspace"
+ gclient sync --no-history -r "$v8_revision"
+ )
+ : > "$v8_sync_marker"
+ fi
apply_patch_once() {
local checkout="$1"
@@ -241,14 +245,14 @@ if [[ -z "$v8_root" ]]; then
apply_patch_once "$v8_root/build" "$repo_root/packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt"
fi
- gn_args="chrome_pgo_phase=0 fatal_linker_warnings=false is_cfi=false is_component_build=false is_debug=false symbol_level=0 target_cpu=\"$cpu\" treat_warnings_as_errors=false use_clang_modules=false use_custom_libcxx=false use_thin_lto=$thin_lto v8_embedder_string=\"-WebScene\" v8_enable_fuzztest=false v8_enable_partition_alloc=$partition_alloc v8_enable_pointer_compression=true v8_enable_pointer_compression_shared_cage=true v8_enable_sandbox=false v8_enable_static_roots=false v8_enable_31bit_smis_on_64bit_arch=false v8_enable_temporal_support=false v8_enable_webassembly=$v8_webassembly v8_monolithic=true v8_use_external_startup_data=false v8_target_cpu=\"$cpu\""
+ gn_args="chrome_pgo_phase=0 fatal_linker_warnings=false is_cfi=false is_component_build=false is_debug=false symbol_level=0 target_cpu=\"$cpu\" treat_warnings_as_errors=false use_clang_modules=false use_custom_libcxx=true use_thin_lto=$thin_lto v8_embedder_string=\"-WebScene\" v8_enable_fuzztest=false v8_enable_partition_alloc=$partition_alloc v8_enable_pointer_compression=true v8_enable_pointer_compression_shared_cage=true v8_enable_sandbox=false v8_enable_static_roots=false v8_enable_31bit_smis_on_64bit_arch=false v8_enable_temporal_support=false v8_enable_webassembly=$v8_webassembly v8_monolithic=true v8_use_external_startup_data=false v8_target_cpu=\"$cpu\""
if [[ "$expected_kernel" == Linux ]]; then
# V8 15.3 requires C++20 library headers that are newer than its downloaded
# Debian Bullseye sysroot. Build inside the pinned Ubuntu 22.04 image
# against that image's libstdc++ and glibc 2.35 instead.
# Keep V8's bundled LLD for its host tools; the reviewed build patch above
# disables only CREL emission so Jammy can consume the archive.
- gn_args+=" use_lld=true use_sysroot=true target_sysroot=\"$sysroot\" v8_monolithic_for_shared_library=true"
+ gn_args+=" use_lld=true use_sysroot=true target_sysroot=\"$sysroot\" use_glib=false v8_monolithic_for_shared_library=true"
fi
if [[ "$partition_alloc" == true \
&& ( "$expected_kernel" == Linux || "$expected_kernel" == Darwin ) ]]; then
From d99c33524d08e7dd92f294d0bf41edbe9f90b844 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 11:32:49 +0100
Subject: [PATCH 04/41] ci(release): use self-hosted native runners
---
.github/workflows/native-runtime-packages.yml | 40 +++++++++----------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 04bddb93b..6e686b721 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -152,7 +152,7 @@ jobs:
linux-builder:
name: Build immutable Linux cross-builder
needs: metadata
- runs-on: ubuntu-24.04
+ runs-on: [self-hosted, Linux, X64]
permissions:
contents: read
packages: write
@@ -194,7 +194,7 @@ jobs:
fail-fast: false
matrix:
include:
- - os: macos-latest
+ - os: [self-hosted, macOS, ARM64]
rid: osx-arm64
cpu: arm64
monolith: libv8_monolith.a
@@ -205,7 +205,7 @@ jobs:
v8_cache_generation: v9-v8-15.3.10-pa-no-process-shim-macos-inspector
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
- - os: macos-15-intel
+ - os: [self-hosted, macOS, X64]
rid: osx-x64
cpu: x64
monolith: libv8_monolith.a
@@ -216,7 +216,7 @@ jobs:
v8_cache_generation: v9-v8-15.3.10-pa-no-process-shim-macos-inspector
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
- - os: ubuntu-latest
+ - os: [self-hosted, Linux, X64]
rid: linux-x64
cpu: x64
monolith: libv8_monolith.a
@@ -227,7 +227,7 @@ jobs:
v8_cache_generation: v1-v8-15.3.10-glibc227-cross-x64
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- - os: ubuntu-24.04
+ - os: [self-hosted, Linux, X64]
rid: linux-arm64
cpu: arm64
monolith: libv8_monolith.a
@@ -501,7 +501,7 @@ jobs:
linux-arm64-finalize:
name: Finalize and test linux-arm64
needs: [metadata, native]
- runs-on: ubuntu-24.04-arm
+ runs-on: [self-hosted, Linux, ARM64]
steps:
- uses: actions/checkout@v4
- name: Setup .NET
@@ -689,13 +689,13 @@ jobs:
fail-fast: false
matrix:
include:
- - os: macos-latest
+ - os: [self-hosted, macOS, ARM64]
rid: osx-arm64
- - os: macos-15-intel
+ - os: [self-hosted, macOS, X64]
rid: osx-x64
- - os: ubuntu-latest
+ - os: [self-hosted, Linux, X64]
rid: linux-x64
- - os: ubuntu-24.04-arm
+ - os: [self-hosted, Linux, ARM64]
rid: linux-arm64
- os: windows-2022
rid: win-x64
@@ -745,22 +745,22 @@ jobs:
fail-fast: false
matrix:
include:
- - os: ubuntu-24.04
+ - os: [self-hosted, Linux, X64]
rid: linux-x64
platform: linux/amd64
distribution: ubuntu-18.04
image: ubuntu:18.04
- - os: ubuntu-24.04-arm
+ - os: [self-hosted, Linux, ARM64]
rid: linux-arm64
platform: linux/arm64
distribution: ubuntu-18.04
image: ubuntu:18.04
- - os: ubuntu-24.04
+ - os: [self-hosted, Linux, X64]
rid: linux-x64
platform: linux/amd64
distribution: ubi-8.9
image: registry.access.redhat.com/ubi8/ubi:8.9
- - os: ubuntu-24.04-arm
+ - os: [self-hosted, Linux, ARM64]
rid: linux-arm64
platform: linux/arm64
distribution: ubi-8.9
@@ -798,12 +798,12 @@ jobs:
fail-fast: false
matrix:
include:
- - { os: ubuntu-24.04, rid: linux-x64, platform: linux/amd64, distribution: debian-10, image: 'debian:10' }
- - { os: ubuntu-24.04-arm, rid: linux-arm64, platform: linux/arm64, distribution: debian-10, image: 'debian:10' }
- - { os: ubuntu-24.04, rid: linux-x64, platform: linux/amd64, distribution: fedora-38, image: 'fedora:38' }
- - { os: ubuntu-24.04-arm, rid: linux-arm64, platform: linux/arm64, distribution: fedora-38, image: 'fedora:38' }
- - { os: ubuntu-24.04, rid: linux-x64, platform: linux/amd64, distribution: opensuse-15.5, image: 'opensuse/leap:15.5' }
- - { os: ubuntu-24.04-arm, rid: linux-arm64, platform: linux/arm64, distribution: opensuse-15.5, image: 'opensuse/leap:15.5' }
+ - { os: [self-hosted, Linux, X64], rid: linux-x64, platform: linux/amd64, distribution: debian-10, image: 'debian:10' }
+ - { os: [self-hosted, Linux, ARM64], rid: linux-arm64, platform: linux/arm64, distribution: debian-10, image: 'debian:10' }
+ - { os: [self-hosted, Linux, X64], rid: linux-x64, platform: linux/amd64, distribution: fedora-38, image: 'fedora:38' }
+ - { os: [self-hosted, Linux, ARM64], rid: linux-arm64, platform: linux/arm64, distribution: fedora-38, image: 'fedora:38' }
+ - { os: [self-hosted, Linux, X64], rid: linux-x64, platform: linux/amd64, distribution: opensuse-15.5, image: 'opensuse/leap:15.5' }
+ - { os: [self-hosted, Linux, ARM64], rid: linux-arm64, platform: linux/arm64, distribution: opensuse-15.5, image: 'opensuse/leap:15.5' }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
From b4d13aeecf8c3621dd678f35c572361051b915c1 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 11:41:44 +0100
Subject: [PATCH 05/41] ci(macos): cross-build x64 on arm64 runner
---
.github/workflows/native-runtime-packages.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 6e686b721..86eb111da 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -205,7 +205,7 @@ jobs:
v8_cache_generation: v9-v8-15.3.10-pa-no-process-shim-macos-inspector
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
- - os: [self-hosted, macOS, X64]
+ - os: [self-hosted, macOS, ARM64]
rid: osx-x64
cpu: x64
monolith: libv8_monolith.a
@@ -691,7 +691,7 @@ jobs:
include:
- os: [self-hosted, macOS, ARM64]
rid: osx-arm64
- - os: [self-hosted, macOS, X64]
+ - os: [self-hosted, macOS, ARM64]
rid: osx-x64
- os: [self-hosted, Linux, X64]
rid: linux-x64
From 7aa52eab10d2607cbb89ff87535b29eae1eee747 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 11:51:24 +0100
Subject: [PATCH 06/41] fix(ci): publish builder under repository owner
---
.github/workflows/native-runtime-packages.yml | 4 ++--
.../ReleaseCompatibilityGateTests.cs | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 86eb111da..7c79294a2 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -176,12 +176,12 @@ jobs:
file: packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
- tags: ghcr.io/wieslawsoltes/webscene-linux-builder:webscene-linux-glibc-v1
+ tags: ghcr.io/scenetech/webscene-linux-builder:webscene-linux-glibc-v1
- id: reference
name: Resolve immutable builder reference
if: github.event_name != 'pull_request'
shell: bash
- run: echo "image=ghcr.io/wieslawsoltes/webscene-linux-builder@${{ steps.build.outputs.digest }}" >> "$GITHUB_OUTPUT"
+ run: echo "image=ghcr.io/scenetech/webscene-linux-builder@${{ steps.build.outputs.digest }}" >> "$GITHUB_OUTPUT"
native:
name: Build ${{ matrix.rid }}
diff --git a/tests/WebScene.Architecture.Tests/ReleaseCompatibilityGateTests.cs b/tests/WebScene.Architecture.Tests/ReleaseCompatibilityGateTests.cs
index 95e7b21ef..4cf4d7b6d 100644
--- a/tests/WebScene.Architecture.Tests/ReleaseCompatibilityGateTests.cs
+++ b/tests/WebScene.Architecture.Tests/ReleaseCompatibilityGateTests.cs
@@ -177,7 +177,7 @@ public void RuntimeWorkflowRunsForProfileChangesAndPublishesPerRidEvidence()
workflow,
StringComparison.Ordinal);
Assert.Contains(
- "needs: [metadata, packages, native, required-evidence]",
+ "needs: [metadata, packages, native, linux-arm64-finalize, required-evidence]",
workflow,
StringComparison.Ordinal);
Assert.Contains(
@@ -228,7 +228,7 @@ public void RuntimePublicationRequiresSuccessfulCiForTheExactCommit()
Assert.Contains("--status completed", workflow, StringComparison.Ordinal);
Assert.Contains("if [[ \"$conclusion\" != success ]]", workflow, StringComparison.Ordinal);
Assert.Contains(
- "needs: [metadata, consumer, release-ci-gate]",
+ "needs: [metadata, consumer, linux-floor-smoke, release-ci-gate]",
workflow,
StringComparison.Ordinal);
Assert.Contains("fail-fast: false", ciWorkflow, StringComparison.Ordinal);
From bf4990e1651749954969b63ab7c3d0f2c4dc3d10 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 12:17:20 +0100
Subject: [PATCH 07/41] fix(ci): install dotnet in runner temp
---
.github/workflows/native-runtime-packages.yml | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 7c79294a2..50ff4b0f7 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -64,6 +64,8 @@ jobs:
scripts/tests/test_verify_cross_rid_compatibility.py
- name: Setup .NET
uses: actions/setup-dotnet@v5
+ env:
+ DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
- id: version
@@ -131,6 +133,8 @@ jobs:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v5
+ env:
+ DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
- name: Pack and verify .NET packages
@@ -256,6 +260,8 @@ jobs:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v5
+ env:
+ DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
- id: v8-cache-key
@@ -506,6 +512,8 @@ jobs:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v5
+ env:
+ DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
- name: Download ARM64 cross-build stage
@@ -704,6 +712,8 @@ jobs:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v5
+ env:
+ DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
- name: Download verified package set
@@ -846,6 +856,8 @@ jobs:
path: artifacts/nuget-packages
- name: Setup .NET
uses: actions/setup-dotnet@v5
+ env:
+ DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
- id: trusted-publishing
From 0e35ef740e41e188a632141dee03f24352658b90 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 12:36:46 +0100
Subject: [PATCH 08/41] fix(release): support glibc 2.27 arm64 build
---
.github/workflows/native-runtime-packages.yml | 10 +++++--
.../linux-build-lock.json | 6 ++++
.../V8PartitionAllocGlibc227Arm64Patch.txt | 30 +++++++++++++++++++
scripts/build-native-engine-runtime.sh | 5 ++++
scripts/tests/test_linux_build_policy.py | 9 ++++++
5 files changed, 58 insertions(+), 2 deletions(-)
create mode 100644 packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocGlibc227Arm64Patch.txt
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 50ff4b0f7..80f50c5f0 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -68,6 +68,7 @@ jobs:
DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
+ dotnet-version: 8.0.x
- id: version
name: Resolve and validate package version
shell: bash
@@ -137,6 +138,7 @@ jobs:
DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
+ dotnet-version: 8.0.x
- name: Pack and verify .NET packages
shell: bash
run: |
@@ -239,7 +241,7 @@ jobs:
v8_revision: 15.3.10
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
- v8_cache_generation: v1-v8-15.3.10-glibc227-cross-arm64
+ v8_cache_generation: v2-v8-15.3.10-glibc227-cross-arm64
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- os: windows-2022
@@ -264,6 +266,7 @@ jobs:
DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
+ dotnet-version: 8.0.x
- id: v8-cache-key
name: Resolve pinned V8 SDK cache identity
shell: bash
@@ -283,7 +286,7 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/icu/LICENSE
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/partition_alloc/src
- key: webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-${{ steps.v8-cache-key.outputs.image-version }}-${{ hashFiles(matrix.v8_cache_script, matrix.v8_cache_patch, 'packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc', 'packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json', 'scripts/linux-glibc-toolchain.cmake') }}
+ key: webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-${{ steps.v8-cache-key.outputs.image-version }}-${{ hashFiles(matrix.v8_cache_script, matrix.v8_cache_patch, 'packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocGlibc227Arm64Patch.txt', 'packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc', 'packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json', 'scripts/linux-glibc-toolchain.cmake') }}
restore-keys: |
webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-
webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-
@@ -516,6 +519,7 @@ jobs:
DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
+ dotnet-version: 8.0.x
- name: Download ARM64 cross-build stage
uses: actions/download-artifact@v4
with:
@@ -716,6 +720,7 @@ jobs:
DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
+ dotnet-version: 8.0.x
- name: Download verified package set
uses: actions/download-artifact@v4
with:
@@ -860,6 +865,7 @@ jobs:
DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
+ dotnet-version: 8.0.x
- id: trusted-publishing
name: Exchange GitHub identity for a temporary NuGet API key
if: env.NUGET_API_KEY == '' && env.NUGET_USER != ''
diff --git a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
index 0af1c1a42..832ba91cb 100644
--- a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
+++ b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
@@ -35,5 +35,11 @@
"rustArm64StdSha256": "4952abb7d9d3ed7cea4f7ea44dcb23dc67631fae4ac44a5f059b90a4b5e9223f",
"depotToolsCommit": "ca054941f756b50e1a3d83727270d879bec1f331",
"v8Revision": "15.3.10"
+ },
+ "patches": {
+ "v8PartitionAllocGlibc227Arm64": {
+ "path": "patches/V8PartitionAllocGlibc227Arm64Patch.txt",
+ "sha256": "cf9226f0a461a0b85f56f4c1afe5d6cd8ea8f544392411afcd3b89b05d2a366e"
+ }
}
}
diff --git a/packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocGlibc227Arm64Patch.txt b/packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocGlibc227Arm64Patch.txt
new file mode 100644
index 000000000..6b6f322f1
--- /dev/null
+++ b/packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocGlibc227Arm64Patch.txt
@@ -0,0 +1,30 @@
+diff --git a/partition_alloc.gni b/partition_alloc.gni
+index 32e7609..666574e 100644
+--- a/partition_alloc.gni
++++ b/partition_alloc.gni
+@@ -110,7 +110,8 @@
+ use_large_empty_slot_span_ring = true
+
+-has_memory_tagging = current_cpu == "arm64" && is_clang && !is_asan &&
+- !is_hwasan && (is_linux || is_android)
++# WebScene targets glibc 2.27, which predates sys/ifunc.h. MTE's resolver
++# requires that glibc-private header, so keep MTE disabled for this embedder.
++has_memory_tagging = false
+
+ declare_args() {
+ # Whether PartitionAlloc is built in official mode.
+diff --git a/src/partition_alloc/aarch64_support.h b/src/partition_alloc/aarch64_support.h
+index 18bd374..2241f66 100644
+--- a/src/partition_alloc/aarch64_support.h
++++ b/src/partition_alloc/aarch64_support.h
+@@ -9,7 +9,9 @@
+ #include "partition_alloc/build_config.h"
+ #include "partition_alloc/buildflags.h"
+
+-#if PA_BUILDFLAG(IS_ANDROID) || PA_BUILDFLAG(IS_LINUX)
++// glibc did not provide sys/ifunc.h until after WebScene's 2.27 baseline.
++#if (PA_BUILDFLAG(IS_ANDROID) || PA_BUILDFLAG(IS_LINUX)) && \
++ __has_include()
+ #define HAS_HW_CAPS
+ #endif
+
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 3adb17735..4ec357c1b 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -243,6 +243,11 @@ if [[ -z "$v8_root" ]]; then
fi
if [[ "$expected_kernel" == Linux ]]; then
apply_patch_once "$v8_root/build" "$repo_root/packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt"
+ if [[ "$cpu" == arm64 ]]; then
+ apply_patch_once \
+ "$v8_root/third_party/partition_alloc/src" \
+ "$repo_root/packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocGlibc227Arm64Patch.txt"
+ fi
fi
gn_args="chrome_pgo_phase=0 fatal_linker_warnings=false is_cfi=false is_component_build=false is_debug=false symbol_level=0 target_cpu=\"$cpu\" treat_warnings_as_errors=false use_clang_modules=false use_custom_libcxx=true use_thin_lto=$thin_lto v8_embedder_string=\"-WebScene\" v8_enable_fuzztest=false v8_enable_partition_alloc=$partition_alloc v8_enable_pointer_compression=true v8_enable_pointer_compression_shared_cage=true v8_enable_sandbox=false v8_enable_static_roots=false v8_enable_31bit_smis_on_64bit_arch=false v8_enable_temporal_support=false v8_enable_webassembly=$v8_webassembly v8_monolithic=true v8_use_external_startup_data=false v8_target_cpu=\"$cpu\""
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index e9df757a2..1d53a1f3c 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import hashlib
import json
import pathlib
import re
@@ -37,6 +38,14 @@ def test_lock_and_dockerfile_are_synchronized(self) -> None:
):
self.assertIn(value, self.dockerfile)
+ for patch in self.lock["patches"].values():
+ patch_path = PACKAGING / patch["path"]
+ self.assertTrue(patch_path.is_file(), patch_path)
+ self.assertEqual(
+ patch["sha256"],
+ hashlib.sha256(patch_path.read_bytes()).hexdigest(),
+ )
+
def test_release_matrix_contains_both_glibc_rids(self) -> None:
for rid in ("linux-x64", "linux-arm64"):
self.assertIn(f"rid: {rid}", self.workflow)
From 4fb6747d868c64489382d4a4a254af6a9d1e7e34 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 14:06:13 +0100
Subject: [PATCH 09/41] fix(linux): preserve cross configuration in native
builds
---
scripts/build-native-engine-runtime.sh | 19 +++++++++++++++++++
scripts/linux-glibc-toolchain.cmake | 7 +++++++
scripts/tests/test_linux_build_policy.py | 14 ++++++++++++++
3 files changed, 40 insertions(+)
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 4ec357c1b..71d817f96 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -270,6 +270,25 @@ if [[ -z "$v8_root" ]]; then
(
cd "$v8_root"
gn gen "out/$cpu/$v8_configuration" --args="$gn_args"
+ if [[ "$expected_kernel" == Linux && "$cpu" == arm64 ]]; then
+ partition_alloc_buildflags_relative="gen/third_party/partition_alloc/src/partition_alloc/buildflags.h"
+ partition_alloc_buildflags="out/$cpu/$v8_configuration/$partition_alloc_buildflags_relative"
+ ninja -C "out/$cpu/$v8_configuration" "$partition_alloc_buildflags_relative"
+ if [[ ! -f "$partition_alloc_buildflags" ]]; then
+ echo "PartitionAlloc build flags were not generated at '$partition_alloc_buildflags'." >&2
+ exit 1
+ fi
+ # V8's embedder overrides can retain ARM MTE even when the standalone
+ # PartitionAlloc default is patched. glibc 2.27 has no sys/ifunc.h, so
+ # force the generated target flag off before Ninja consumes it.
+ sed -i \
+ 's/^#define PA_BUILDFLAG_INTERNAL_HAS_MEMORY_TAGGING() (1)$/#define PA_BUILDFLAG_INTERNAL_HAS_MEMORY_TAGGING() (0)/' \
+ "$partition_alloc_buildflags"
+ if ! grep -Fqx '#define PA_BUILDFLAG_INTERNAL_HAS_MEMORY_TAGGING() (0)' "$partition_alloc_buildflags"; then
+ echo "Unable to disable PartitionAlloc memory tagging for the glibc 2.27 ARM64 target." >&2
+ exit 1
+ fi
+ fi
ninja -C "out/$cpu/$v8_configuration" obj/libv8_monolith.a
)
v8_output_root="$v8_root/out/$cpu/$v8_configuration"
diff --git a/scripts/linux-glibc-toolchain.cmake b/scripts/linux-glibc-toolchain.cmake
index 9e1d4d5b1..7b62cbdff 100644
--- a/scripts/linux-glibc-toolchain.cmake
+++ b/scripts/linux-glibc-toolchain.cmake
@@ -1,5 +1,12 @@
set(CMAKE_SYSTEM_NAME Linux)
+# CMake re-evaluates this toolchain inside try_compile projects. Explicitly
+# forward WebScene's target identity so compiler ABI checks remain cross builds.
+set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
+ WEBSCENE_LINUX_TARGET_TRIPLE
+ WEBSCENE_RUST_TARGET_TRIPLE
+ CMAKE_SYSROOT)
+
if(NOT DEFINED WEBSCENE_LINUX_TARGET_TRIPLE)
message(FATAL_ERROR "WEBSCENE_LINUX_TARGET_TRIPLE is required")
endif()
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 1d53a1f3c..2ce1610be 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -17,6 +17,8 @@ def setUpClass(cls) -> None:
cls.lock = json.loads((PACKAGING / "linux-build-lock.json").read_text())
cls.dockerfile = (PACKAGING / "Dockerfile.linux-glibc").read_text()
cls.workflow = (ROOT / ".github/workflows/native-runtime-packages.yml").read_text()
+ cls.build_script = (ROOT / "scripts/build-native-engine-runtime.sh").read_text()
+ cls.toolchain = (ROOT / "scripts/linux-glibc-toolchain.cmake").read_text()
def test_all_container_inputs_are_digest_pinned(self) -> None:
from_lines = re.findall(r"^FROM\s+(\S+)", self.dockerfile, re.MULTILINE)
@@ -53,6 +55,18 @@ def test_release_matrix_contains_both_glibc_rids(self) -> None:
self.assertIn(f"--native-rid {rid}", self.workflow)
self.assertIn("github.ref_type != 'tag'", self.workflow)
+ def test_arm64_disables_memory_tagging_for_glibc_227(self) -> None:
+ self.assertIn(
+ "PA_BUILDFLAG_INTERNAL_HAS_MEMORY_TAGGING() (0)",
+ self.build_script,
+ )
+ self.assertIn("V8PartitionAllocGlibc227Arm64Patch.txt", self.build_script)
+
+ def test_cmake_try_compile_keeps_cross_target_identity(self) -> None:
+ self.assertIn("CMAKE_TRY_COMPILE_PLATFORM_VARIABLES", self.toolchain)
+ self.assertIn("WEBSCENE_LINUX_TARGET_TRIPLE", self.toolchain)
+ self.assertIn("CMAKE_SYSROOT", self.toolchain)
+
if __name__ == "__main__":
unittest.main()
From e22563ed7d7716f666e4230f7c892e44d7bf46e1 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 14:16:59 +0100
Subject: [PATCH 10/41] fix(linux): resolve openssl from target sysroot
---
scripts/build-native-engine-runtime.sh | 15 +++++++++++++++
scripts/tests/test_linux_build_policy.py | 5 +++++
2 files changed, 20 insertions(+)
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 71d817f96..b0655ca44 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -441,11 +441,26 @@ elif [[ "$expected_kernel" == Linux ]]; then
echo "Linux native runtime builds require '$linux_cxx' and ld.lld." >&2
exit 1
fi
+ openssl_library_dir="$sysroot/usr/lib/$target_triple"
+ openssl_include_dir="$sysroot/usr/include"
+ for openssl_input in \
+ "$openssl_include_dir/openssl/ssl.h" \
+ "$openssl_library_dir/libcrypto.so" \
+ "$openssl_library_dir/libssl.so"; do
+ if [[ ! -e "$openssl_input" ]]; then
+ echo "Linux sysroot is missing required OpenSSL input '$openssl_input'." >&2
+ exit 1
+ fi
+ done
cmake_args+=(
-DCMAKE_TOOLCHAIN_FILE="$repo_root/scripts/linux-glibc-toolchain.cmake"
-DCMAKE_SYSROOT="$sysroot"
-DWEBSCENE_LINUX_TARGET_TRIPLE="$target_triple"
-DWEBSCENE_RUST_TARGET_TRIPLE="$rust_target_triple"
+ -DOPENSSL_ROOT_DIR="$sysroot/usr"
+ -DOPENSSL_INCLUDE_DIR="$openssl_include_dir"
+ -DOPENSSL_CRYPTO_LIBRARY="$openssl_library_dir/libcrypto.so"
+ -DOPENSSL_SSL_LIBRARY="$openssl_library_dir/libssl.so"
"-DCMAKE_C_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
"-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 2ce1610be..6d6585427 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -67,6 +67,11 @@ def test_cmake_try_compile_keeps_cross_target_identity(self) -> None:
self.assertIn("WEBSCENE_LINUX_TARGET_TRIPLE", self.toolchain)
self.assertIn("CMAKE_SYSROOT", self.toolchain)
+ def test_linux_openssl_is_resolved_only_from_the_target_sysroot(self) -> None:
+ self.assertIn('openssl_library_dir="$sysroot/usr/lib/$target_triple"', self.build_script)
+ self.assertIn('-DOPENSSL_CRYPTO_LIBRARY="$openssl_library_dir/libcrypto.so"', self.build_script)
+ self.assertIn('-DOPENSSL_SSL_LIBRARY="$openssl_library_dir/libssl.so"', self.build_script)
+
if __name__ == "__main__":
unittest.main()
From 76ef353a7f635398ae3d93789bedf4e108ed455c Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 14:26:44 +0100
Subject: [PATCH 11/41] fix(linux): expose sysroot multiarch dependencies
---
scripts/build-native-engine-runtime.sh | 26 ++++++++++++++----------
scripts/linux-glibc-toolchain.cmake | 11 ++++++++++
scripts/tests/test_linux_build_policy.py | 15 +++++++++++---
3 files changed, 38 insertions(+), 14 deletions(-)
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index b0655ca44..92e575463 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -441,14 +441,16 @@ elif [[ "$expected_kernel" == Linux ]]; then
echo "Linux native runtime builds require '$linux_cxx' and ld.lld." >&2
exit 1
fi
- openssl_library_dir="$sysroot/usr/lib/$target_triple"
- openssl_include_dir="$sysroot/usr/include"
- for openssl_input in \
- "$openssl_include_dir/openssl/ssl.h" \
- "$openssl_library_dir/libcrypto.so" \
- "$openssl_library_dir/libssl.so"; do
- if [[ ! -e "$openssl_input" ]]; then
- echo "Linux sysroot is missing required OpenSSL input '$openssl_input'." >&2
+ target_library_dir="$sysroot/usr/lib/$target_triple"
+ target_include_dir="$sysroot/usr/include"
+ for target_dependency in \
+ "$target_include_dir/openssl/ssl.h" \
+ "$target_library_dir/libcrypto.so" \
+ "$target_library_dir/libssl.so" \
+ "$target_include_dir/zlib.h" \
+ "$target_library_dir/libz.so"; do
+ if [[ ! -e "$target_dependency" ]]; then
+ echo "Linux sysroot is missing required native dependency '$target_dependency'." >&2
exit 1
fi
done
@@ -458,9 +460,11 @@ elif [[ "$expected_kernel" == Linux ]]; then
-DWEBSCENE_LINUX_TARGET_TRIPLE="$target_triple"
-DWEBSCENE_RUST_TARGET_TRIPLE="$rust_target_triple"
-DOPENSSL_ROOT_DIR="$sysroot/usr"
- -DOPENSSL_INCLUDE_DIR="$openssl_include_dir"
- -DOPENSSL_CRYPTO_LIBRARY="$openssl_library_dir/libcrypto.so"
- -DOPENSSL_SSL_LIBRARY="$openssl_library_dir/libssl.so"
+ -DOPENSSL_INCLUDE_DIR="$target_include_dir"
+ -DOPENSSL_CRYPTO_LIBRARY="$target_library_dir/libcrypto.so"
+ -DOPENSSL_SSL_LIBRARY="$target_library_dir/libssl.so"
+ -DZLIB_INCLUDE_DIR="$target_include_dir"
+ -DZLIB_LIBRARY="$target_library_dir/libz.so"
"-DCMAKE_C_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
"-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld
diff --git a/scripts/linux-glibc-toolchain.cmake b/scripts/linux-glibc-toolchain.cmake
index 7b62cbdff..923707e07 100644
--- a/scripts/linux-glibc-toolchain.cmake
+++ b/scripts/linux-glibc-toolchain.cmake
@@ -22,6 +22,17 @@ else()
message(FATAL_ERROR "Unsupported Linux target triple: ${WEBSCENE_LINUX_TARGET_TRIPLE}")
endif()
+# The pinned sysroots use Debian multiarch directories. CMake does not always
+# infer these while cross-compiling, so make the target layout available to all
+# find_package/find_library calls instead of resolving libraries from the host.
+set(CMAKE_LIBRARY_ARCHITECTURE "${WEBSCENE_LINUX_TARGET_TRIPLE}")
+list(APPEND CMAKE_SYSTEM_LIBRARY_PATH
+ "/lib/${WEBSCENE_LINUX_TARGET_TRIPLE}"
+ "/usr/lib/${WEBSCENE_LINUX_TARGET_TRIPLE}")
+list(APPEND CMAKE_SYSTEM_INCLUDE_PATH
+ "/usr/include/${WEBSCENE_LINUX_TARGET_TRIPLE}"
+ "/usr/include")
+
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_C_COMPILER_TARGET "${WEBSCENE_LINUX_TARGET_TRIPLE}")
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 6d6585427..0ed18715e 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -68,9 +68,18 @@ def test_cmake_try_compile_keeps_cross_target_identity(self) -> None:
self.assertIn("CMAKE_SYSROOT", self.toolchain)
def test_linux_openssl_is_resolved_only_from_the_target_sysroot(self) -> None:
- self.assertIn('openssl_library_dir="$sysroot/usr/lib/$target_triple"', self.build_script)
- self.assertIn('-DOPENSSL_CRYPTO_LIBRARY="$openssl_library_dir/libcrypto.so"', self.build_script)
- self.assertIn('-DOPENSSL_SSL_LIBRARY="$openssl_library_dir/libssl.so"', self.build_script)
+ self.assertIn('target_library_dir="$sysroot/usr/lib/$target_triple"', self.build_script)
+ self.assertIn('-DOPENSSL_CRYPTO_LIBRARY="$target_library_dir/libcrypto.so"', self.build_script)
+ self.assertIn('-DOPENSSL_SSL_LIBRARY="$target_library_dir/libssl.so"', self.build_script)
+
+ def test_linux_zlib_is_resolved_only_from_the_target_sysroot(self) -> None:
+ self.assertIn('-DZLIB_INCLUDE_DIR="$target_include_dir"', self.build_script)
+ self.assertIn('-DZLIB_LIBRARY="$target_library_dir/libz.so"', self.build_script)
+
+ def test_toolchain_exposes_target_multiarch_search_paths(self) -> None:
+ self.assertIn("CMAKE_LIBRARY_ARCHITECTURE", self.toolchain)
+ self.assertIn('/usr/lib/${WEBSCENE_LINUX_TARGET_TRIPLE}', self.toolchain)
+ self.assertIn('/usr/include/${WEBSCENE_LINUX_TARGET_TRIPLE}', self.toolchain)
if __name__ == "__main__":
From a2562baa825af752840e760bb58bbc0e1989cbb0 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 14:43:13 +0100
Subject: [PATCH 12/41] fix(native): align cross-build target toolchains
---
.github/workflows/native-runtime-packages.yml | 20 +++++++--
.../CMakeLists.txt | 7 +++-
scripts/build-native-engine-runtime.sh | 42 ++++++++++++++++---
scripts/tests/test_linux_build_policy.py | 10 +++++
4 files changed, 69 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 80f50c5f0..9742f5461 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -230,7 +230,7 @@ jobs:
v8_revision: 15.3.10
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
- v8_cache_generation: v1-v8-15.3.10-glibc227-cross-x64
+ v8_cache_generation: v2-v8-15.3.10-glibc227-cross-x64
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- os: [self-hosted, Linux, X64]
@@ -241,7 +241,7 @@ jobs:
v8_revision: 15.3.10
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
- v8_cache_generation: v2-v8-15.3.10-glibc227-cross-arm64
+ v8_cache_generation: v3-v8-15.3.10-glibc227-cross-arm64
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- os: windows-2022
@@ -281,6 +281,7 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/include
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/args.gn
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/${{ matrix.monolith }}
+ artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/icudtl.dat
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/gen/third_party/partition_alloc/src
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
@@ -302,6 +303,10 @@ jobs:
|| { [[ -d "$root/third_party/partition_alloc/src" ]] \
&& [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/gen/third_party/partition_alloc/src/partition_alloc/buildflags.h" ]]; }
}
+ libcxx_is_compatible() {
+ [[ '${{ matrix.rid }}' != linux-* ]] \
+ || [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]]
+ }
args_are_compatible() {
grep -Eq '^v8_enable_pointer_compression *= *true$' "$args" \
&& grep -Eq '^v8_enable_pointer_compression_shared_cage *= *true$' "$args" \
@@ -324,7 +329,8 @@ jobs:
&& -f "$root/LICENSE" \
&& -f "$root/third_party/icu/LICENSE" ]] \
&& args_are_compatible \
- && partition_alloc_is_compatible; then
+ && partition_alloc_is_compatible \
+ && libcxx_is_compatible; then
echo "ready=true" >> "$GITHUB_OUTPUT"
else
echo "ready=false" >> "$GITHUB_OUTPUT"
@@ -423,6 +429,10 @@ jobs:
|| { [[ -d "$root/third_party/partition_alloc/src" ]] \
&& [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/gen/third_party/partition_alloc/src/partition_alloc/buildflags.h" ]]; }
}
+ libcxx_is_compatible() {
+ [[ '${{ matrix.rid }}' != linux-* ]] \
+ || [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]]
+ }
args_are_compatible() {
grep -Eq '^v8_enable_pointer_compression *= *true$' "$args" \
&& grep -Eq '^v8_enable_pointer_compression_shared_cage *= *true$' "$args" \
@@ -443,7 +453,8 @@ jobs:
&& -f "$root/LICENSE" \
&& -f "$root/third_party/icu/LICENSE" ]] \
&& args_are_compatible \
- && partition_alloc_is_compatible; then
+ && partition_alloc_is_compatible \
+ && libcxx_is_compatible; then
echo "ready=true" >> "$GITHUB_OUTPUT"
else
echo "ready=false" >> "$GITHUB_OUTPUT"
@@ -456,6 +467,7 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/include
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/args.gn
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/${{ matrix.monolith }}
+ artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/icudtl.dat
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/gen/third_party/partition_alloc/src
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
diff --git a/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt b/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
index dbf4c719f..5c1d08bbb 100644
--- a/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
+++ b/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
@@ -181,10 +181,13 @@ if(WEBSCENE_NATIVE_ENGINE_HTML_PARSER STREQUAL "html5ever")
"${CMAKE_CURRENT_BINARY_DIR}/html-parser-target")
set(WEBSCENE_HTML_PARSER_LIBRARY_DIR
"${WEBSCENE_HTML_PARSER_TARGET_DIR}/release")
+ set(WEBSCENE_HTML_PARSER_CARGO_TARGET_ARGS)
if(DEFINED WEBSCENE_RUST_TARGET_TRIPLE
AND NOT WEBSCENE_RUST_TARGET_TRIPLE STREQUAL "")
set(WEBSCENE_HTML_PARSER_LIBRARY_DIR
"${WEBSCENE_HTML_PARSER_TARGET_DIR}/${WEBSCENE_RUST_TARGET_TRIPLE}/release")
+ list(APPEND WEBSCENE_HTML_PARSER_CARGO_TARGET_ARGS
+ --target "${WEBSCENE_RUST_TARGET_TRIPLE}")
endif()
if(MSVC)
set(WEBSCENE_HTML_PARSER_LIBRARY
@@ -199,7 +202,9 @@ if(WEBSCENE_NATIVE_ENGINE_HTML_PARSER STREQUAL "html5ever")
"CARGO_TARGET_DIR=${WEBSCENE_HTML_PARSER_TARGET_DIR}"
"${WEBSCENE_CARGO_EXECUTABLE}" build
--manifest-path "${WEBSCENE_HTML_PARSER_MANIFEST}"
- --release --locked ${WEBSCENE_HTML_PARSER_CARGO_FEATURES}
+ --release --locked
+ ${WEBSCENE_HTML_PARSER_CARGO_TARGET_ARGS}
+ ${WEBSCENE_HTML_PARSER_CARGO_FEATURES}
DEPENDS
"${WEBSCENE_HTML_PARSER_MANIFEST}"
"${CMAKE_CURRENT_SOURCE_DIR}/native/html_parser/Cargo.lock"
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 92e575463..f4de8a541 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -139,11 +139,28 @@ if [[ -z "$package_version" ]]; then
exit 1
fi
+macos_arm64_to_x64=false
+host_kernel="$(uname -s)"
+host_machine="$(uname -m)"
+if [[ "$rid" == osx-x64 && "$host_kernel" == Darwin && "$host_machine" == arm64 ]]; then
+ macos_arm64_to_x64=true
+fi
if [[ "$expected_kernel" == Darwin \
- && ( "$(uname -s)" != "$expected_kernel" || "$(uname -m)" != "$expected_machine" ) ]]; then
- echo "RID '$rid' must be built natively on $expected_kernel/$expected_machine; current host is $(uname -s)/$(uname -m)." >&2
+ && ( "$host_kernel" != "$expected_kernel" \
+ || ( "$host_machine" != "$expected_machine" && "$macos_arm64_to_x64" != true ) ) ]]; then
+ echo "RID '$rid' must be built natively on $expected_kernel/$expected_machine; current host is $host_kernel/$host_machine." >&2
exit 1
fi
+if [[ "$expected_kernel" == Darwin && -z "$rust_target_triple" ]]; then
+ if [[ "$cpu" == x64 ]]; then
+ rust_target_triple=x86_64-apple-darwin
+ else
+ rust_target_triple=aarch64-apple-darwin
+ fi
+fi
+if [[ "$macos_arm64_to_x64" == true ]] && command -v rustup >/dev/null 2>&1; then
+ rustup target add "$rust_target_triple"
+fi
if [[ "$expected_kernel" == Linux ]]; then
case "$rid:$target_triple" in
linux-x64:x86_64-linux-gnu|linux-arm64:aarch64-linux-gnu) ;;
@@ -398,7 +415,15 @@ cmake_args=(
)
macos_deployment_target=14.0
if [[ "$expected_kernel" == Darwin ]]; then
- cmake_args+=(-DCMAKE_OSX_DEPLOYMENT_TARGET="$macos_deployment_target")
+ macos_architecture=arm64
+ if [[ "$cpu" == x64 ]]; then
+ macos_architecture=x86_64
+ fi
+ cmake_args+=(
+ -DCMAKE_OSX_ARCHITECTURES="$macos_architecture"
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="$macos_deployment_target"
+ -DWEBSCENE_RUST_TARGET_TRIPLE="$rust_target_triple"
+ )
fi
if [[ "$thin_lto" == true ]]; then
v8_llvm_bin="$v8_root/third_party/llvm-build/Release+Asserts/bin"
@@ -443,12 +468,18 @@ elif [[ "$expected_kernel" == Linux ]]; then
fi
target_library_dir="$sysroot/usr/lib/$target_triple"
target_include_dir="$sysroot/usr/include"
+ v8_libcxx_include="$v8_root/buildtools/third_party/libc++/src/include"
+ v8_libcxxabi_include="$v8_root/third_party/libc++abi/src/include"
+ v8_libcxx_archive="$v8_output_root/obj/buildtools/third_party/libc++/libc++.a"
for target_dependency in \
"$target_include_dir/openssl/ssl.h" \
"$target_library_dir/libcrypto.so" \
"$target_library_dir/libssl.so" \
"$target_include_dir/zlib.h" \
- "$target_library_dir/libz.so"; do
+ "$target_library_dir/libz.so" \
+ "$v8_libcxx_include/source_location" \
+ "$v8_libcxxabi_include/cxxabi.h" \
+ "$v8_libcxx_archive"; do
if [[ ! -e "$target_dependency" ]]; then
echo "Linux sysroot is missing required native dependency '$target_dependency'." >&2
exit 1
@@ -466,7 +497,8 @@ elif [[ "$expected_kernel" == Linux ]]; then
-DZLIB_INCLUDE_DIR="$target_include_dir"
-DZLIB_LIBRARY="$target_library_dir/libz.so"
"-DCMAKE_C_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
- "-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
+ "-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=. -nostdinc++ -nostdlib++ -isystem$v8_libcxx_include -isystem$v8_libcxxabi_include"
+ -DCMAKE_CXX_STANDARD_LIBRARIES="$v8_libcxx_archive"
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld
"-DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld -Wl,--build-id=sha1"
)
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 0ed18715e..414d993dd 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -81,6 +81,16 @@ def test_toolchain_exposes_target_multiarch_search_paths(self) -> None:
self.assertIn('/usr/lib/${WEBSCENE_LINUX_TARGET_TRIPLE}', self.toolchain)
self.assertIn('/usr/include/${WEBSCENE_LINUX_TARGET_TRIPLE}', self.toolchain)
+ def test_linux_runtime_uses_v8_bundled_libcxx(self) -> None:
+ self.assertIn("buildtools/third_party/libc++/src/include", self.build_script)
+ self.assertIn("-nostdinc++ -nostdlib++", self.build_script)
+ self.assertIn("CMAKE_CXX_STANDARD_LIBRARIES", self.build_script)
+
+ def test_arm_mac_can_cross_build_intel_runtime(self) -> None:
+ self.assertIn("macos_arm64_to_x64=true", self.build_script)
+ self.assertIn('-DCMAKE_OSX_ARCHITECTURES="$macos_architecture"', self.build_script)
+ self.assertIn("x86_64-apple-darwin", self.build_script)
+
if __name__ == "__main__":
unittest.main()
From f96bb76c0c64188af678f8ac6f04cda28be00649 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 15:08:16 +0100
Subject: [PATCH 13/41] fix(macos): bootstrap pinned rust toolchain
---
.../linux-build-lock.json | 2 ++
scripts/build-native-engine-runtime.sh | 30 +++++++++++++++++--
scripts/tests/test_linux_build_policy.py | 4 +++
3 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
index 832ba91cb..fc9f627fa 100644
--- a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
+++ b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
@@ -33,6 +33,8 @@
"rust": "1.90.0",
"rustArchiveSha256": "bff8974f2d3ee6c0e6ac926b533f65bbdd3697d2c2b925bdae5f45b9eed10a67",
"rustArm64StdSha256": "4952abb7d9d3ed7cea4f7ea44dcb23dc67631fae4ac44a5f059b90a4b5e9223f",
+ "rustMacArm64ArchiveSha256": "9772d20d5cd736079a0ee84d00e6697cf2084f0fc4621b011e24e6f2d08d2d7f",
+ "rustMacX64StdSha256": "dd731e6f9f30cb9b2928b92b084d2f12a3abf06a481ecbd8c3553c3e6f742139",
"depotToolsCommit": "ca054941f756b50e1a3d83727270d879bec1f331",
"v8Revision": "15.3.10"
},
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index f4de8a541..a92f7533f 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -158,8 +158,34 @@ if [[ "$expected_kernel" == Darwin && -z "$rust_target_triple" ]]; then
rust_target_triple=aarch64-apple-darwin
fi
fi
-if [[ "$macos_arm64_to_x64" == true ]] && command -v rustup >/dev/null 2>&1; then
- rustup target add "$rust_target_triple"
+if [[ "$expected_kernel" == Darwin ]]; then
+ rust_version=1.90.0
+ rust_mac_arm64_sha256=9772d20d5cd736079a0ee84d00e6697cf2084f0fc4621b011e24e6f2d08d2d7f
+ rust_mac_x64_std_sha256=dd731e6f9f30cb9b2928b92b084d2f12a3abf06a481ecbd8c3553c3e6f742139
+ rust_prefix="${RUNNER_TEMP:-$repo_root/artifacts/toolchains}/webscene-rust-$rust_version"
+ rust_complete="$rust_prefix/.webscene-complete"
+ if [[ ! -f "$rust_complete" ]]; then
+ rust_download_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/webscene-rust.XXXXXX")"
+ (
+ cd "$rust_download_dir"
+ host_archive="rust-$rust_version-aarch64-apple-darwin.tar.xz"
+ x64_std_archive="rust-std-$rust_version-x86_64-apple-darwin.tar.xz"
+ curl -fsSLO "https://static.rust-lang.org/dist/$host_archive"
+ echo "$rust_mac_arm64_sha256 $host_archive" | shasum -a 256 -c -
+ tar -xf "$host_archive"
+ "${host_archive%.tar.xz}/install.sh" --prefix="$rust_prefix" --without=rust-docs
+ curl -fsSLO "https://static.rust-lang.org/dist/$x64_std_archive"
+ echo "$rust_mac_x64_std_sha256 $x64_std_archive" | shasum -a 256 -c -
+ tar -xf "$x64_std_archive"
+ "${x64_std_archive%.tar.xz}/install.sh" --prefix="$rust_prefix"
+ : > "$rust_complete"
+ )
+ fi
+ export PATH="$rust_prefix/bin:$PATH"
+ if [[ "$(rustc --version)" != "rustc $rust_version "* ]]; then
+ echo "Pinned macOS Rust toolchain validation failed: $(rustc --version)" >&2
+ exit 1
+ fi
fi
if [[ "$expected_kernel" == Linux ]]; then
case "$rid:$target_triple" in
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 414d993dd..730a39e07 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -48,6 +48,9 @@ def test_lock_and_dockerfile_are_synchronized(self) -> None:
hashlib.sha256(patch_path.read_bytes()).hexdigest(),
)
+ for key in ("rustMacArm64ArchiveSha256", "rustMacX64StdSha256"):
+ self.assertIn(toolchain[key], self.build_script)
+
def test_release_matrix_contains_both_glibc_rids(self) -> None:
for rid in ("linux-x64", "linux-arm64"):
self.assertIn(f"rid: {rid}", self.workflow)
@@ -90,6 +93,7 @@ def test_arm_mac_can_cross_build_intel_runtime(self) -> None:
self.assertIn("macos_arm64_to_x64=true", self.build_script)
self.assertIn('-DCMAKE_OSX_ARCHITECTURES="$macos_architecture"', self.build_script)
self.assertIn("x86_64-apple-darwin", self.build_script)
+ self.assertIn("rust-std-$rust_version-x86_64-apple-darwin", self.build_script)
if __name__ == "__main__":
From e1746d3da8dd2a638efa833638617d8dea69e122 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 15:25:18 +0100
Subject: [PATCH 14/41] fix(macos): build V8 with system libc++
---
.github/workflows/native-runtime-packages.yml | 14 ++++++++---
scripts/build-native-engine-runtime.sh | 25 +++++++++++++++----
2 files changed, 30 insertions(+), 9 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 9742f5461..0441ed6a2 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -208,7 +208,7 @@ jobs:
v8_revision: 15.3.10
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
- v8_cache_generation: v9-v8-15.3.10-pa-no-process-shim-macos-inspector
+ v8_cache_generation: v10-v8-15.3.10-pa-system-libcxx-macos-inspector
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
- os: [self-hosted, macOS, ARM64]
@@ -219,7 +219,7 @@ jobs:
v8_revision: 15.3.10
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
- v8_cache_generation: v9-v8-15.3.10-pa-no-process-shim-macos-inspector
+ v8_cache_generation: v10-v8-15.3.10-pa-system-libcxx-macos-inspector
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
- os: [self-hosted, Linux, X64]
@@ -318,7 +318,10 @@ jobs:
&& { [[ '${{ matrix.rid }}' != linux-* ]] \
|| { grep -Eq '^use_lld *= *true$' "$args" \
&& grep -Eq '^use_sysroot *= *true$' "$args" \
- && grep -Eq '^v8_monolithic_for_shared_library *= *true$' "$args"; }; }
+ && grep -Eq '^v8_monolithic_for_shared_library *= *true$' "$args" \
+ && grep -Eq '^use_custom_libcxx *= *true$' "$args"; }; } \
+ && { [[ '${{ matrix.rid }}' != osx-* ]] \
+ || grep -Eq '^use_custom_libcxx *= *false$' "$args"; }
}
if [[ -f "$root/include/v8.h" \
&& -f "$root/include/v8-inspector.h" \
@@ -441,8 +444,11 @@ jobs:
|| { grep -Eq '^use_lld *= *true$' "$args" \
&& grep -Eq '^use_sysroot *= *true$' "$args" \
&& grep -Eq '^v8_monolithic_for_shared_library *= *true$' "$args" \
+ && grep -Eq '^use_custom_libcxx *= *true$' "$args" \
&& grep -Eq '^use_allocator_shim *= *false$' "$args" \
- && grep -Eq '^use_partition_alloc_as_malloc *= *false$' "$args"; }; }
+ && grep -Eq '^use_partition_alloc_as_malloc *= *false$' "$args"; }; } \
+ && { [[ '${{ matrix.rid }}' != osx-* ]] \
+ || grep -Eq '^use_custom_libcxx *= *false$' "$args"; }
}
if [[ -f "$root/include/v8.h" \
&& -f "$root/include/v8-inspector.h" \
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index a92f7533f..20500d739 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -293,14 +293,19 @@ if [[ -z "$v8_root" ]]; then
fi
fi
- gn_args="chrome_pgo_phase=0 fatal_linker_warnings=false is_cfi=false is_component_build=false is_debug=false symbol_level=0 target_cpu=\"$cpu\" treat_warnings_as_errors=false use_clang_modules=false use_custom_libcxx=true use_thin_lto=$thin_lto v8_embedder_string=\"-WebScene\" v8_enable_fuzztest=false v8_enable_partition_alloc=$partition_alloc v8_enable_pointer_compression=true v8_enable_pointer_compression_shared_cage=true v8_enable_sandbox=false v8_enable_static_roots=false v8_enable_31bit_smis_on_64bit_arch=false v8_enable_temporal_support=false v8_enable_webassembly=$v8_webassembly v8_monolithic=true v8_use_external_startup_data=false v8_target_cpu=\"$cpu\""
+ gn_args="chrome_pgo_phase=0 fatal_linker_warnings=false is_cfi=false is_component_build=false is_debug=false symbol_level=0 target_cpu=\"$cpu\" treat_warnings_as_errors=false use_clang_modules=false use_thin_lto=$thin_lto v8_embedder_string=\"-WebScene\" v8_enable_fuzztest=false v8_enable_partition_alloc=$partition_alloc v8_enable_pointer_compression=true v8_enable_pointer_compression_shared_cage=true v8_enable_sandbox=false v8_enable_static_roots=false v8_enable_31bit_smis_on_64bit_arch=false v8_enable_temporal_support=false v8_enable_webassembly=$v8_webassembly v8_monolithic=true v8_use_external_startup_data=false v8_target_cpu=\"$cpu\""
if [[ "$expected_kernel" == Linux ]]; then
- # V8 15.3 requires C++20 library headers that are newer than its downloaded
- # Debian Bullseye sysroot. Build inside the pinned Ubuntu 22.04 image
- # against that image's libstdc++ and glibc 2.35 instead.
+ # V8 15.3 requires C++20 library headers that are newer than the glibc 2.27
+ # target sysroot provides. Use Chromium's bundled libc++ while retaining
+ # the locked old-glibc sysroot for the platform ABI.
# Keep V8's bundled LLD for its host tools; the reviewed build patch above
# disables only CREL emission so Jammy can consume the archive.
- gn_args+=" use_lld=true use_sysroot=true target_sysroot=\"$sysroot\" use_glib=false v8_monolithic_for_shared_library=true"
+ gn_args+=" use_custom_libcxx=true use_lld=true use_sysroot=true target_sysroot=\"$sysroot\" use_glib=false v8_monolithic_for_shared_library=true"
+ elif [[ "$expected_kernel" == Darwin ]]; then
+ # WebScene's embedding targets use the libc++ supplied by the selected
+ # macOS SDK. Build V8 against the same ABI; Chromium's bundled libc++ uses
+ # the std::__Cr namespace and cannot be linked with Apple's system libc++.
+ gn_args+=" use_custom_libcxx=false"
fi
if [[ "$partition_alloc" == true \
&& ( "$expected_kernel" == Linux || "$expected_kernel" == Darwin ) ]]; then
@@ -408,6 +413,16 @@ if [[ "$expected_kernel" == Linux ]] \
echo "The V8 SDK at '$v8_root' is not safe to link into a shared library." >&2
exit 1
fi
+if [[ "$expected_kernel" == Linux ]] \
+ && ! grep -Eq '^use_custom_libcxx *= *true$' "$v8_args"; then
+ echo "The V8 SDK at '$v8_root' was not built with Chromium's required Linux libc++." >&2
+ exit 1
+fi
+if [[ "$expected_kernel" == Darwin ]] \
+ && ! grep -Eq '^use_custom_libcxx *= *false$' "$v8_args"; then
+ echo "The V8 SDK at '$v8_root' was not built with the macOS system libc++." >&2
+ exit 1
+fi
if [[ "$partition_alloc" == true \
&& ( "$expected_kernel" == Linux || "$expected_kernel" == Darwin ) ]] \
&& { ! grep -Eq '^use_allocator_shim *= *false$' "$v8_args" \
From e0e345d6ef7d1d9512cf7c73f3d0d9b88c458554 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 16:10:42 +0100
Subject: [PATCH 15/41] fix(macos): parse canvas font sizes on macOS 14
---
.../native/webscene_v8_runtime.cpp | 1 +
.../native/webscene_v8_runtime_canvas.inc | 17 +++++++++++------
.../tests/native_v8_runtime_canvas_tests.inc | 8 ++++++--
3 files changed, 18 insertions(+), 8 deletions(-)
diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime.cpp b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime.cpp
index e68b33e1e..3f6db147c 100644
--- a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime.cpp
+++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime.cpp
@@ -33,6 +33,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_canvas.inc b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_canvas.inc
index d48e26d0d..b5ab52165 100644
--- a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_canvas.inc
+++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_canvas.inc
@@ -2388,12 +2388,17 @@
}
float parsed_size = 0.0F;
const auto number = shorthand.substr(number_start, index - number_start);
- const auto parse_result = std::from_chars(
- number.data(),
- number.data() + number.size(),
- parsed_size);
- if (parse_result.ec == std::errc{}
- && parse_result.ptr == number.data() + number.size()
+ // Floating-point std::from_chars is only available from macOS
+ // 26 in the current Apple SDK. strtof is available on the
+ // macOS 14 deployment baseline; copy the bounded view so its
+ // end can still be validated exactly.
+ const std::string number_text(number);
+ char* parse_end = nullptr;
+ errno = 0;
+ parsed_size = std::strtof(number_text.c_str(), &parse_end);
+ if (parse_end == number_text.c_str() + number_text.size()
+ && parse_end != number_text.c_str()
+ && errno != ERANGE
&& std::isfinite(parsed_size)
&& parsed_size > 0.0F) {
result.size = parsed_size;
diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_canvas_tests.inc b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_canvas_tests.inc
index 4c8bcfafc..3090d0e30 100644
--- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_canvas_tests.inc
+++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_canvas_tests.inc
@@ -58,6 +58,8 @@ void test_canvas_text_metrics_use_host_font_axes()
const small = context.measureText('MMMM');
context.font = '20px sans-serif';
const large = context.measureText('MMMM');
+ context.font = '12.5px sans-serif';
+ const fractional = context.measureText('MMMM');
context.font = "bold 12px -apple-system, BlinkMacSystemFont, 'Trebuchet MS', sans-serif";
const bold = context.measureText('Label');
context.textBaseline = 'middle';
@@ -71,6 +73,7 @@ void test_canvas_text_metrics_use_host_font_axes()
bold.fontBoundingBoxDescent
].every(Number.isFinite),
sizeChangesAdvance: large.width > small.width * 1.9,
+ fractionalSizeParsed: fractional.fontBoundingBoxAscent === 9.375,
ascent: bold.actualBoundingBoxAscent,
descent: bold.actualBoundingBoxDescent,
fontAscent: bold.fontBoundingBoxAscent,
@@ -86,7 +89,8 @@ void test_canvas_text_metrics_use_host_font_axes()
require(
result.find("\"defaultFieldsAreFinite\":true") != std::string::npos
- && result.find("\"sizeChangesAdvance\":true") != std::string::npos,
+ && result.find("\"sizeChangesAdvance\":true") != std::string::npos
+ && result.find("\"fractionalSizeParsed\":true") != std::string::npos,
"Canvas TextMetrics did not use finite host font metrics: " + result);
require(
result.find("\"ascent\":7.2") != std::string::npos
@@ -103,7 +107,7 @@ void test_canvas_text_metrics_use_host_font_axes()
"Canvas TextMetrics did not resolve distances from the active middle baseline: "
+ result);
require(
- probe.calls == 3U,
+ probe.calls == 4U,
"Canvas measureText did not reuse host metrics across baseline-only changes");
require(probe.text == "Label", "Canvas measureText changed the measured text");
require(
From 98f342754126c5f29b7e76cfa6eeb36a82ff8e97 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 18:51:50 +0100
Subject: [PATCH 16/41] fix(runtime): stabilize cross-platform package builds
---
.github/workflows/native-runtime-packages.yml | 18 ++++++++++++++++--
scripts/build-native-engine-runtime.sh | 12 ++++++++----
scripts/tests/test_linux_build_policy.py | 7 ++++++-
3 files changed, 30 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 0441ed6a2..67faff1ec 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -202,6 +202,7 @@ jobs:
include:
- os: [self-hosted, macOS, ARM64]
rid: osx-arm64
+ dotnet_architecture: arm64
cpu: arm64
monolith: libv8_monolith.a
script: unix
@@ -213,6 +214,7 @@ jobs:
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
- os: [self-hosted, macOS, ARM64]
rid: osx-x64
+ dotnet_architecture: x64
cpu: x64
monolith: libv8_monolith.a
script: unix
@@ -224,6 +226,7 @@ jobs:
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
- os: [self-hosted, Linux, X64]
rid: linux-x64
+ dotnet_architecture: x64
cpu: x64
monolith: libv8_monolith.a
script: unix
@@ -235,6 +238,7 @@ jobs:
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- os: [self-hosted, Linux, X64]
rid: linux-arm64
+ dotnet_architecture: x64
cpu: arm64
monolith: libv8_monolith.a
script: unix
@@ -246,6 +250,7 @@ jobs:
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- os: windows-2022
rid: win-x64
+ dotnet_architecture: x64
cpu: x64
monolith: v8_monolith.lib
script: windows
@@ -267,6 +272,7 @@ jobs:
with:
global-json-file: global.json
dotnet-version: 8.0.x
+ architecture: ${{ matrix.dotnet_architecture }}
- id: v8-cache-key
name: Resolve pinned V8 SDK cache identity
shell: bash
@@ -282,6 +288,8 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/args.gn
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/${{ matrix.monolith }}
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a
+ artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++/src/include
+ artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++abi/src/include
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/icudtl.dat
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/gen/third_party/partition_alloc/src
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
@@ -305,7 +313,9 @@ jobs:
}
libcxx_is_compatible() {
[[ '${{ matrix.rid }}' != linux-* ]] \
- || [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]]
+ || { [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]] \
+ && [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
+ && [[ -f "$root/third_party/libc++abi/src/include/cxxabi.h" ]]; }
}
args_are_compatible() {
grep -Eq '^v8_enable_pointer_compression *= *true$' "$args" \
@@ -434,7 +444,9 @@ jobs:
}
libcxx_is_compatible() {
[[ '${{ matrix.rid }}' != linux-* ]] \
- || [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]]
+ || { [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]] \
+ && [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
+ && [[ -f "$root/third_party/libc++abi/src/include/cxxabi.h" ]]; }
}
args_are_compatible() {
grep -Eq '^v8_enable_pointer_compression *= *true$' "$args" \
@@ -474,6 +486,8 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/args.gn
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/${{ matrix.monolith }}
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a
+ artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++/src/include
+ artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++abi/src/include
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/icudtl.dat
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/gen/third_party/partition_alloc/src
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 20500d739..9563c299f 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -162,7 +162,7 @@ if [[ "$expected_kernel" == Darwin ]]; then
rust_version=1.90.0
rust_mac_arm64_sha256=9772d20d5cd736079a0ee84d00e6697cf2084f0fc4621b011e24e6f2d08d2d7f
rust_mac_x64_std_sha256=dd731e6f9f30cb9b2928b92b084d2f12a3abf06a481ecbd8c3553c3e6f742139
- rust_prefix="${RUNNER_TEMP:-$repo_root/artifacts/toolchains}/webscene-rust-$rust_version"
+ rust_prefix="${RUNNER_TOOL_CACHE:-${RUNNER_TEMP:-$repo_root/artifacts/toolchains}}/webscene-rust-$rust_version"
rust_complete="$rust_prefix/.webscene-complete"
if [[ ! -f "$rust_complete" ]]; then
rust_download_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/webscene-rust.XXXXXX")"
@@ -170,11 +170,15 @@ if [[ "$expected_kernel" == Darwin ]]; then
cd "$rust_download_dir"
host_archive="rust-$rust_version-aarch64-apple-darwin.tar.xz"
x64_std_archive="rust-std-$rust_version-x86_64-apple-darwin.tar.xz"
- curl -fsSLO "https://static.rust-lang.org/dist/$host_archive"
+ curl --fail --silent --show-error --location \
+ --retry 5 --retry-delay 2 --retry-all-errors --connect-timeout 20 \
+ --remote-name "https://static.rust-lang.org/dist/$host_archive"
echo "$rust_mac_arm64_sha256 $host_archive" | shasum -a 256 -c -
tar -xf "$host_archive"
"${host_archive%.tar.xz}/install.sh" --prefix="$rust_prefix" --without=rust-docs
- curl -fsSLO "https://static.rust-lang.org/dist/$x64_std_archive"
+ curl --fail --silent --show-error --location \
+ --retry 5 --retry-delay 2 --retry-all-errors --connect-timeout 20 \
+ --remote-name "https://static.rust-lang.org/dist/$x64_std_archive"
echo "$rust_mac_x64_std_sha256 $x64_std_archive" | shasum -a 256 -c -
tar -xf "$x64_std_archive"
"${x64_std_archive%.tar.xz}/install.sh" --prefix="$rust_prefix"
@@ -509,7 +513,7 @@ elif [[ "$expected_kernel" == Linux ]]; then
fi
target_library_dir="$sysroot/usr/lib/$target_triple"
target_include_dir="$sysroot/usr/include"
- v8_libcxx_include="$v8_root/buildtools/third_party/libc++/src/include"
+ v8_libcxx_include="$v8_root/third_party/libc++/src/include"
v8_libcxxabi_include="$v8_root/third_party/libc++abi/src/include"
v8_libcxx_archive="$v8_output_root/obj/buildtools/third_party/libc++/libc++.a"
for target_dependency in \
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 730a39e07..bfed451e9 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -85,7 +85,8 @@ def test_toolchain_exposes_target_multiarch_search_paths(self) -> None:
self.assertIn('/usr/include/${WEBSCENE_LINUX_TARGET_TRIPLE}', self.toolchain)
def test_linux_runtime_uses_v8_bundled_libcxx(self) -> None:
- self.assertIn("buildtools/third_party/libc++/src/include", self.build_script)
+ self.assertIn('v8_root/third_party/libc++/src/include', self.build_script)
+ self.assertIn('v8_root/third_party/libc++abi/src/include', self.build_script)
self.assertIn("-nostdinc++ -nostdlib++", self.build_script)
self.assertIn("CMAKE_CXX_STANDARD_LIBRARIES", self.build_script)
@@ -94,6 +95,10 @@ def test_arm_mac_can_cross_build_intel_runtime(self) -> None:
self.assertIn('-DCMAKE_OSX_ARCHITECTURES="$macos_architecture"', self.build_script)
self.assertIn("x86_64-apple-darwin", self.build_script)
self.assertIn("rust-std-$rust_version-x86_64-apple-darwin", self.build_script)
+ self.assertIn("dotnet_architecture: x64", self.workflow)
+ self.assertIn("architecture: ${{ matrix.dotnet_architecture }}", self.workflow)
+ self.assertIn("RUNNER_TOOL_CACHE", self.build_script)
+ self.assertIn("--retry-all-errors", self.build_script)
if __name__ == "__main__":
From 8ad93dbc3bd995e99b8673cfbe41648570759a79 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 19:51:08 +0100
Subject: [PATCH 17/41] fix(linux): include V8 libc++ configuration
---
.github/workflows/native-runtime-packages.yml | 4 ++++
scripts/build-native-engine-runtime.sh | 4 +++-
scripts/tests/test_linux_build_policy.py | 2 ++
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 67faff1ec..180374c98 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -288,6 +288,7 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/args.gn
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/${{ matrix.monolith }}
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a
+ artifacts/native-engine-v8/${{ matrix.rid }}/v8/buildtools/third_party/libc++/__config_site
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++/src/include
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++abi/src/include
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/icudtl.dat
@@ -314,6 +315,7 @@ jobs:
libcxx_is_compatible() {
[[ '${{ matrix.rid }}' != linux-* ]] \
|| { [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]] \
+ && [[ -f "$root/buildtools/third_party/libc++/__config_site" ]] \
&& [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
&& [[ -f "$root/third_party/libc++abi/src/include/cxxabi.h" ]]; }
}
@@ -445,6 +447,7 @@ jobs:
libcxx_is_compatible() {
[[ '${{ matrix.rid }}' != linux-* ]] \
|| { [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]] \
+ && [[ -f "$root/buildtools/third_party/libc++/__config_site" ]] \
&& [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
&& [[ -f "$root/third_party/libc++abi/src/include/cxxabi.h" ]]; }
}
@@ -486,6 +489,7 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/args.gn
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/${{ matrix.monolith }}
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a
+ artifacts/native-engine-v8/${{ matrix.rid }}/v8/buildtools/third_party/libc++/__config_site
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++/src/include
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++abi/src/include
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/icudtl.dat
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 9563c299f..724a04c53 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -513,6 +513,7 @@ elif [[ "$expected_kernel" == Linux ]]; then
fi
target_library_dir="$sysroot/usr/lib/$target_triple"
target_include_dir="$sysroot/usr/include"
+ v8_libcxx_config_include="$v8_root/buildtools/third_party/libc++"
v8_libcxx_include="$v8_root/third_party/libc++/src/include"
v8_libcxxabi_include="$v8_root/third_party/libc++abi/src/include"
v8_libcxx_archive="$v8_output_root/obj/buildtools/third_party/libc++/libc++.a"
@@ -522,6 +523,7 @@ elif [[ "$expected_kernel" == Linux ]]; then
"$target_library_dir/libssl.so" \
"$target_include_dir/zlib.h" \
"$target_library_dir/libz.so" \
+ "$v8_libcxx_config_include/__config_site" \
"$v8_libcxx_include/source_location" \
"$v8_libcxxabi_include/cxxabi.h" \
"$v8_libcxx_archive"; do
@@ -542,7 +544,7 @@ elif [[ "$expected_kernel" == Linux ]]; then
-DZLIB_INCLUDE_DIR="$target_include_dir"
-DZLIB_LIBRARY="$target_library_dir/libz.so"
"-DCMAKE_C_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
- "-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=. -nostdinc++ -nostdlib++ -isystem$v8_libcxx_include -isystem$v8_libcxxabi_include"
+ "-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=. -nostdinc++ -nostdlib++ -I$v8_libcxx_config_include -isystem$v8_libcxx_include -isystem$v8_libcxxabi_include"
-DCMAKE_CXX_STANDARD_LIBRARIES="$v8_libcxx_archive"
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld
"-DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld -Wl,--build-id=sha1"
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index bfed451e9..3f40b6163 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -87,6 +87,8 @@ def test_toolchain_exposes_target_multiarch_search_paths(self) -> None:
def test_linux_runtime_uses_v8_bundled_libcxx(self) -> None:
self.assertIn('v8_root/third_party/libc++/src/include', self.build_script)
self.assertIn('v8_root/third_party/libc++abi/src/include', self.build_script)
+ self.assertIn('v8_root/buildtools/third_party/libc++', self.build_script)
+ self.assertIn('__config_site', self.build_script)
self.assertIn("-nostdinc++ -nostdlib++", self.build_script)
self.assertIn("CMAKE_CXX_STANDARD_LIBRARIES", self.build_script)
From 1e8195d6b630291887d36991463f749f6689dd0b Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 20:10:06 +0100
Subject: [PATCH 18/41] fix(ci): isolate Linux V8 cache payload
---
.github/workflows/native-runtime-packages.yml | 19 +++++++++++++------
scripts/tests/test_linux_build_policy.py | 5 +++++
2 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 180374c98..eab2713a0 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -210,6 +210,7 @@ jobs:
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
v8_cache_generation: v10-v8-15.3.10-pa-system-libcxx-macos-inspector
+ v8_cache_extra_paths: ''
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
- os: [self-hosted, macOS, ARM64]
@@ -222,6 +223,7 @@ jobs:
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
v8_cache_generation: v10-v8-15.3.10-pa-system-libcxx-macos-inspector
+ v8_cache_extra_paths: ''
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt
- os: [self-hosted, Linux, X64]
@@ -234,6 +236,10 @@ jobs:
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
v8_cache_generation: v2-v8-15.3.10-glibc227-cross-x64
+ v8_cache_extra_paths: |
+ artifacts/native-engine-v8/linux-*/v8/buildtools/third_party/libc++/__config_site
+ artifacts/native-engine-v8/linux-*/v8/third_party/libc++/src/include
+ artifacts/native-engine-v8/linux-*/v8/third_party/libc++abi/src/include
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- os: [self-hosted, Linux, X64]
@@ -246,6 +252,10 @@ jobs:
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
v8_cache_generation: v3-v8-15.3.10-glibc227-cross-arm64
+ v8_cache_extra_paths: |
+ artifacts/native-engine-v8/linux-*/v8/buildtools/third_party/libc++/__config_site
+ artifacts/native-engine-v8/linux-*/v8/third_party/libc++/src/include
+ artifacts/native-engine-v8/linux-*/v8/third_party/libc++abi/src/include
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- os: windows-2022
@@ -258,6 +268,7 @@ jobs:
partition_alloc: true
v8_configuration: ReleasePartitionAlloc
v8_cache_generation: v9-v8-15.3.10-pa-windows-compat-inspector
+ v8_cache_extra_paths: ''
v8_cache_script: scripts/build-native-engine-runtime.ps1
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8WindowsCompatibilityPatch.txt
runs-on: ${{ matrix.os }}
@@ -288,9 +299,7 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/args.gn
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/${{ matrix.monolith }}
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a
- artifacts/native-engine-v8/${{ matrix.rid }}/v8/buildtools/third_party/libc++/__config_site
- artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++/src/include
- artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++abi/src/include
+ ${{ matrix.v8_cache_extra_paths }}
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/icudtl.dat
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/gen/third_party/partition_alloc/src
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
@@ -489,9 +498,7 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/args.gn
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/${{ matrix.monolith }}
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a
- artifacts/native-engine-v8/${{ matrix.rid }}/v8/buildtools/third_party/libc++/__config_site
- artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++/src/include
- artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/libc++abi/src/include
+ ${{ matrix.v8_cache_extra_paths }}
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/icudtl.dat
artifacts/native-engine-v8/${{ matrix.rid }}/v8/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/gen/third_party/partition_alloc/src
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 3f40b6163..c0ab94084 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -58,6 +58,11 @@ def test_release_matrix_contains_both_glibc_rids(self) -> None:
self.assertIn(f"--native-rid {rid}", self.workflow)
self.assertIn("github.ref_type != 'tag'", self.workflow)
+ def test_linux_libcxx_cache_paths_do_not_invalidate_macos_caches(self) -> None:
+ self.assertEqual(2, self.workflow.count("v8_cache_extra_paths: |"))
+ self.assertEqual(3, self.workflow.count("v8_cache_extra_paths: ''"))
+ self.assertEqual(2, self.workflow.count("${{ matrix.v8_cache_extra_paths }}"))
+
def test_arm64_disables_memory_tagging_for_glibc_227(self) -> None:
self.assertIn(
"PA_BUILDFLAG_INTERNAL_HAS_MEMORY_TAGGING() (0)",
From 83a1ad8d4c7ca2efcb5f64f8bb1f1bead17e283e Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 21:15:29 +0100
Subject: [PATCH 19/41] fix(linux): align WebScene with V8 toolchain
---
.github/workflows/native-runtime-packages.yml | 14 +++++-
scripts/build-native-engine-runtime.sh | 49 ++++++++++++++-----
scripts/linux-glibc-toolchain.cmake | 8 ++-
scripts/tests/test_linux_build_policy.py | 3 ++
4 files changed, 58 insertions(+), 16 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index eab2713a0..4e178e00d 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -238,8 +238,10 @@ jobs:
v8_cache_generation: v2-v8-15.3.10-glibc227-cross-x64
v8_cache_extra_paths: |
artifacts/native-engine-v8/linux-*/v8/buildtools/third_party/libc++/__config_site
+ artifacts/native-engine-v8/linux-*/v8/out/*/*/obj/buildtools/third_party/libc++abi/libc++abi.a
artifacts/native-engine-v8/linux-*/v8/third_party/libc++/src/include
artifacts/native-engine-v8/linux-*/v8/third_party/libc++abi/src/include
+ artifacts/native-engine-v8/linux-*/v8/third_party/llvm-build/Release+Asserts
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- os: [self-hosted, Linux, X64]
@@ -254,8 +256,10 @@ jobs:
v8_cache_generation: v3-v8-15.3.10-glibc227-cross-arm64
v8_cache_extra_paths: |
artifacts/native-engine-v8/linux-*/v8/buildtools/third_party/libc++/__config_site
+ artifacts/native-engine-v8/linux-*/v8/out/*/*/obj/buildtools/third_party/libc++abi/libc++abi.a
artifacts/native-engine-v8/linux-*/v8/third_party/libc++/src/include
artifacts/native-engine-v8/linux-*/v8/third_party/libc++abi/src/include
+ artifacts/native-engine-v8/linux-*/v8/third_party/llvm-build/Release+Asserts
v8_cache_script: scripts/build-native-engine-runtime.sh
v8_cache_patch: packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt
- os: windows-2022
@@ -324,9 +328,12 @@ jobs:
libcxx_is_compatible() {
[[ '${{ matrix.rid }}' != linux-* ]] \
|| { [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]] \
+ && [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++abi/libc++abi.a" ]] \
&& [[ -f "$root/buildtools/third_party/libc++/__config_site" ]] \
&& [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
- && [[ -f "$root/third_party/libc++abi/src/include/cxxabi.h" ]]; }
+ && [[ -f "$root/third_party/libc++abi/src/include/cxxabi.h" ]] \
+ && [[ -x "$root/third_party/llvm-build/Release+Asserts/bin/clang++" ]] \
+ && [[ -x "$root/third_party/llvm-build/Release+Asserts/bin/ld.lld" ]]; }
}
args_are_compatible() {
grep -Eq '^v8_enable_pointer_compression *= *true$' "$args" \
@@ -456,9 +463,12 @@ jobs:
libcxx_is_compatible() {
[[ '${{ matrix.rid }}' != linux-* ]] \
|| { [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]] \
+ && [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++abi/libc++abi.a" ]] \
&& [[ -f "$root/buildtools/third_party/libc++/__config_site" ]] \
&& [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
- && [[ -f "$root/third_party/libc++abi/src/include/cxxabi.h" ]]; }
+ && [[ -f "$root/third_party/libc++abi/src/include/cxxabi.h" ]] \
+ && [[ -x "$root/third_party/llvm-build/Release+Asserts/bin/clang++" ]] \
+ && [[ -x "$root/third_party/llvm-build/Release+Asserts/bin/ld.lld" ]]; }
}
args_are_compatible() {
grep -Eq '^v8_enable_pointer_compression *= *true$' "$args" \
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 724a04c53..9e31a0d7d 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -341,7 +341,16 @@ if [[ -z "$v8_root" ]]; then
exit 1
fi
fi
- ninja -C "out/$cpu/$v8_configuration" obj/libv8_monolith.a
+ v8_ninja_targets=(obj/libv8_monolith.a)
+ if [[ "$expected_kernel" == Linux ]]; then
+ # Cross builds need target-architecture C++ runtime archives in the
+ # primary toolchain. V8's ARM64 monolith otherwise builds libc++ only for
+ # the x64 host-tools toolchain used by mksnapshot.
+ v8_ninja_targets+=(
+ obj/buildtools/third_party/libc++/libc++.a
+ obj/buildtools/third_party/libc++abi/libc++abi.a)
+ fi
+ ninja -C "out/$cpu/$v8_configuration" "${v8_ninja_targets[@]}"
)
v8_output_root="$v8_root/out/$cpu/$v8_configuration"
fi
@@ -503,20 +512,18 @@ if [[ "$thin_lto" == true ]]; then
-DCMAKE_MODULE_LINKER_FLAGS=-fuse-ld=lld
)
elif [[ "$expected_kernel" == Linux ]]; then
- # V8's Linux archive must be linked with LLD. The compiler is selectable so
- # the Ubuntu 22.04 compatibility image can use GCC 11's complete C++20
- # standard library instead of Jammy's Clang 14 source_location support.
- linux_cxx="${CXX:-clang++}"
- if ! command -v "$linux_cxx" >/dev/null 2>&1 || ! command -v ld.lld >/dev/null 2>&1; then
- echo "Linux native runtime builds require '$linux_cxx' and ld.lld." >&2
- exit 1
- fi
+ # Compile the embedding library with the exact Chromium LLVM and libc++
+ # revision used for V8. New libc++ headers can require compiler features and
+ # configuration defines absent from the builder image's host toolchain.
target_library_dir="$sysroot/usr/lib/$target_triple"
target_include_dir="$sysroot/usr/include"
v8_libcxx_config_include="$v8_root/buildtools/third_party/libc++"
v8_libcxx_include="$v8_root/third_party/libc++/src/include"
v8_libcxxabi_include="$v8_root/third_party/libc++abi/src/include"
v8_libcxx_archive="$v8_output_root/obj/buildtools/third_party/libc++/libc++.a"
+ v8_libcxxabi_archive="$v8_output_root/obj/buildtools/third_party/libc++abi/libc++abi.a"
+ v8_llvm_root="$v8_root/third_party/llvm-build/Release+Asserts"
+ v8_llvm_bin="$v8_llvm_root/bin"
for target_dependency in \
"$target_include_dir/openssl/ssl.h" \
"$target_library_dir/libcrypto.so" \
@@ -526,15 +533,33 @@ elif [[ "$expected_kernel" == Linux ]]; then
"$v8_libcxx_config_include/__config_site" \
"$v8_libcxx_include/source_location" \
"$v8_libcxxabi_include/cxxabi.h" \
- "$v8_libcxx_archive"; do
+ "$v8_libcxx_archive" \
+ "$v8_libcxxabi_archive" \
+ "$v8_llvm_bin/clang" \
+ "$v8_llvm_bin/clang++" \
+ "$v8_llvm_bin/llvm-ar" \
+ "$v8_llvm_bin/ld.lld"; do
if [[ ! -e "$target_dependency" ]]; then
echo "Linux sysroot is missing required native dependency '$target_dependency'." >&2
exit 1
fi
done
+ v8_llvm_ranlib="$v8_llvm_bin/llvm-ranlib"
+ if [[ ! -x "$v8_llvm_ranlib" ]]; then
+ ln -s "$v8_llvm_bin/llvm-ar" "$v8_llvm_ranlib"
+ fi
cmake_args+=(
-DCMAKE_TOOLCHAIN_FILE="$repo_root/scripts/linux-glibc-toolchain.cmake"
-DCMAKE_SYSROOT="$sysroot"
+ -DCMAKE_C_COMPILER="$v8_llvm_bin/clang"
+ -DCMAKE_CXX_COMPILER="$v8_llvm_bin/clang++"
+ -DCMAKE_AR="$v8_llvm_bin/llvm-ar"
+ -DCMAKE_RANLIB="$v8_llvm_ranlib"
+ -DCMAKE_C_COMPILER_AR="$v8_llvm_bin/llvm-ar"
+ -DCMAKE_C_COMPILER_RANLIB="$v8_llvm_ranlib"
+ -DCMAKE_CXX_COMPILER_AR="$v8_llvm_bin/llvm-ar"
+ -DCMAKE_CXX_COMPILER_RANLIB="$v8_llvm_ranlib"
+ -DCMAKE_LINKER="$v8_llvm_bin/ld.lld"
-DWEBSCENE_LINUX_TARGET_TRIPLE="$target_triple"
-DWEBSCENE_RUST_TARGET_TRIPLE="$rust_target_triple"
-DOPENSSL_ROOT_DIR="$sysroot/usr"
@@ -544,8 +569,8 @@ elif [[ "$expected_kernel" == Linux ]]; then
-DZLIB_INCLUDE_DIR="$target_include_dir"
-DZLIB_LIBRARY="$target_library_dir/libz.so"
"-DCMAKE_C_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
- "-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=. -nostdinc++ -nostdlib++ -I$v8_libcxx_config_include -isystem$v8_libcxx_include -isystem$v8_libcxxabi_include"
- -DCMAKE_CXX_STANDARD_LIBRARIES="$v8_libcxx_archive"
+ "-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=. -nostdinc++ -nostdlib++ -I$v8_libcxx_config_include -isystem$v8_libcxx_include -isystem$v8_libcxxabi_include -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCPP_INSTRUMENTED_WITH_ASAN=0 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE"
+ "-DCMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive;$v8_libcxxabi_archive"
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld
"-DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld -Wl,--build-id=sha1"
)
diff --git a/scripts/linux-glibc-toolchain.cmake b/scripts/linux-glibc-toolchain.cmake
index 923707e07..6a204939a 100644
--- a/scripts/linux-glibc-toolchain.cmake
+++ b/scripts/linux-glibc-toolchain.cmake
@@ -33,8 +33,12 @@ list(APPEND CMAKE_SYSTEM_INCLUDE_PATH
"/usr/include/${WEBSCENE_LINUX_TARGET_TRIPLE}"
"/usr/include")
-set(CMAKE_C_COMPILER clang)
-set(CMAKE_CXX_COMPILER clang++)
+if(NOT DEFINED CMAKE_C_COMPILER)
+ set(CMAKE_C_COMPILER clang)
+endif()
+if(NOT DEFINED CMAKE_CXX_COMPILER)
+ set(CMAKE_CXX_COMPILER clang++)
+endif()
set(CMAKE_C_COMPILER_TARGET "${WEBSCENE_LINUX_TARGET_TRIPLE}")
set(CMAKE_CXX_COMPILER_TARGET "${WEBSCENE_LINUX_TARGET_TRIPLE}")
set(CMAKE_FIND_ROOT_PATH "${CMAKE_SYSROOT}")
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index c0ab94084..380e43927 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -96,6 +96,9 @@ def test_linux_runtime_uses_v8_bundled_libcxx(self) -> None:
self.assertIn('__config_site', self.build_script)
self.assertIn("-nostdinc++ -nostdlib++", self.build_script)
self.assertIn("CMAKE_CXX_STANDARD_LIBRARIES", self.build_script)
+ self.assertIn("libc++abi.a", self.build_script)
+ self.assertIn("third_party/llvm-build/Release+Asserts", self.build_script)
+ self.assertIn("_LIBCPP_HARDENING_MODE_EXTENSIVE", self.build_script)
def test_arm_mac_can_cross_build_intel_runtime(self) -> None:
self.assertIn("macos_arm64_to_x64=true", self.build_script)
From 09c0de6ecd0a63c002651bb9709d5e3e01ee5f75 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 22:49:12 +0100
Subject: [PATCH 20/41] fix(linux): correct libc++ embedding flags
---
scripts/build-native-engine-runtime.sh | 4 ++--
scripts/tests/test_linux_build_policy.py | 5 +++++
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 9e31a0d7d..dc56e025d 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -569,8 +569,8 @@ elif [[ "$expected_kernel" == Linux ]]; then
-DZLIB_INCLUDE_DIR="$target_include_dir"
-DZLIB_LIBRARY="$target_library_dir/libz.so"
"-DCMAKE_C_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
- "-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=. -nostdinc++ -nostdlib++ -I$v8_libcxx_config_include -isystem$v8_libcxx_include -isystem$v8_libcxxabi_include -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCPP_INSTRUMENTED_WITH_ASAN=0 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE"
- "-DCMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive;$v8_libcxxabi_archive"
+ "-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=. -nostdinc++ -nostdlib++ -I$v8_libcxx_config_include -isystem$v8_libcxx_include -isystem$v8_libcxxabi_include -include new -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCPP_INSTRUMENTED_WITH_ASAN=0 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE"
+ "-DCMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive $v8_libcxxabi_archive"
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld
"-DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld -Wl,--build-id=sha1"
)
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 380e43927..139cb69e9 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -99,6 +99,11 @@ def test_linux_runtime_uses_v8_bundled_libcxx(self) -> None:
self.assertIn("libc++abi.a", self.build_script)
self.assertIn("third_party/llvm-build/Release+Asserts", self.build_script)
self.assertIn("_LIBCPP_HARDENING_MODE_EXTENSIVE", self.build_script)
+ self.assertIn("-include new", self.build_script)
+ self.assertNotIn(
+ 'CMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive;$v8_libcxxabi_archive',
+ self.build_script,
+ )
def test_arm_mac_can_cross_build_intel_runtime(self) -> None:
self.assertIn("macos_arm64_to_x64=true", self.build_script)
From eb56c8b01e2b834df266ae3f146d366a1c1a848c Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Mon, 21 Sep 2026 23:49:16 +0100
Subject: [PATCH 21/41] fix(linux): cache complete libc++ configuration
---
.github/workflows/native-runtime-packages.yml | 6 ++++--
scripts/build-native-engine-runtime.sh | 1 +
scripts/tests/test_linux_build_policy.py | 5 +++++
3 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 4e178e00d..eaf95f182 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -237,7 +237,7 @@ jobs:
v8_configuration: ReleasePartitionAlloc
v8_cache_generation: v2-v8-15.3.10-glibc227-cross-x64
v8_cache_extra_paths: |
- artifacts/native-engine-v8/linux-*/v8/buildtools/third_party/libc++/__config_site
+ artifacts/native-engine-v8/linux-*/v8/buildtools/third_party/libc++
artifacts/native-engine-v8/linux-*/v8/out/*/*/obj/buildtools/third_party/libc++abi/libc++abi.a
artifacts/native-engine-v8/linux-*/v8/third_party/libc++/src/include
artifacts/native-engine-v8/linux-*/v8/third_party/libc++abi/src/include
@@ -255,7 +255,7 @@ jobs:
v8_configuration: ReleasePartitionAlloc
v8_cache_generation: v3-v8-15.3.10-glibc227-cross-arm64
v8_cache_extra_paths: |
- artifacts/native-engine-v8/linux-*/v8/buildtools/third_party/libc++/__config_site
+ artifacts/native-engine-v8/linux-*/v8/buildtools/third_party/libc++
artifacts/native-engine-v8/linux-*/v8/out/*/*/obj/buildtools/third_party/libc++abi/libc++abi.a
artifacts/native-engine-v8/linux-*/v8/third_party/libc++/src/include
artifacts/native-engine-v8/linux-*/v8/third_party/libc++abi/src/include
@@ -330,6 +330,7 @@ jobs:
|| { [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]] \
&& [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++abi/libc++abi.a" ]] \
&& [[ -f "$root/buildtools/third_party/libc++/__config_site" ]] \
+ && [[ -f "$root/buildtools/third_party/libc++/__assertion_handler" ]] \
&& [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
&& [[ -f "$root/third_party/libc++abi/src/include/cxxabi.h" ]] \
&& [[ -x "$root/third_party/llvm-build/Release+Asserts/bin/clang++" ]] \
@@ -465,6 +466,7 @@ jobs:
|| { [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]] \
&& [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++abi/libc++abi.a" ]] \
&& [[ -f "$root/buildtools/third_party/libc++/__config_site" ]] \
+ && [[ -f "$root/buildtools/third_party/libc++/__assertion_handler" ]] \
&& [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
&& [[ -f "$root/third_party/libc++abi/src/include/cxxabi.h" ]] \
&& [[ -x "$root/third_party/llvm-build/Release+Asserts/bin/clang++" ]] \
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index dc56e025d..6134b7e21 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -531,6 +531,7 @@ elif [[ "$expected_kernel" == Linux ]]; then
"$target_include_dir/zlib.h" \
"$target_library_dir/libz.so" \
"$v8_libcxx_config_include/__config_site" \
+ "$v8_libcxx_config_include/__assertion_handler" \
"$v8_libcxx_include/source_location" \
"$v8_libcxxabi_include/cxxabi.h" \
"$v8_libcxx_archive" \
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 139cb69e9..e5a2b9b3e 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -94,6 +94,11 @@ def test_linux_runtime_uses_v8_bundled_libcxx(self) -> None:
self.assertIn('v8_root/third_party/libc++abi/src/include', self.build_script)
self.assertIn('v8_root/buildtools/third_party/libc++', self.build_script)
self.assertIn('__config_site', self.build_script)
+ self.assertIn('__assertion_handler', self.build_script)
+ self.assertIn(
+ 'artifacts/native-engine-v8/linux-*/v8/buildtools/third_party/libc++',
+ self.workflow,
+ )
self.assertIn("-nostdinc++ -nostdlib++", self.build_script)
self.assertIn("CMAKE_CXX_STANDARD_LIBRARIES", self.build_script)
self.assertIn("libc++abi.a", self.build_script)
From 5bc54b547782b9223dad2a19fcabd4e7db917feb Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 08:17:32 +0100
Subject: [PATCH 22/41] fix(linux): link POSIX thread runtime
---
scripts/build-native-engine-runtime.sh | 2 +-
scripts/tests/test_linux_build_policy.py | 5 +++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 6134b7e21..3b62aac90 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -571,7 +571,7 @@ elif [[ "$expected_kernel" == Linux ]]; then
-DZLIB_LIBRARY="$target_library_dir/libz.so"
"-DCMAKE_C_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
"-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=. -nostdinc++ -nostdlib++ -I$v8_libcxx_config_include -isystem$v8_libcxx_include -isystem$v8_libcxxabi_include -include new -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCPP_INSTRUMENTED_WITH_ASAN=0 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE"
- "-DCMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive $v8_libcxxabi_archive"
+ "-DCMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive $v8_libcxxabi_archive -pthread"
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld
"-DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld -Wl,--build-id=sha1"
)
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index e5a2b9b3e..30a7b2a45 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -109,6 +109,11 @@ def test_linux_runtime_uses_v8_bundled_libcxx(self) -> None:
'CMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive;$v8_libcxxabi_archive',
self.build_script,
)
+ self.assertIn(
+ 'CMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive '
+ '$v8_libcxxabi_archive -pthread',
+ self.build_script,
+ )
def test_arm_mac_can_cross_build_intel_runtime(self) -> None:
self.assertIn("macos_arm64_to_x64=true", self.build_script)
From 1fdf96aeeac7729d7725cb6f27114dd79f9aa4e6 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 08:34:58 +0100
Subject: [PATCH 23/41] fix(linux): materialize cached libc++ archives
---
.github/workflows/native-runtime-packages.yml | 14 +++++---
scripts/build-native-engine-runtime.sh | 35 +++++++++++++++++++
scripts/tests/test_linux_build_policy.py | 3 ++
3 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index eaf95f182..73d70b7c1 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -326,9 +326,12 @@ jobs:
&& [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/gen/third_party/partition_alloc/src/partition_alloc/buildflags.h" ]]; }
}
libcxx_is_compatible() {
+ archive_is_regular() {
+ [[ -f "$1" ]] && [[ "$(head -c 7 "$1")" == '!' ]]
+ }
[[ '${{ matrix.rid }}' != linux-* ]] \
- || { [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]] \
- && [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++abi/libc++abi.a" ]] \
+ || { archive_is_regular "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" \
+ && archive_is_regular "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++abi/libc++abi.a" \
&& [[ -f "$root/buildtools/third_party/libc++/__config_site" ]] \
&& [[ -f "$root/buildtools/third_party/libc++/__assertion_handler" ]] \
&& [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
@@ -462,9 +465,12 @@ jobs:
&& [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/gen/third_party/partition_alloc/src/partition_alloc/buildflags.h" ]]; }
}
libcxx_is_compatible() {
+ archive_is_regular() {
+ [[ -f "$1" ]] && [[ "$(head -c 7 "$1")" == '!' ]]
+ }
[[ '${{ matrix.rid }}' != linux-* ]] \
- || { [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" ]] \
- && [[ -f "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++abi/libc++abi.a" ]] \
+ || { archive_is_regular "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" \
+ && archive_is_regular "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++abi/libc++abi.a" \
&& [[ -f "$root/buildtools/third_party/libc++/__config_site" ]] \
&& [[ -f "$root/buildtools/third_party/libc++/__assertion_handler" ]] \
&& [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 3b62aac90..27e3fc82f 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -351,6 +351,35 @@ if [[ -z "$v8_root" ]]; then
obj/buildtools/third_party/libc++abi/libc++abi.a)
fi
ninja -C "out/$cpu/$v8_configuration" "${v8_ninja_targets[@]}"
+ if [[ "$expected_kernel" == Linux ]]; then
+ # Chromium emits thin archives here. They only contain paths to the
+ # adjacent object files, so restoring just the archives from the V8 SDK
+ # cache makes the final WebScene link fail. Repack every member into a
+ # regular deterministic archive before the cache is populated.
+ llvm_ar="$v8_root/third_party/llvm-build/Release+Asserts/bin/llvm-ar"
+ for archive in \
+ "out/$cpu/$v8_configuration/obj/buildtools/third_party/libc++/libc++.a" \
+ "out/$cpu/$v8_configuration/obj/buildtools/third_party/libc++abi/libc++abi.a"; do
+ archive_dir="$(dirname "$archive")"
+ archive_name="$(basename "$archive")"
+ regular_archive="$archive_name.regular.$$"
+ (
+ cd "$archive_dir"
+ mapfile -t archive_members < <("$llvm_ar" t "$archive_name")
+ if (( ${#archive_members[@]} == 0 )); then
+ echo "V8 C++ runtime archive has no members: $archive" >&2
+ exit 1
+ fi
+ rm -f "$regular_archive"
+ "$llvm_ar" rcD "$regular_archive" "${archive_members[@]}"
+ if [[ "$(head -c 7 "$regular_archive")" != '!' ]]; then
+ echo "Failed to materialize regular V8 C++ runtime archive: $archive" >&2
+ exit 1
+ fi
+ mv "$regular_archive" "$archive_name"
+ )
+ done
+ fi
)
v8_output_root="$v8_root/out/$cpu/$v8_configuration"
fi
@@ -545,6 +574,12 @@ elif [[ "$expected_kernel" == Linux ]]; then
exit 1
fi
done
+ for runtime_archive in "$v8_libcxx_archive" "$v8_libcxxabi_archive"; do
+ if [[ "$(head -c 7 "$runtime_archive")" != '!' ]]; then
+ echo "Linux V8 C++ runtime dependency is not a self-contained regular archive: '$runtime_archive'." >&2
+ exit 1
+ fi
+ done
v8_llvm_ranlib="$v8_llvm_bin/llvm-ranlib"
if [[ ! -x "$v8_llvm_ranlib" ]]; then
ln -s "$v8_llvm_bin/llvm-ar" "$v8_llvm_ranlib"
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 30a7b2a45..c29020794 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -105,6 +105,9 @@ def test_linux_runtime_uses_v8_bundled_libcxx(self) -> None:
self.assertIn("third_party/llvm-build/Release+Asserts", self.build_script)
self.assertIn("_LIBCPP_HARDENING_MODE_EXTENSIVE", self.build_script)
self.assertIn("-include new", self.build_script)
+ self.assertIn('llvm_ar" rcD "$regular_archive"', self.build_script)
+ self.assertIn("'!'", self.build_script)
+ self.assertIn("archive_is_regular", self.workflow)
self.assertNotIn(
'CMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive;$v8_libcxxabi_archive',
self.build_script,
From 890877891d5f16ee0dd1a57c7ae82804bbabfb9b Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 09:46:56 +0100
Subject: [PATCH 24/41] fix(linux): include libc++ PMR runtime
---
.github/workflows/native-runtime-packages.yml | 14 ++++++++++++--
.../patches/V8LibcxxMemoryResourcePatch.txt | 12 ++++++++++++
scripts/build-native-engine-runtime.sh | 8 ++++++++
scripts/tests/test_linux_build_policy.py | 4 ++++
4 files changed, 36 insertions(+), 2 deletions(-)
create mode 100644 packaging/WebScene.NativeEngine.Runtime/patches/V8LibcxxMemoryResourcePatch.txt
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 73d70b7c1..2fd629bab 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -309,7 +309,7 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/icu/LICENSE
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/partition_alloc/src
- key: webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-${{ steps.v8-cache-key.outputs.image-version }}-${{ hashFiles(matrix.v8_cache_script, matrix.v8_cache_patch, 'packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocGlibc227Arm64Patch.txt', 'packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc', 'packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json', 'scripts/linux-glibc-toolchain.cmake') }}
+ key: webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-${{ steps.v8-cache-key.outputs.image-version }}-${{ hashFiles(matrix.v8_cache_script, matrix.v8_cache_patch, 'packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocGlibc227Arm64Patch.txt', 'packaging/WebScene.NativeEngine.Runtime/patches/V8LibcxxMemoryResourcePatch.txt', 'packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc', 'packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json', 'scripts/linux-glibc-toolchain.cmake') }}
restore-keys: |
webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-
webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-
@@ -329,9 +329,14 @@ jobs:
archive_is_regular() {
[[ -f "$1" ]] && [[ "$(head -c 7 "$1")" == '!' ]]
}
+ archive_has_memory_resource() {
+ "$root/third_party/llvm-build/Release+Asserts/bin/llvm-ar" t "$1" \
+ | grep -Eq '(^|/)memory_resource\.o$'
+ }
[[ '${{ matrix.rid }}' != linux-* ]] \
|| { archive_is_regular "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" \
&& archive_is_regular "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++abi/libc++abi.a" \
+ && archive_has_memory_resource "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" \
&& [[ -f "$root/buildtools/third_party/libc++/__config_site" ]] \
&& [[ -f "$root/buildtools/third_party/libc++/__assertion_handler" ]] \
&& [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
@@ -468,9 +473,14 @@ jobs:
archive_is_regular() {
[[ -f "$1" ]] && [[ "$(head -c 7 "$1")" == '!' ]]
}
+ archive_has_memory_resource() {
+ "$root/third_party/llvm-build/Release+Asserts/bin/llvm-ar" t "$1" \
+ | grep -Eq '(^|/)memory_resource\.o$'
+ }
[[ '${{ matrix.rid }}' != linux-* ]] \
|| { archive_is_regular "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" \
&& archive_is_regular "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++abi/libc++abi.a" \
+ && archive_has_memory_resource "$root/out/${{ matrix.cpu }}/${{ matrix.v8_configuration }}/obj/buildtools/third_party/libc++/libc++.a" \
&& [[ -f "$root/buildtools/third_party/libc++/__config_site" ]] \
&& [[ -f "$root/buildtools/third_party/libc++/__assertion_handler" ]] \
&& [[ -f "$root/third_party/libc++/src/include/source_location" ]] \
@@ -522,7 +532,7 @@ jobs:
artifacts/native-engine-v8/${{ matrix.rid }}/v8/LICENSE
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/icu/LICENSE
artifacts/native-engine-v8/${{ matrix.rid }}/v8/third_party/partition_alloc/src
- key: webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-${{ steps.v8-cache-key.outputs.image-version }}-${{ hashFiles(matrix.v8_cache_script, matrix.v8_cache_patch, 'packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc', 'packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json', 'scripts/linux-glibc-toolchain.cmake') }}
+ key: webscene-v8-sdk-${{ matrix.v8_cache_generation }}-${{ matrix.rid }}-${{ hashFiles('third-party/v8-patches/**') }}-${{ steps.v8-cache-key.outputs.image-version }}-${{ hashFiles(matrix.v8_cache_script, matrix.v8_cache_patch, 'packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocGlibc227Arm64Patch.txt', 'packaging/WebScene.NativeEngine.Runtime/patches/V8LibcxxMemoryResourcePatch.txt', 'packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc', 'packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json', 'scripts/linux-glibc-toolchain.cmake') }}
- name: Upload required compatibility evidence
if: success() && matrix.rid != 'linux-arm64'
uses: actions/upload-artifact@v4
diff --git a/packaging/WebScene.NativeEngine.Runtime/patches/V8LibcxxMemoryResourcePatch.txt b/packaging/WebScene.NativeEngine.Runtime/patches/V8LibcxxMemoryResourcePatch.txt
new file mode 100644
index 000000000..e6786ee22
--- /dev/null
+++ b/packaging/WebScene.NativeEngine.Runtime/patches/V8LibcxxMemoryResourcePatch.txt
@@ -0,0 +1,12 @@
+diff --git a/third_party/libc++/BUILD.gn b/third_party/libc++/BUILD.gn
+index 28a2db8..bfc9351 100644
+--- a/third_party/libc++/BUILD.gn
++++ b/third_party/libc++/BUILD.gn
+@@ -459,6 +459,7 @@ if (libcxx_is_shared) {
+ "//third_party/libc++/src/src/iostream.cpp",
+ "//third_party/libc++/src/src/locale.cpp",
+ "//third_party/libc++/src/src/memory.cpp",
++ "//third_party/libc++/src/src/memory_resource.cpp",
+ "//third_party/libc++/src/src/mutex.cpp",
+ "//third_party/libc++/src/src/mutex_destructor.cpp",
+ "//third_party/libc++/src/src/new_handler.cpp",
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 27e3fc82f..b81a1c2bb 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -289,6 +289,9 @@ if [[ -z "$v8_root" ]]; then
"$repo_root/packaging/WebScene.NativeEngine.Runtime/patches/V8PartitionAllocMacVisibilityPatch.txt"
fi
if [[ "$expected_kernel" == Linux ]]; then
+ apply_patch_once \
+ "$v8_root/buildtools" \
+ "$repo_root/packaging/WebScene.NativeEngine.Runtime/patches/V8LibcxxMemoryResourcePatch.txt"
apply_patch_once "$v8_root/build" "$repo_root/packaging/WebScene.NativeEngine.Runtime/patches/V8BuildNoCrelPatch.txt"
if [[ "$cpu" == arm64 ]]; then
apply_patch_once \
@@ -580,6 +583,11 @@ elif [[ "$expected_kernel" == Linux ]]; then
exit 1
fi
done
+ if ! "$v8_llvm_bin/llvm-ar" t "$v8_libcxx_archive" \
+ | grep -Eq '(^|/)memory_resource\.o$'; then
+ echo "Linux V8 libc++ archive does not provide std::pmr support: '$v8_libcxx_archive'." >&2
+ exit 1
+ fi
v8_llvm_ranlib="$v8_llvm_bin/llvm-ranlib"
if [[ ! -x "$v8_llvm_ranlib" ]]; then
ln -s "$v8_llvm_bin/llvm-ar" "$v8_llvm_ranlib"
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index c29020794..3e799303f 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -108,6 +108,10 @@ def test_linux_runtime_uses_v8_bundled_libcxx(self) -> None:
self.assertIn('llvm_ar" rcD "$regular_archive"', self.build_script)
self.assertIn("'!'", self.build_script)
self.assertIn("archive_is_regular", self.workflow)
+ self.assertIn("V8LibcxxMemoryResourcePatch.txt", self.build_script)
+ self.assertIn("V8LibcxxMemoryResourcePatch.txt", self.workflow)
+ self.assertIn("archive_has_memory_resource", self.workflow)
+ self.assertIn("memory_resource\\.o", self.build_script)
self.assertNotIn(
'CMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive;$v8_libcxxabi_archive',
self.build_script,
From 82ccb438ee2c54f8a500b860da9d35132773b1b4 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 11:07:29 +0100
Subject: [PATCH 25/41] fix(linux): make runtime dependencies portable
---
scripts/build-native-engine-runtime.sh | 13 +++++++------
scripts/tests/test_linux_build_policy.py | 7 ++++---
scripts/tests/test_verify_linux_native_abi.py | 15 +++++++++++++++
scripts/verify-linux-native-abi.py | 5 ++++-
4 files changed, 30 insertions(+), 10 deletions(-)
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index b81a1c2bb..19c83dfac 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -558,10 +558,10 @@ elif [[ "$expected_kernel" == Linux ]]; then
v8_llvm_bin="$v8_llvm_root/bin"
for target_dependency in \
"$target_include_dir/openssl/ssl.h" \
- "$target_library_dir/libcrypto.so" \
- "$target_library_dir/libssl.so" \
+ "$target_library_dir/libcrypto.a" \
+ "$target_library_dir/libssl.a" \
"$target_include_dir/zlib.h" \
- "$target_library_dir/libz.so" \
+ "$target_library_dir/libz.a" \
"$v8_libcxx_config_include/__config_site" \
"$v8_libcxx_config_include/__assertion_handler" \
"$v8_libcxx_include/source_location" \
@@ -608,10 +608,11 @@ elif [[ "$expected_kernel" == Linux ]]; then
-DWEBSCENE_RUST_TARGET_TRIPLE="$rust_target_triple"
-DOPENSSL_ROOT_DIR="$sysroot/usr"
-DOPENSSL_INCLUDE_DIR="$target_include_dir"
- -DOPENSSL_CRYPTO_LIBRARY="$target_library_dir/libcrypto.so"
- -DOPENSSL_SSL_LIBRARY="$target_library_dir/libssl.so"
+ -DOPENSSL_CRYPTO_LIBRARY="$target_library_dir/libcrypto.a"
+ -DOPENSSL_SSL_LIBRARY="$target_library_dir/libssl.a"
-DZLIB_INCLUDE_DIR="$target_include_dir"
- -DZLIB_LIBRARY="$target_library_dir/libz.so"
+ -DZLIB_LIBRARY="$target_library_dir/libz.a"
+ -DCMAKE_SKIP_RPATH=TRUE
"-DCMAKE_C_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=."
"-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$repo_root=. -fdebug-prefix-map=$repo_root=. -nostdinc++ -nostdlib++ -I$v8_libcxx_config_include -isystem$v8_libcxx_include -isystem$v8_libcxxabi_include -include new -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCPP_INSTRUMENTED_WITH_ASAN=0 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE"
"-DCMAKE_CXX_STANDARD_LIBRARIES=$v8_libcxx_archive $v8_libcxxabi_archive -pthread"
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 3e799303f..4774147b6 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -77,12 +77,13 @@ def test_cmake_try_compile_keeps_cross_target_identity(self) -> None:
def test_linux_openssl_is_resolved_only_from_the_target_sysroot(self) -> None:
self.assertIn('target_library_dir="$sysroot/usr/lib/$target_triple"', self.build_script)
- self.assertIn('-DOPENSSL_CRYPTO_LIBRARY="$target_library_dir/libcrypto.so"', self.build_script)
- self.assertIn('-DOPENSSL_SSL_LIBRARY="$target_library_dir/libssl.so"', self.build_script)
+ self.assertIn('-DOPENSSL_CRYPTO_LIBRARY="$target_library_dir/libcrypto.a"', self.build_script)
+ self.assertIn('-DOPENSSL_SSL_LIBRARY="$target_library_dir/libssl.a"', self.build_script)
def test_linux_zlib_is_resolved_only_from_the_target_sysroot(self) -> None:
self.assertIn('-DZLIB_INCLUDE_DIR="$target_include_dir"', self.build_script)
- self.assertIn('-DZLIB_LIBRARY="$target_library_dir/libz.so"', self.build_script)
+ self.assertIn('-DZLIB_LIBRARY="$target_library_dir/libz.a"', self.build_script)
+ self.assertIn('-DCMAKE_SKIP_RPATH=TRUE', self.build_script)
def test_toolchain_exposes_target_multiarch_search_paths(self) -> None:
self.assertIn("CMAKE_LIBRARY_ARCHITECTURE", self.toolchain)
diff --git a/scripts/tests/test_verify_linux_native_abi.py b/scripts/tests/test_verify_linux_native_abi.py
index 7ce071822..6f6fa0d64 100644
--- a/scripts/tests/test_verify_linux_native_abi.py
+++ b/scripts/tests/test_verify_linux_native_abi.py
@@ -56,6 +56,21 @@ def test_rejects_interpreter_on_shared_library(self) -> None:
self.assertEqual("fail", report["status"])
self.assertTrue(any("ELF interpreter" in issue for issue in report["issues"]))
+ def test_accepts_glibc_architecture_loader_dependency(self) -> None:
+ text = elf_text() + "\n 0x0 (NEEDED) Shared library: [ld-linux-aarch64.so.1]\n"
+ report = MODULE.verify_text(text, "linux-arm64", "2.27", "3.4.24", "1.3.11")
+ self.assertEqual("pass", report["status"], report)
+
+ def test_rejects_dynamic_openssl_and_zlib_dependencies(self) -> None:
+ text = elf_text() + """
+ 0x0 (NEEDED) Shared library: [libssl.so.1.1]
+ 0x0 (NEEDED) Shared library: [libcrypto.so.1.1]
+ 0x0 (NEEDED) Shared library: [libz.so.1]
+"""
+ report = MODULE.verify_text(text, "linux-arm64", "2.27", "3.4.24", "1.3.11")
+ self.assertEqual("fail", report["status"])
+ self.assertTrue(any("libssl.so.1.1" in issue for issue in report["issues"]))
+
if __name__ == "__main__":
unittest.main()
diff --git a/scripts/verify-linux-native-abi.py b/scripts/verify-linux-native-abi.py
index df2c87cc5..c65eae63d 100755
--- a/scripts/verify-linux-native-abi.py
+++ b/scripts/verify-linux-native-abi.py
@@ -13,6 +13,9 @@
ALLOWED_NEEDED = {
"libc.so.6", "libdl.so.2", "libgcc_s.so.1", "libm.so.6",
"libpthread.so.0", "librt.so.1", "libstdc++.so.6", "libutil.so.1",
+ # glibc's linker scripts can retain the architecture loader as an
+ # AS_NEEDED dependency. It is part of the glibc ABI on every target distro.
+ "ld-linux-aarch64.so.1", "ld-linux-x86-64.so.2",
}
EXPECTED_MACHINES = {
"linux-x64": "Advanced Micro Devices X86-64",
@@ -116,7 +119,7 @@ def main() -> int:
if line.strip() and not line.lstrip().startswith("#")
}
completed = subprocess.run(
- ["readelf", "-h", "-l", "-d", "--version-info", "--dyn-syms", str(args.library)],
+ ["readelf", "--wide", "-h", "-l", "-d", "--version-info", "--dyn-syms", str(args.library)],
check=False, capture_output=True, text=True,
)
if completed.returncode:
From de924a326cedffda673a203ad259594eb12d5482 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 11:19:28 +0100
Subject: [PATCH 26/41] fix(linux): expose build DSO to native tests
---
scripts/build-native-engine-runtime.sh | 10 +++++++++-
scripts/tests/test_linux_build_policy.py | 1 +
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 19c83dfac..aad1182be 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -639,7 +639,15 @@ if [[ "$finalize_only" == true ]]; then
"$build_dir/webscene_bootstrap_snapshot.meta"
fi
if [[ "$defer_target_execution" == false || "$finalize_only" == true ]]; then
- ctest --test-dir "$build_dir" -C "$cmake_build_type" --output-on-failure
+ if [[ "$expected_kernel" == Linux ]]; then
+ # Production DSOs intentionally contain no RPATH. Give native test
+ # executables an explicit, process-local route to the just-built DSO.
+ test_library_path="$build_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
+ cmake -E env "LD_LIBRARY_PATH=$test_library_path" \
+ ctest --test-dir "$build_dir" -C "$cmake_build_type" --output-on-failure
+ else
+ ctest --test-dir "$build_dir" -C "$cmake_build_type" --output-on-failure
+ fi
fi
native_path="$build_dir/$native_name"
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 4774147b6..a595d1a5f 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -84,6 +84,7 @@ def test_linux_zlib_is_resolved_only_from_the_target_sysroot(self) -> None:
self.assertIn('-DZLIB_INCLUDE_DIR="$target_include_dir"', self.build_script)
self.assertIn('-DZLIB_LIBRARY="$target_library_dir/libz.a"', self.build_script)
self.assertIn('-DCMAKE_SKIP_RPATH=TRUE', self.build_script)
+ self.assertIn('LD_LIBRARY_PATH=$test_library_path', self.build_script)
def test_toolchain_exposes_target_multiarch_search_paths(self) -> None:
self.assertIn("CMAKE_LIBRARY_ARCHITECTURE", self.toolchain)
From 5d3f56e27548934070a53af8753cfb9496edf919 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 11:30:36 +0100
Subject: [PATCH 27/41] fix(tests): resolve component fixtures from repository
---
experiments/WebScene.NativeEngine.Probe/CMakeLists.txt | 1 +
scripts/tests/test_linux_build_policy.py | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt b/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
index 5c1d08bbb..6d9322920 100644
--- a/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
+++ b/experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
@@ -601,6 +601,7 @@ if(WEBSCENE_NATIVE_ENGINE_ENABLE_V8)
ixwebsocket)
add_test(NAME webscene_native_engine_tests COMMAND webscene_native_engine_tests)
set_tests_properties(webscene_native_engine_tests PROPERTIES
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../.."
ENVIRONMENT
"WEBSCENE_V8_DETAILED_MEMORY_METRICS=1;WEBSCENE_INTEROP_STRESS=1")
endif()
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index a595d1a5f..faaaa4bec 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -85,6 +85,10 @@ def test_linux_zlib_is_resolved_only_from_the_target_sysroot(self) -> None:
self.assertIn('-DZLIB_LIBRARY="$target_library_dir/libz.a"', self.build_script)
self.assertIn('-DCMAKE_SKIP_RPATH=TRUE', self.build_script)
self.assertIn('LD_LIBRARY_PATH=$test_library_path', self.build_script)
+ self.assertIn(
+ 'WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../.."',
+ (ROOT / "experiments/WebScene.NativeEngine.Probe/CMakeLists.txt").read_text(),
+ )
def test_toolchain_exposes_target_multiarch_search_paths(self) -> None:
self.assertIn("CMAKE_LIBRARY_ARCHITECTURE", self.toolchain)
From cc3997625b59a51dfbf046f74f933789341046ab Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 11:41:24 +0100
Subject: [PATCH 28/41] fix(tests): run compatibility harness on net10
---
.github/workflows/CI.yml | 2 +-
.github/workflows/native-runtime-packages.yml | 4 ++--
scripts/build-native-engine-runtime.ps1 | 2 +-
scripts/build-native-engine-runtime.sh | 2 +-
scripts/tests/test_linux_build_policy.py | 4 ++++
.../runner/WebScene.WebPlatformSubset.Runner.csproj | 2 +-
6 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 5e9ffe6e2..99226a848 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -66,7 +66,7 @@ jobs:
- name: Avalonia headless tests
run: dotnet test tests/WebScene.Backend.Avalonia.Tests/WebScene.Backend.Avalonia.Tests.csproj -c Release --no-build
- name: WPT manifest integrity
- run: dotnet run --project tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj -c Release --no-build -- --selection all --list
+ run: dotnet run --project tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj -c Release -f net10.0 --no-build -- --selection all --list
- name: Native C++ portability build (without V8)
run: >-
cmake
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 2fd629bab..e459d9be8 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -451,7 +451,7 @@ jobs:
build_dir="${native_path%%/package-smoke/runtimes/*}"
dotnet run \
--project tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj \
- -c Release --no-build -- \
+ -c Release -f net10.0 --no-build -- \
--selection candidate \
--native-library "$native_path" \
--native-cache-directory "$build_dir/code-cache" \
@@ -628,7 +628,7 @@ jobs:
build_dir="${native_path%%/package-smoke/runtimes/*}"
dotnet run \
--project tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj \
- -c Release --no-build -- \
+ -c Release -f net10.0 --no-build -- \
--selection candidate \
--native-library "$native_path" \
--native-cache-directory "$build_dir/code-cache" \
diff --git a/scripts/build-native-engine-runtime.ps1 b/scripts/build-native-engine-runtime.ps1
index 923fb8c0e..e041d0643 100644
--- a/scripts/build-native-engine-runtime.ps1
+++ b/scripts/build-native-engine-runtime.ps1
@@ -291,7 +291,7 @@ $env:WEBSCENE_VARIABLE_FONT_INSTANCING = '1'
try {
& dotnet run `
--project (Join-Path $repoRoot "tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj") `
- -c Release -- `
+ -c Release -f net10.0 -- `
--selection required `
--native-library $packageNativePath `
--native-cache-directory (Join-Path $buildDir "code-cache") `
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index aad1182be..6615f480b 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -751,7 +751,7 @@ package_native_path="$package_smoke_dir/runtimes/$rid/native/$native_name"
WEBSCENE_VARIABLE_FONT_INSTANCING=1 dotnet run \
--project "$repo_root/tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj" \
- -c Release -- \
+ -c Release -f net10.0 -- \
--selection required \
--native-library "$package_native_path" \
--native-cache-directory "$build_dir/code-cache" \
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index faaaa4bec..b70b98175 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -85,6 +85,10 @@ def test_linux_zlib_is_resolved_only_from_the_target_sysroot(self) -> None:
self.assertIn('-DZLIB_LIBRARY="$target_library_dir/libz.a"', self.build_script)
self.assertIn('-DCMAKE_SKIP_RPATH=TRUE', self.build_script)
self.assertIn('LD_LIBRARY_PATH=$test_library_path', self.build_script)
+ self.assertIn(
+ '-c Release -f net10.0 --',
+ self.build_script,
+ )
self.assertIn(
'WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../.."',
(ROOT / "experiments/WebScene.NativeEngine.Probe/CMakeLists.txt").read_text(),
diff --git a/tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj b/tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj
index eb8818854..a50370e89 100644
--- a/tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj
+++ b/tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj
@@ -1,7 +1,7 @@
Exe
- net8.0
+ net8.0;net10.0
enable
enable
false
From 0ab81c1032c971a9877a039db80fd58a7f80259b Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 11:52:12 +0100
Subject: [PATCH 29/41] fix(linux): install pinned fontconfig runtime
---
.../WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc | 3 +++
.../WebScene.NativeEngine.Runtime/linux-build-lock.json | 3 +++
scripts/tests/test_linux_build_policy.py | 5 +++++
3 files changed, 11 insertions(+)
diff --git a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
index b19ade491..5f65a34fc 100644
--- a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
+++ b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
@@ -19,6 +19,9 @@ ENV DEBIAN_FRONTEND=noninteractive \
COPY --from=arm64-sysroot /crossrootfs/arm64 /crossrootfs/arm64
COPY --from=dotnet-sdk /usr/share/dotnet /usr/share/dotnet
+RUN tdnf install -y fontconfig-2.14.2-2.azl3 \
+ && tdnf clean all
+
RUN set -eux; \
curl -fsSLO "https://static.rust-lang.org/dist/rust-${RUST_VERSION}-x86_64-unknown-linux-gnu.tar.xz"; \
echo "${RUST_ARCHIVE_SHA256} rust-${RUST_VERSION}-x86_64-unknown-linux-gnu.tar.xz" | sha256sum -c -; \
diff --git a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
index fc9f627fa..89a8caaba 100644
--- a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
+++ b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
@@ -2,6 +2,9 @@
"schemaVersion": 1,
"builderIdentity": "webscene-linux-glibc-v1",
"hostPlatform": "linux/amd64",
+ "hostRuntimePackages": {
+ "fontconfig": "2.14.2-2.azl3"
+ },
"dotnetSdk": {
"version": "10.0.302",
"image": "mcr.microsoft.com/dotnet/sdk:10.0.302-noble-amd64",
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index b70b98175..0a06f32e7 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -28,6 +28,11 @@ def test_all_container_inputs_are_digest_pinned(self) -> None:
self.assertNotIn("apt-get", self.dockerfile)
def test_lock_and_dockerfile_are_synchronized(self) -> None:
+ self.assertEqual(
+ "2.14.2-2.azl3",
+ self.lock["hostRuntimePackages"]["fontconfig"],
+ )
+ self.assertIn("tdnf install -y fontconfig-2.14.2-2.azl3", self.dockerfile)
expected = [self.lock["dotnetSdk"], *self.lock["sysroots"].values()]
for item in expected:
image = item.get("image", item.get("sourceImage"))
From 17b9d27bab48cb4d31bca6c48e7300a5cb6c4d59 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 12:00:54 +0100
Subject: [PATCH 30/41] fix(linux): install pinned test font
---
.../WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc | 4 +++-
.../WebScene.NativeEngine.Runtime/linux-build-lock.json | 1 +
scripts/tests/test_linux_build_policy.py | 7 ++++++-
3 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
index 5f65a34fc..fd93f7934 100644
--- a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
+++ b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
@@ -19,7 +19,9 @@ ENV DEBIAN_FRONTEND=noninteractive \
COPY --from=arm64-sysroot /crossrootfs/arm64 /crossrootfs/arm64
COPY --from=dotnet-sdk /usr/share/dotnet /usr/share/dotnet
-RUN tdnf install -y fontconfig-2.14.2-2.azl3 \
+RUN tdnf install -y \
+ dejavu-sans-fonts-2.37-3.azl3 \
+ fontconfig-2.14.2-2.azl3 \
&& tdnf clean all
RUN set -eux; \
diff --git a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
index 89a8caaba..b4fe8caf0 100644
--- a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
+++ b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
@@ -3,6 +3,7 @@
"builderIdentity": "webscene-linux-glibc-v1",
"hostPlatform": "linux/amd64",
"hostRuntimePackages": {
+ "dejavu-sans-fonts": "2.37-3.azl3",
"fontconfig": "2.14.2-2.azl3"
},
"dotnetSdk": {
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 0a06f32e7..c747f929b 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -32,7 +32,12 @@ def test_lock_and_dockerfile_are_synchronized(self) -> None:
"2.14.2-2.azl3",
self.lock["hostRuntimePackages"]["fontconfig"],
)
- self.assertIn("tdnf install -y fontconfig-2.14.2-2.azl3", self.dockerfile)
+ self.assertEqual(
+ "2.37-3.azl3",
+ self.lock["hostRuntimePackages"]["dejavu-sans-fonts"],
+ )
+ self.assertIn("fontconfig-2.14.2-2.azl3", self.dockerfile)
+ self.assertIn("dejavu-sans-fonts-2.37-3.azl3", self.dockerfile)
expected = [self.lock["dotnetSdk"], *self.lock["sysroots"].values()]
for item in expected:
image = item.get("image", item.get("sourceImage"))
From d7c50ac1f6ccbfa70fe4553a038ceccae6305edc Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 12:30:46 +0100
Subject: [PATCH 31/41] ci(linux): finalize arm64 packages under qemu
---
.github/workflows/native-runtime-packages.yml | 95 +++++++++++++------
.../Dockerfile.linux-arm64-finalizer | 17 ++++
.../linux-build-lock.json | 11 +++
scripts/tests/test_linux_build_policy.py | 19 +++-
4 files changed, 113 insertions(+), 29 deletions(-)
create mode 100644 packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-arm64-finalizer
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index e459d9be8..795f6bfa8 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -164,8 +164,12 @@ jobs:
packages: write
outputs:
image: ${{ steps.reference.outputs.image }}
+ arm64-finalizer-image: ${{ steps.finalizer-reference.outputs.image }}
steps:
- uses: actions/checkout@v4
+ - uses: docker/setup-qemu-action@v3
+ with:
+ platforms: arm64
- uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
@@ -188,6 +192,25 @@ jobs:
if: github.event_name != 'pull_request'
shell: bash
run: echo "image=ghcr.io/scenetech/webscene-linux-builder@${{ steps.build.outputs.digest }}" >> "$GITHUB_OUTPUT"
+ - id: finalizer-build
+ name: Build pinned Linux ARM64 finalizer
+ uses: docker/build-push-action@v6
+ with:
+ context: packaging/WebScene.NativeEngine.Runtime
+ file: packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-arm64-finalizer
+ platforms: linux/arm64
+ push: ${{ github.event_name != 'pull_request' }}
+ load: ${{ github.event_name == 'pull_request' }}
+ tags: ghcr.io/scenetech/webscene-linux-arm64-finalizer:webscene-linux-glibc-v1
+ - id: finalizer-reference
+ name: Resolve immutable ARM64 finalizer reference
+ shell: bash
+ run: |
+ if [[ '${{ github.event_name }}' == pull_request ]]; then
+ echo "image=ghcr.io/scenetech/webscene-linux-arm64-finalizer:webscene-linux-glibc-v1" >> "$GITHUB_OUTPUT"
+ else
+ echo "image=ghcr.io/scenetech/webscene-linux-arm64-finalizer@${{ steps.finalizer-build.outputs.digest }}" >> "$GITHUB_OUTPUT"
+ fi
native:
name: Build ${{ matrix.rid }}
@@ -580,17 +603,22 @@ jobs:
linux-arm64-finalize:
name: Finalize and test linux-arm64
- needs: [metadata, native]
- runs-on: [self-hosted, Linux, ARM64]
+ needs: [metadata, linux-builder, native]
+ runs-on: [self-hosted, Linux, X64]
+ permissions:
+ contents: read
+ packages: read
steps:
- uses: actions/checkout@v4
- - name: Setup .NET
- uses: actions/setup-dotnet@v5
- env:
- DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
+ - uses: docker/setup-qemu-action@v3
with:
- global-json-file: global.json
- dotnet-version: 8.0.x
+ platforms: arm64
+ - name: Log in to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
- name: Download ARM64 cross-build stage
uses: actions/download-artifact@v4
with:
@@ -601,21 +629,23 @@ jobs:
run: |
find artifacts/native-engine-runtime-build/linux-arm64-* -type f \
\( -name 'webscene_*' -o -name '*_tests' \) -exec chmod +x {} +
- - name: Generate snapshot, test, and package natively
+ - name: Generate snapshot, test, and package under ARM64 emulation
shell: bash
run: |
- if [[ ! -e /workspace ]]; then
- sudo ln -s "$GITHUB_WORKSPACE" /workspace
- fi
- if [[ "$(realpath /workspace)" != "$(realpath "$GITHUB_WORKSPACE")" ]]; then
- echo "/workspace must resolve to the checked-out repository for deterministic paths." >&2
- exit 1
- fi
- scripts/build-linux-native-runtime.sh \
- --rid linux-arm64 \
- --package-version '${{ needs.metadata.outputs.package-version }}' \
- --output "$GITHUB_WORKSPACE/artifacts/nuget-packages" \
- --stage finalize
+ docker run --rm \
+ --platform linux/arm64 \
+ --user "$(id -u):$(id -g)" \
+ --env HOME=/tmp/webscene-home \
+ --env DOTNET_CLI_HOME=/tmp/webscene-home \
+ --env NUGET_PACKAGES=/tmp/webscene-home/.nuget/packages \
+ --volume "$GITHUB_WORKSPACE:/workspace" \
+ --workdir /workspace \
+ '${{ needs.linux-builder.outputs.arm64-finalizer-image }}' \
+ scripts/build-linux-native-runtime.sh \
+ --rid linux-arm64 \
+ --package-version '${{ needs.metadata.outputs.package-version }}' \
+ --output /workspace/artifacts/nuget-packages \
+ --stage finalize
native_path="$(find artifacts/native-engine-runtime-build -path '*/linux-arm64-*/libwebscene_native_engine.so' -print -quit)"
python3 scripts/verify-linux-native-abi.py "$native_path" \
--rid linux-arm64 \
@@ -626,13 +656,22 @@ jobs:
run: |
native_path="$(find artifacts/native-engine-runtime-build -path '*/package-smoke/runtimes/linux-arm64/native/libwebscene_native_engine.so' -print -quit)"
build_dir="${native_path%%/package-smoke/runtimes/*}"
- dotnet run \
- --project tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj \
- -c Release -f net10.0 --no-build -- \
- --selection candidate \
- --native-library "$native_path" \
- --native-cache-directory "$build_dir/code-cache" \
- --output "$build_dir/wpt-candidate-results"
+ docker run --rm \
+ --platform linux/arm64 \
+ --user "$(id -u):$(id -g)" \
+ --env HOME=/tmp/webscene-home \
+ --env DOTNET_CLI_HOME=/tmp/webscene-home \
+ --env NUGET_PACKAGES=/tmp/webscene-home/.nuget/packages \
+ --volume "$GITHUB_WORKSPACE:/workspace" \
+ --workdir /workspace \
+ '${{ needs.linux-builder.outputs.arm64-finalizer-image }}' \
+ dotnet run \
+ --project tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj \
+ -c Release -f net10.0 --no-build -- \
+ --selection candidate \
+ --native-library "$native_path" \
+ --native-cache-directory "$build_dir/code-cache" \
+ --output "$build_dir/wpt-candidate-results"
- name: Upload ARM64 required compatibility evidence
if: success()
uses: actions/upload-artifact@v4
diff --git a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-arm64-finalizer b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-arm64-finalizer
new file mode 100644
index 000000000..9b5d90589
--- /dev/null
+++ b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-arm64-finalizer
@@ -0,0 +1,17 @@
+FROM mcr.microsoft.com/dotnet/sdk:10.0.302-noble-arm64v8@sha256:683d16913974bf1311381ccd6d6aba55213f313501c39ef964b0458f44c0c4bc
+
+ENV DEBIAN_FRONTEND=noninteractive \
+ DOTNET_CLI_TELEMETRY_OPTOUT=1 \
+ DOTNET_NOLOGO=1 \
+ NUGET_XMLDOC_MODE=skip
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ binutils=2.42-4ubuntu2 \
+ cmake=3.28.3-1build7 \
+ fontconfig=2.15.0-1.1ubuntu2 \
+ fonts-dejavu-core=2.37-8 \
+ python3=3.12.3-0ubuntu1 \
+ && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /workspace
diff --git a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
index b4fe8caf0..20746eeca 100644
--- a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
+++ b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
@@ -6,6 +6,17 @@
"dejavu-sans-fonts": "2.37-3.azl3",
"fontconfig": "2.14.2-2.azl3"
},
+ "arm64Finalizer": {
+ "image": "mcr.microsoft.com/dotnet/sdk:10.0.302-noble-arm64v8",
+ "digest": "sha256:683d16913974bf1311381ccd6d6aba55213f313501c39ef964b0458f44c0c4bc",
+ "packages": {
+ "binutils": "2.42-4ubuntu2",
+ "cmake": "3.28.3-1build7",
+ "fontconfig": "2.15.0-1.1ubuntu2",
+ "fonts-dejavu-core": "2.37-8",
+ "python3": "3.12.3-0ubuntu1"
+ }
+ },
"dotnetSdk": {
"version": "10.0.302",
"image": "mcr.microsoft.com/dotnet/sdk:10.0.302-noble-amd64",
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index c747f929b..821a6e5a6 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -16,12 +16,19 @@ class LinuxBuildPolicyTests(unittest.TestCase):
def setUpClass(cls) -> None:
cls.lock = json.loads((PACKAGING / "linux-build-lock.json").read_text())
cls.dockerfile = (PACKAGING / "Dockerfile.linux-glibc").read_text()
+ cls.finalizer_dockerfile = (
+ PACKAGING / "Dockerfile.linux-arm64-finalizer"
+ ).read_text()
cls.workflow = (ROOT / ".github/workflows/native-runtime-packages.yml").read_text()
cls.build_script = (ROOT / "scripts/build-native-engine-runtime.sh").read_text()
cls.toolchain = (ROOT / "scripts/linux-glibc-toolchain.cmake").read_text()
def test_all_container_inputs_are_digest_pinned(self) -> None:
- from_lines = re.findall(r"^FROM\s+(\S+)", self.dockerfile, re.MULTILINE)
+ from_lines = re.findall(
+ r"^FROM\s+(\S+)",
+ self.dockerfile + "\n" + self.finalizer_dockerfile,
+ re.MULTILINE,
+ )
external = [value for value in from_lines if value not in {"x64-sysroot"}]
self.assertTrue(external)
self.assertTrue(all("@sha256:" in value for value in external), external)
@@ -38,6 +45,13 @@ def test_lock_and_dockerfile_are_synchronized(self) -> None:
)
self.assertIn("fontconfig-2.14.2-2.azl3", self.dockerfile)
self.assertIn("dejavu-sans-fonts-2.37-3.azl3", self.dockerfile)
+ finalizer = self.lock["arm64Finalizer"]
+ self.assertIn(
+ f'{finalizer["image"]}@{finalizer["digest"]}',
+ self.finalizer_dockerfile,
+ )
+ for package, version in finalizer["packages"].items():
+ self.assertIn(f"{package}={version}", self.finalizer_dockerfile)
expected = [self.lock["dotnetSdk"], *self.lock["sysroots"].values()]
for item in expected:
image = item.get("image", item.get("sourceImage"))
@@ -67,6 +81,9 @@ def test_release_matrix_contains_both_glibc_rids(self) -> None:
self.assertIn(f"--expected-rid {rid}", self.workflow)
self.assertIn(f"--native-rid {rid}", self.workflow)
self.assertIn("github.ref_type != 'tag'", self.workflow)
+ self.assertIn("runs-on: [self-hosted, Linux, X64]", self.workflow)
+ self.assertIn("--platform linux/arm64", self.workflow)
+ self.assertNotIn("runs-on: [self-hosted, Linux, ARM64]", self.workflow)
def test_linux_libcxx_cache_paths_do_not_invalidate_macos_caches(self) -> None:
self.assertEqual(2, self.workflow.count("v8_cache_extra_paths: |"))
From 9b04e2f072ad85929be19b5fb9c4d769d46bcbc5 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 12:45:33 +0100
Subject: [PATCH 32/41] fix(ci): update arm64 finalizer package locks
---
.../Dockerfile.linux-arm64-finalizer | 4 ++--
packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-arm64-finalizer b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-arm64-finalizer
index 9b5d90589..725fe31a4 100644
--- a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-arm64-finalizer
+++ b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-arm64-finalizer
@@ -7,11 +7,11 @@ ENV DEBIAN_FRONTEND=noninteractive \
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
- binutils=2.42-4ubuntu2 \
+ binutils=2.42-4ubuntu2.10 \
cmake=3.28.3-1build7 \
fontconfig=2.15.0-1.1ubuntu2 \
fonts-dejavu-core=2.37-8 \
- python3=3.12.3-0ubuntu1 \
+ python3=3.12.3-0ubuntu2.1 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
diff --git a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
index 20746eeca..25fbc8d64 100644
--- a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
+++ b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
@@ -10,11 +10,11 @@
"image": "mcr.microsoft.com/dotnet/sdk:10.0.302-noble-arm64v8",
"digest": "sha256:683d16913974bf1311381ccd6d6aba55213f313501c39ef964b0458f44c0c4bc",
"packages": {
- "binutils": "2.42-4ubuntu2",
+ "binutils": "2.42-4ubuntu2.10",
"cmake": "3.28.3-1build7",
"fontconfig": "2.15.0-1.1ubuntu2",
"fonts-dejavu-core": "2.37-8",
- "python3": "3.12.3-0ubuntu1"
+ "python3": "3.12.3-0ubuntu2.1"
}
},
"dotnetSdk": {
From 3f8182a788fb6fc609aec9b6ba0a641b9146a9b2 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 13:19:41 +0100
Subject: [PATCH 33/41] fix(linux): skip sysroot checks during arm64 finalize
---
scripts/build-native-engine-runtime.sh | 2 +-
scripts/tests/test_linux_build_policy.py | 4 ++++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 6615f480b..8a9b7d365 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -543,7 +543,7 @@ if [[ "$thin_lto" == true ]]; then
-DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld
-DCMAKE_MODULE_LINKER_FLAGS=-fuse-ld=lld
)
-elif [[ "$expected_kernel" == Linux ]]; then
+elif [[ "$expected_kernel" == Linux && "$finalize_only" == false ]]; then
# Compile the embedding library with the exact Chromium LLVM and libc++
# revision used for V8. New libc++ headers can require compiler features and
# configuration defines absent from the builder image's host toolchain.
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 821a6e5a6..4f8c9baf8 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -103,6 +103,10 @@ def test_cmake_try_compile_keeps_cross_target_identity(self) -> None:
self.assertIn("CMAKE_SYSROOT", self.toolchain)
def test_linux_openssl_is_resolved_only_from_the_target_sysroot(self) -> None:
+ self.assertIn(
+ 'elif [[ "$expected_kernel" == Linux && "$finalize_only" == false ]]; then',
+ self.build_script,
+ )
self.assertIn('target_library_dir="$sysroot/usr/lib/$target_triple"', self.build_script)
self.assertIn('-DOPENSSL_CRYPTO_LIBRARY="$target_library_dir/libcrypto.a"', self.build_script)
self.assertIn('-DOPENSSL_SSL_LIBRARY="$target_library_dir/libssl.a"', self.build_script)
From 3dc36ce7d86fed42a00a3c3ce21e2f6b86b1f7a2 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 14:32:47 +0100
Subject: [PATCH 34/41] Isolate native runtime .NET validation
---
scripts/build-native-engine-runtime.sh | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/scripts/build-native-engine-runtime.sh b/scripts/build-native-engine-runtime.sh
index 8a9b7d365..b3c5dbe4b 100755
--- a/scripts/build-native-engine-runtime.sh
+++ b/scripts/build-native-engine-runtime.sh
@@ -736,7 +736,14 @@ if [[ "$html_parser" == html5ever ]]; then
"-p:WebSceneNativeEngineHtmlParserNoticesPath=$repo_root/experiments/WebScene.NativeEngine.Probe/native/html_parser/THIRD-PARTY-NOTICES.md")
fi
pack_args+=("-p:PackageVersion=$package_version")
-dotnet pack "${pack_args[@]}"
+# Self-hosted runners retain .NET build-server processes between invocations and
+# jobs. This is especially problematic when an Apple Silicon runner alternates
+# between native arm64 and Rosetta x64 SDKs: a later command can wait forever on
+# a server from the other architecture. Ensure validation is isolated from any
+# persistent server state and do not create new reusable servers below.
+dotnet build-server shutdown
+
+dotnet pack "${pack_args[@]}" --disable-build-servers
package_path="$output_dir/WebScene.NativeEngine.Runtime.$rid.$package_version.nupkg"
if [[ ! -f "$package_path" ]]; then
@@ -751,7 +758,7 @@ package_native_path="$package_smoke_dir/runtimes/$rid/native/$native_name"
WEBSCENE_VARIABLE_FONT_INSTANCING=1 dotnet run \
--project "$repo_root/tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj" \
- -c Release -f net10.0 -- \
+ -c Release -f net10.0 --disable-build-servers -- \
--selection required \
--native-library "$package_native_path" \
--native-cache-directory "$build_dir/code-cache" \
@@ -760,13 +767,13 @@ WEBSCENE_VARIABLE_FONT_INSTANCING=1 dotnet run \
WEBSCENE_TEST_NATIVE_LIBRARY="$package_native_path" \
WEBSCENE_VARIABLE_FONT_INSTANCING=1 \
dotnet test "$repo_root/tests/WebScene.Backend.Avalonia.Tests/WebScene.Backend.Avalonia.Tests.csproj" \
- -c Release -f net10.0 \
+ -c Release -f net10.0 --disable-build-servers \
--filter 'FullyQualifiedName~NativeWebFontCacheTests|FullyQualifiedName~VariableWebFontTests|FullyQualifiedName~SvgPictureRenderingTests'
WEBSCENE_NATIVE_ENGINE_PATH="$package_native_path" \
dotnet run \
--project "$repo_root/benchmarks/WebScene.NativeEngine.Benchmarks/WebScene.NativeEngine.Benchmarks.csproj" \
- -c Release -- \
+ -c Release --disable-build-servers -- \
probe native-interop-race --batches 100 --width 32
consumer_smoke_root="$repo_root/artifacts/native-engine-consumer-smoke"
From f05bee7daad2f2ba2a355f835e600bf144fb8732 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 15:16:25 +0100
Subject: [PATCH 35/41] Stabilize Linux native runtime validation
---
.../tests/native_v8_runtime_inspector_tests.inc | 11 ++++++++---
.../Dockerfile.linux-glibc | 2 ++
.../linux-build-lock.json | 2 ++
scripts/tests/test_linux_build_policy.py | 10 ++++++++++
.../NativeCanvasSceneRenderer.cs | 10 +++++++---
src/WebScene.Backend.Avalonia/NativeTextShaping.cs | 12 +++++++++---
.../webscene-component-profile.json | 2 +-
.../NativeTextShapingTests.cs | 14 ++++++++++++++
8 files changed, 53 insertions(+), 10 deletions(-)
diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_inspector_tests.inc b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_inspector_tests.inc
index 8082836f9..f1e02e406 100644
--- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_inspector_tests.inc
+++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_inspector_tests.inc
@@ -61,12 +61,13 @@ void receive_inspector_test_message(
bool wait_for_inspector_message(
inspector_test_messages& messages,
std::string_view first,
- std::string_view second = {})
+ std::string_view second = {},
+ std::chrono::steady_clock::duration timeout = std::chrono::seconds(5))
{
std::unique_lock lock(messages.mutex);
return messages.available.wait_for(
lock,
- std::chrono::seconds(5),
+ timeout,
[&] {
return std::any_of(
messages.values.begin(),
@@ -1391,7 +1392,11 @@ void test_v8_inspector_raw_cdp_session(webscene_engine* engine)
session_id,
R"({"id":68,"method":"Runtime.evaluate","params":{"expression":"globalThis.__websceneCappedRejections = Array.from({ length: 1025 }, (_, index) => Promise.reject(new Error('webscene-inspector-capped-rejection-' + index)));","returnByValue":true}})");
require(
- wait_for_inspector_message(messages, R"("id":68)"),
+ wait_for_inspector_message(
+ messages,
+ R"("id":68)",
+ {},
+ std::chrono::seconds(30)),
"Inspector rejection-cap fixture did not evaluate");
require(
wait_for_inspector_message_count(
diff --git a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
index fd93f7934..46b9d30cc 100644
--- a/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
+++ b/packaging/WebScene.NativeEngine.Runtime/Dockerfile.linux-glibc
@@ -21,6 +21,8 @@ COPY --from=dotnet-sdk /usr/share/dotnet /usr/share/dotnet
RUN tdnf install -y \
dejavu-sans-fonts-2.37-3.azl3 \
+ dejavu-sans-mono-fonts-2.37-3.azl3 \
+ dejavu-serif-fonts-2.37-3.azl3 \
fontconfig-2.14.2-2.azl3 \
&& tdnf clean all
diff --git a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
index 25fbc8d64..24e8b881c 100644
--- a/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
+++ b/packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json
@@ -4,6 +4,8 @@
"hostPlatform": "linux/amd64",
"hostRuntimePackages": {
"dejavu-sans-fonts": "2.37-3.azl3",
+ "dejavu-sans-mono-fonts": "2.37-3.azl3",
+ "dejavu-serif-fonts": "2.37-3.azl3",
"fontconfig": "2.14.2-2.azl3"
},
"arm64Finalizer": {
diff --git a/scripts/tests/test_linux_build_policy.py b/scripts/tests/test_linux_build_policy.py
index 4f8c9baf8..e99195061 100644
--- a/scripts/tests/test_linux_build_policy.py
+++ b/scripts/tests/test_linux_build_policy.py
@@ -43,8 +43,18 @@ def test_lock_and_dockerfile_are_synchronized(self) -> None:
"2.37-3.azl3",
self.lock["hostRuntimePackages"]["dejavu-sans-fonts"],
)
+ self.assertEqual(
+ "2.37-3.azl3",
+ self.lock["hostRuntimePackages"]["dejavu-sans-mono-fonts"],
+ )
+ self.assertEqual(
+ "2.37-3.azl3",
+ self.lock["hostRuntimePackages"]["dejavu-serif-fonts"],
+ )
self.assertIn("fontconfig-2.14.2-2.azl3", self.dockerfile)
self.assertIn("dejavu-sans-fonts-2.37-3.azl3", self.dockerfile)
+ self.assertIn("dejavu-sans-mono-fonts-2.37-3.azl3", self.dockerfile)
+ self.assertIn("dejavu-serif-fonts-2.37-3.azl3", self.dockerfile)
finalizer = self.lock["arm64Finalizer"]
self.assertIn(
f'{finalizer["image"]}@{finalizer["digest"]}',
diff --git a/src/WebScene.Backend.Avalonia/NativeCanvasSceneRenderer.cs b/src/WebScene.Backend.Avalonia/NativeCanvasSceneRenderer.cs
index 83b5f31f0..989f92920 100644
--- a/src/WebScene.Backend.Avalonia/NativeCanvasSceneRenderer.cs
+++ b/src/WebScene.Backend.Avalonia/NativeCanvasSceneRenderer.cs
@@ -3187,15 +3187,19 @@ private NativeTextShaping.CanvasFontDescription ConfigureFont(
if (generic is "-apple-system" or "blinkmacsystemfont" or "system-ui"
or "sans-serif")
{
- family = OperatingSystem.IsMacOS() ? ".AppleSystemUIFont" : "Arial";
+ family = OperatingSystem.IsMacOS()
+ ? ".AppleSystemUIFont"
+ : OperatingSystem.IsWindows() ? "Arial" : "sans-serif";
}
else if (generic == "serif")
{
- family = "Times New Roman";
+ family = OperatingSystem.IsLinux() ? "serif" : "Times New Roman";
}
else if (generic == "monospace")
{
- family = OperatingSystem.IsMacOS() ? "Menlo" : "Consolas";
+ family = OperatingSystem.IsMacOS()
+ ? "Menlo"
+ : OperatingSystem.IsWindows() ? "Consolas" : "monospace";
}
var candidate = SKTypeface.FromFamilyName(
family,
diff --git a/src/WebScene.Backend.Avalonia/NativeTextShaping.cs b/src/WebScene.Backend.Avalonia/NativeTextShaping.cs
index 9eb26bcfc..822072cb2 100644
--- a/src/WebScene.Backend.Avalonia/NativeTextShaping.cs
+++ b/src/WebScene.Backend.Avalonia/NativeTextShaping.cs
@@ -468,9 +468,15 @@ internal static SKTypeface ResolveTypeface(
? ".AppleSystemUIFont"
: OperatingSystem.IsWindows() ? "Segoe UI" : "sans-serif";
else if (genericFamily == "sans-serif")
- family = OperatingSystem.IsMacOS() ? "Helvetica" : "Arial";
- else if (genericFamily == "serif") family = "Times New Roman";
- else if (genericFamily == "monospace") family = OperatingSystem.IsMacOS() ? "Menlo" : "Consolas";
+ family = OperatingSystem.IsMacOS()
+ ? "Helvetica"
+ : OperatingSystem.IsWindows() ? "Arial" : "sans-serif";
+ else if (genericFamily == "serif")
+ family = OperatingSystem.IsLinux() ? "serif" : "Times New Roman";
+ else if (genericFamily == "monospace")
+ family = OperatingSystem.IsMacOS()
+ ? "Menlo"
+ : OperatingSystem.IsWindows() ? "Consolas" : "monospace";
var candidate = SKTypeface.FromFamilyName(
family,
diff --git a/tests/WebPlatformSubset/webscene-component-profile.json b/tests/WebPlatformSubset/webscene-component-profile.json
index 962f79e43..763b4a0d7 100644
--- a/tests/WebPlatformSubset/webscene-component-profile.json
+++ b/tests/WebPlatformSubset/webscene-component-profile.json
@@ -1951,7 +1951,7 @@
"secondColor": "#0000ff",
"axis": "horizontal",
"minimumPixels": 5,
- "maximumPixels": 15,
+ "maximumPixels": 24,
"description": "generated inline whitespace remains visible between differently weighted runs"
},
{
diff --git a/tests/WebScene.Backend.Avalonia.Tests/NativeTextShapingTests.cs b/tests/WebScene.Backend.Avalonia.Tests/NativeTextShapingTests.cs
index 125fe7434..19a9fa79e 100644
--- a/tests/WebScene.Backend.Avalonia.Tests/NativeTextShapingTests.cs
+++ b/tests/WebScene.Backend.Avalonia.Tests/NativeTextShapingTests.cs
@@ -9,6 +9,20 @@ namespace WebScene.Backend.Avalonia.Tests;
[Collection("Native web-font cache")]
public sealed class NativeTextShapingTests
{
+ [Fact]
+ public void LinuxGenericFamiliesResolveThroughFontconfig()
+ {
+ if (!OperatingSystem.IsLinux()) return;
+
+ var sansSerif = NativeTextShaping.ResolveTypeface("sans-serif", 400);
+ var serif = NativeTextShaping.ResolveTypeface("serif", 400);
+ var monospace = NativeTextShaping.ResolveTypeface("monospace", 400);
+
+ Assert.NotEqual(sansSerif.FamilyName, serif.FamilyName);
+ Assert.NotEqual(sansSerif.FamilyName, monospace.FamilyName);
+ Assert.NotEqual(serif.FamilyName, monospace.FamilyName);
+ }
+
[Fact]
public void WindowsGenericFamiliesKeepSystemUiAndSansSerifDistinct()
{
From b82bf55d45159b1515ea9aeea5a3e24a34674b19 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 15:50:46 +0100
Subject: [PATCH 36/41] Fix late-stage native package verification
---
.github/workflows/native-runtime-packages.yml | 31 ++++++++++++++-----
1 file changed, 23 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 795f6bfa8..47338b02d 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -472,13 +472,28 @@ jobs:
exit 1
fi
build_dir="${native_path%%/package-smoke/runtimes/*}"
- dotnet run \
- --project tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj \
- -c Release -f net10.0 --no-build -- \
- --selection candidate \
- --native-library "$native_path" \
- --native-cache-directory "$build_dir/code-cache" \
- --output "$build_dir/wpt-candidate-results"
+ runner_args=(
+ dotnet run
+ --project tests/WebPlatformSubset/runner/WebScene.WebPlatformSubset.Runner.csproj
+ -c Release -f net10.0 --no-build --disable-build-servers --
+ --selection candidate
+ --native-library "$native_path"
+ --native-cache-directory "$build_dir/code-cache"
+ --output "$build_dir/wpt-candidate-results")
+ if [[ '${{ matrix.rid }}' == 'linux-x64' ]]; then
+ docker run --rm \
+ --platform linux/amd64 \
+ --user "$(id -u):$(id -g)" \
+ --env HOME=/tmp/webscene-home \
+ --env DOTNET_CLI_HOME=/tmp/webscene-home \
+ --env NUGET_PACKAGES=/tmp/webscene-home/.nuget/packages \
+ --volume "$GITHUB_WORKSPACE:/workspace" \
+ --workdir /workspace \
+ '${{ needs.linux-builder.outputs.image }}' \
+ "${runner_args[@]}"
+ else
+ "${runner_args[@]}"
+ fi
- id: v8-sdk-ready
name: Validate completed V8 SDK for caching
if: always() && steps.v8-cache.outputs.cache-hit != 'true'
@@ -740,7 +755,7 @@ jobs:
uses: actions/download-artifact@v4
with:
pattern: native-*-${{ needs.metadata.outputs.package-version }}
- path: artifacts/nuget-packages
+ path: artifacts
merge-multiple: true
- name: Verify versions, dependencies, symbols, and package inventory
shell: bash
From 840d56359e7476d4267d13bb1bf68312000a51a5 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 16:32:20 +0100
Subject: [PATCH 37/41] Run cross-architecture consumers on available runners
---
.github/workflows/native-runtime-packages.yml | 86 +++++++++++++++++--
1 file changed, 77 insertions(+), 9 deletions(-)
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 47338b02d..d07002d23 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -821,48 +821,74 @@ jobs:
consumer:
name: Package consumer ${{ matrix.rid }}
- needs: [metadata, package-set]
+ needs: [metadata, package-set, linux-builder]
+ permissions:
+ contents: read
+ packages: read
strategy:
fail-fast: false
matrix:
include:
- os: [self-hosted, macOS, ARM64]
rid: osx-arm64
+ dotnet_architecture: arm64
- os: [self-hosted, macOS, ARM64]
rid: osx-x64
+ dotnet_architecture: x64
- os: [self-hosted, Linux, X64]
rid: linux-x64
- - os: [self-hosted, Linux, ARM64]
+ dotnet_architecture: x64
+ - os: [self-hosted, Linux, X64]
rid: linux-arm64
+ dotnet_architecture: x64
- os: windows-2022
rid: win-x64
+ dotnet_architecture: x64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup .NET
+ if: matrix.rid != 'linux-arm64'
uses: actions/setup-dotnet@v5
env:
DOTNET_INSTALL_DIR: ${{ runner.temp }}/dotnet
with:
global-json-file: global.json
dotnet-version: 8.0.x
+ architecture: ${{ matrix.dotnet_architecture }}
+ - name: Set up ARM64 emulation
+ if: matrix.rid == 'linux-arm64'
+ uses: docker/setup-qemu-action@v3
+ with:
+ platforms: arm64
+ - name: Log in to GitHub Container Registry
+ if: matrix.rid == 'linux-arm64'
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
- name: Download verified package set
uses: actions/download-artifact@v4
with:
name: release-${{ needs.metadata.outputs.package-version }}
path: artifacts/nuget-packages
- name: Restore clean package consumer
+ if: matrix.rid != 'linux-arm64'
shell: bash
run: |
dotnet restore tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
-p:RuntimeIdentifier='${{ matrix.rid }}' \
-p:WebScenePackageVersion='${{ needs.metadata.outputs.package-version }}' \
- --configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config
+ --configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
+ --disable-build-servers
dotnet restore tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
-p:RuntimeIdentifier='${{ matrix.rid }}' \
-p:WebScenePackageVersion='${{ needs.metadata.outputs.package-version }}' \
- --configfile tests/WebScene.Uno.PackageSmoke/NuGet.config
+ --configfile tests/WebScene.Uno.PackageSmoke/NuGet.config \
+ --disable-build-servers
- name: Build and run clean package consumer
+ if: matrix.rid != 'linux-arm64'
shell: bash
run: |
dotnet run \
@@ -870,13 +896,45 @@ jobs:
-c Release \
-r '${{ matrix.rid }}' \
--no-restore \
+ --disable-build-servers \
-p:WebScenePackageVersion='${{ needs.metadata.outputs.package-version }}'
dotnet run \
--project tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
-c Release \
-r '${{ matrix.rid }}' \
--no-restore \
+ --disable-build-servers \
-p:WebScenePackageVersion='${{ needs.metadata.outputs.package-version }}'
+ - name: Restore, build, and run ARM64 package consumer under emulation
+ if: matrix.rid == 'linux-arm64'
+ shell: bash
+ run: |
+ docker run --rm \
+ --platform linux/arm64 \
+ --user "$(id -u):$(id -g)" \
+ --env HOME=/tmp/webscene-home \
+ --env DOTNET_CLI_HOME=/tmp/webscene-home \
+ --env NUGET_PACKAGES=/tmp/webscene-home/.nuget/packages \
+ --volume "$GITHUB_WORKSPACE:/workspace" \
+ --workdir /workspace \
+ '${{ needs.linux-builder.outputs.arm64-finalizer-image }}' \
+ bash -euo pipefail -c '
+ dotnet restore tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
+ -p:RuntimeIdentifier=linux-arm64 \
+ -p:WebScenePackageVersion=${{ needs.metadata.outputs.package-version }} \
+ --configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
+ --disable-build-servers
+ dotnet restore tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
+ -p:RuntimeIdentifier=linux-arm64 \
+ -p:WebScenePackageVersion=${{ needs.metadata.outputs.package-version }} \
+ --configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
+ --disable-build-servers
+ dotnet run --project tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
+ -c Release -r linux-arm64 --no-restore --disable-build-servers \
+ -p:WebScenePackageVersion=${{ needs.metadata.outputs.package-version }}
+ dotnet run --project tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
+ -c Release -r linux-arm64 --no-restore --disable-build-servers \
+ -p:WebScenePackageVersion=${{ needs.metadata.outputs.package-version }}'
linux-floor-smoke:
name: Linux floor ${{ matrix.distribution }} ${{ matrix.rid }}
@@ -890,7 +948,7 @@ jobs:
platform: linux/amd64
distribution: ubuntu-18.04
image: ubuntu:18.04
- - os: [self-hosted, Linux, ARM64]
+ - os: [self-hosted, Linux, X64]
rid: linux-arm64
platform: linux/arm64
distribution: ubuntu-18.04
@@ -900,7 +958,7 @@ jobs:
platform: linux/amd64
distribution: ubi-8.9
image: registry.access.redhat.com/ubi8/ubi:8.9
- - os: [self-hosted, Linux, ARM64]
+ - os: [self-hosted, Linux, X64]
rid: linux-arm64
platform: linux/arm64
distribution: ubi-8.9
@@ -908,6 +966,11 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
+ - name: Set up ARM64 emulation
+ if: matrix.platform == 'linux/arm64'
+ uses: docker/setup-qemu-action@v3
+ with:
+ platforms: arm64
- uses: actions/download-artifact@v4
with:
name: release-${{ needs.metadata.outputs.package-version }}
@@ -939,14 +1002,19 @@ jobs:
matrix:
include:
- { os: [self-hosted, Linux, X64], rid: linux-x64, platform: linux/amd64, distribution: debian-10, image: 'debian:10' }
- - { os: [self-hosted, Linux, ARM64], rid: linux-arm64, platform: linux/arm64, distribution: debian-10, image: 'debian:10' }
+ - { os: [self-hosted, Linux, X64], rid: linux-arm64, platform: linux/arm64, distribution: debian-10, image: 'debian:10' }
- { os: [self-hosted, Linux, X64], rid: linux-x64, platform: linux/amd64, distribution: fedora-38, image: 'fedora:38' }
- - { os: [self-hosted, Linux, ARM64], rid: linux-arm64, platform: linux/arm64, distribution: fedora-38, image: 'fedora:38' }
+ - { os: [self-hosted, Linux, X64], rid: linux-arm64, platform: linux/arm64, distribution: fedora-38, image: 'fedora:38' }
- { os: [self-hosted, Linux, X64], rid: linux-x64, platform: linux/amd64, distribution: opensuse-15.5, image: 'opensuse/leap:15.5' }
- - { os: [self-hosted, Linux, ARM64], rid: linux-arm64, platform: linux/arm64, distribution: opensuse-15.5, image: 'opensuse/leap:15.5' }
+ - { os: [self-hosted, Linux, X64], rid: linux-arm64, platform: linux/arm64, distribution: opensuse-15.5, image: 'opensuse/leap:15.5' }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
+ - name: Set up ARM64 emulation
+ if: matrix.platform == 'linux/arm64'
+ uses: docker/setup-qemu-action@v3
+ with:
+ platforms: arm64
- uses: actions/download-artifact@v4
with:
name: release-${{ needs.metadata.outputs.package-version }}
From 4a89bd1a648906214d8e0e1407b3cbf645f21028 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 16:57:47 +0100
Subject: [PATCH 38/41] Relax async native test polling under emulation
---
.../tests/native_v8_runtime_browser_dom_tests.inc | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc
index 298a46228..1b9a508ca 100644
--- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc
+++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc
@@ -1408,7 +1408,8 @@ void test_async_save_acknowledgement_publishes_without_pointer_input()
pending: __saveState.pending,
label: document.getElementById('label').textContent
}))JS", "async-save-coalescing.js",
- R"JSON({"activations":2,"requests":1,"pending":true,"label":"Save"})JSON");
+ R"JSON({"activations":2,"requests":1,"pending":true,"label":"Save"})JSON",
+ 1000);
require(
coalesced_state
== R"JSON({"activations":2,"requests":1,"pending":true,"label":"Save"})JSON",
@@ -1418,7 +1419,7 @@ void test_async_save_acknowledgement_publishes_without_pointer_input()
webscene_engine_metrics after_activation{};
webscene_engine_get_metrics(engine, &after_activation);
webscene_engine_metrics after_ack{};
- for (auto attempt = 0; attempt < 200; ++attempt) {
+ for (auto attempt = 0; attempt < 1000; ++attempt) {
webscene_engine_get_metrics(engine, &after_ack);
if (after_ack.published_scenes > after_activation.published_scenes
&& evaluate(engine, "__saveState.acknowledgements",
@@ -1455,7 +1456,7 @@ void test_async_save_acknowledgement_publishes_without_pointer_input()
engine,
failure_consumed_before + 2U,
"failing save activation was not consumed");
- for (auto attempt = 0; attempt < 200; ++attempt) {
+ for (auto attempt = 0; attempt < 1000; ++attempt) {
if (evaluate(engine, "__saveState.acknowledgements",
"async-save-failure-wait.js") == "2") {
break;
From e01e2bae694da93fb2600073d33a932128e7b783 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 17:51:48 +0100
Subject: [PATCH 39/41] Add targeted ARM64 consumer recheck
---
.../native-runtime-consumer-recheck.yml | 79 +++++++++++++++++++
.github/workflows/native-runtime-packages.yml | 6 +-
.../WebScene.Runtime.PackageSmoke.csproj | 2 +-
3 files changed, 84 insertions(+), 3 deletions(-)
create mode 100644 .github/workflows/native-runtime-consumer-recheck.yml
diff --git a/.github/workflows/native-runtime-consumer-recheck.yml b/.github/workflows/native-runtime-consumer-recheck.yml
new file mode 100644
index 000000000..3dded64ce
--- /dev/null
+++ b/.github/workflows/native-runtime-consumer-recheck.yml
@@ -0,0 +1,79 @@
+name: Recheck native runtime consumer
+
+on:
+ workflow_dispatch:
+ inputs:
+ source_run_id:
+ description: NuGet packages run containing the verified release artifact
+ required: true
+ type: string
+ package_version:
+ description: Package version contained in the release artifact
+ required: true
+ default: 1.0.35
+ type: string
+
+permissions:
+ actions: read
+ contents: read
+
+jobs:
+ linux-arm64-consumer:
+ name: Package consumer linux-arm64
+ runs-on: [self-hosted, Linux, X64]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up ARM64 emulation
+ uses: docker/setup-qemu-action@v3
+ with:
+ platforms: arm64
+ - name: Download verified package set
+ uses: actions/download-artifact@v4
+ with:
+ name: release-${{ inputs.package_version }}
+ path: artifacts/nuget-packages
+ run-id: ${{ inputs.source_run_id }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ - id: image
+ name: Resolve pinned ARM64 .NET image
+ shell: bash
+ run: |
+ image="$(python3 - <<'PY'
+ import json
+ from pathlib import Path
+ lock = json.loads(Path("packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json").read_text())
+ finalizer = lock["arm64Finalizer"]
+ print(f'{finalizer["image"]}@{finalizer["digest"]}')
+ PY
+ )"
+ echo "reference=$image" >> "$GITHUB_OUTPUT"
+ - name: Restore, build, and run ARM64 package consumer
+ shell: bash
+ run: |
+ docker run --rm \
+ --platform linux/arm64 \
+ --user "$(id -u):$(id -g)" \
+ --env HOME=/tmp/webscene-home \
+ --env DOTNET_CLI_HOME=/tmp/webscene-home \
+ --env NUGET_PACKAGES=/tmp/webscene-home/.nuget/packages \
+ --volume "$GITHUB_WORKSPACE:/workspace" \
+ --workdir /workspace \
+ '${{ steps.image.outputs.reference }}' \
+ bash -euo pipefail -c '
+ dotnet restore tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
+ -p:TargetFramework=net10.0 \
+ -p:RuntimeIdentifier=linux-arm64 \
+ -p:WebScenePackageVersion=${{ inputs.package_version }} \
+ --configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
+ --disable-build-servers
+ dotnet restore tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
+ -p:RuntimeIdentifier=linux-arm64 \
+ -p:WebScenePackageVersion=${{ inputs.package_version }} \
+ --configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
+ --disable-build-servers
+ dotnet run --project tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
+ -c Release -f net10.0 -r linux-arm64 --no-restore --disable-build-servers \
+ -p:WebScenePackageVersion=${{ inputs.package_version }}
+ dotnet run --project tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
+ -c Release -r linux-arm64 --no-restore --disable-build-servers \
+ -p:WebScenePackageVersion=${{ inputs.package_version }}'
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index d07002d23..567dacc86 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -878,6 +878,7 @@ jobs:
shell: bash
run: |
dotnet restore tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
+ -p:TargetFramework=net10.0 \
-p:RuntimeIdentifier='${{ matrix.rid }}' \
-p:WebScenePackageVersion='${{ needs.metadata.outputs.package-version }}' \
--configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
@@ -893,7 +894,7 @@ jobs:
run: |
dotnet run \
--project tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
- -c Release \
+ -c Release -f net10.0 \
-r '${{ matrix.rid }}' \
--no-restore \
--disable-build-servers \
@@ -920,6 +921,7 @@ jobs:
'${{ needs.linux-builder.outputs.arm64-finalizer-image }}' \
bash -euo pipefail -c '
dotnet restore tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
+ -p:TargetFramework=net10.0 \
-p:RuntimeIdentifier=linux-arm64 \
-p:WebScenePackageVersion=${{ needs.metadata.outputs.package-version }} \
--configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
@@ -930,7 +932,7 @@ jobs:
--configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
--disable-build-servers
dotnet run --project tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
- -c Release -r linux-arm64 --no-restore --disable-build-servers \
+ -c Release -f net10.0 -r linux-arm64 --no-restore --disable-build-servers \
-p:WebScenePackageVersion=${{ needs.metadata.outputs.package-version }}
dotnet run --project tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
-c Release -r linux-arm64 --no-restore --disable-build-servers \
diff --git a/tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj b/tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj
index f7ad46b3c..2e255fa8c 100644
--- a/tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj
+++ b/tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj
@@ -1,7 +1,7 @@
Exe
- net8.0
+ net8.0;net10.0
enable
enable
false
From 4e244fa1f56be12afafe1f81447da8e9cffa67d4 Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 17:53:00 +0100
Subject: [PATCH 40/41] Reuse release artifacts for consumer rechecks
---
.../native-runtime-consumer-recheck.yml | 79 -------------------
.github/workflows/native-runtime-packages.yml | 75 ++++++++++++++++++
2 files changed, 75 insertions(+), 79 deletions(-)
delete mode 100644 .github/workflows/native-runtime-consumer-recheck.yml
diff --git a/.github/workflows/native-runtime-consumer-recheck.yml b/.github/workflows/native-runtime-consumer-recheck.yml
deleted file mode 100644
index 3dded64ce..000000000
--- a/.github/workflows/native-runtime-consumer-recheck.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-name: Recheck native runtime consumer
-
-on:
- workflow_dispatch:
- inputs:
- source_run_id:
- description: NuGet packages run containing the verified release artifact
- required: true
- type: string
- package_version:
- description: Package version contained in the release artifact
- required: true
- default: 1.0.35
- type: string
-
-permissions:
- actions: read
- contents: read
-
-jobs:
- linux-arm64-consumer:
- name: Package consumer linux-arm64
- runs-on: [self-hosted, Linux, X64]
- steps:
- - uses: actions/checkout@v4
- - name: Set up ARM64 emulation
- uses: docker/setup-qemu-action@v3
- with:
- platforms: arm64
- - name: Download verified package set
- uses: actions/download-artifact@v4
- with:
- name: release-${{ inputs.package_version }}
- path: artifacts/nuget-packages
- run-id: ${{ inputs.source_run_id }}
- github-token: ${{ secrets.GITHUB_TOKEN }}
- - id: image
- name: Resolve pinned ARM64 .NET image
- shell: bash
- run: |
- image="$(python3 - <<'PY'
- import json
- from pathlib import Path
- lock = json.loads(Path("packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json").read_text())
- finalizer = lock["arm64Finalizer"]
- print(f'{finalizer["image"]}@{finalizer["digest"]}')
- PY
- )"
- echo "reference=$image" >> "$GITHUB_OUTPUT"
- - name: Restore, build, and run ARM64 package consumer
- shell: bash
- run: |
- docker run --rm \
- --platform linux/arm64 \
- --user "$(id -u):$(id -g)" \
- --env HOME=/tmp/webscene-home \
- --env DOTNET_CLI_HOME=/tmp/webscene-home \
- --env NUGET_PACKAGES=/tmp/webscene-home/.nuget/packages \
- --volume "$GITHUB_WORKSPACE:/workspace" \
- --workdir /workspace \
- '${{ steps.image.outputs.reference }}' \
- bash -euo pipefail -c '
- dotnet restore tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
- -p:TargetFramework=net10.0 \
- -p:RuntimeIdentifier=linux-arm64 \
- -p:WebScenePackageVersion=${{ inputs.package_version }} \
- --configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
- --disable-build-servers
- dotnet restore tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
- -p:RuntimeIdentifier=linux-arm64 \
- -p:WebScenePackageVersion=${{ inputs.package_version }} \
- --configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
- --disable-build-servers
- dotnet run --project tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
- -c Release -f net10.0 -r linux-arm64 --no-restore --disable-build-servers \
- -p:WebScenePackageVersion=${{ inputs.package_version }}
- dotnet run --project tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
- -c Release -r linux-arm64 --no-restore --disable-build-servers \
- -p:WebScenePackageVersion=${{ inputs.package_version }}'
diff --git a/.github/workflows/native-runtime-packages.yml b/.github/workflows/native-runtime-packages.yml
index 567dacc86..ce5b93f31 100644
--- a/.github/workflows/native-runtime-packages.yml
+++ b/.github/workflows/native-runtime-packages.yml
@@ -8,6 +8,16 @@ on:
required: true
default: false
type: boolean
+ consumer_recheck_run_id:
+ description: Existing run whose verified release artifact should be rechecked
+ required: false
+ default: ''
+ type: string
+ consumer_recheck_version:
+ description: Package version in the existing release artifact
+ required: false
+ default: 1.0.35
+ type: string
push:
branches:
- main
@@ -49,6 +59,7 @@ permissions:
jobs:
metadata:
+ if: inputs.consumer_recheck_run_id == ''
runs-on: ubuntu-latest
outputs:
package-version: ${{ steps.version.outputs.package-version }}
@@ -1083,3 +1094,67 @@ jobs:
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
done
+
+ linux-arm64-consumer-recheck:
+ name: Recheck package consumer linux-arm64
+ if: inputs.consumer_recheck_run_id != ''
+ runs-on: [self-hosted, Linux, X64]
+ permissions:
+ actions: read
+ contents: read
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up ARM64 emulation
+ uses: docker/setup-qemu-action@v3
+ with:
+ platforms: arm64
+ - name: Download verified package set
+ uses: actions/download-artifact@v4
+ with:
+ name: release-${{ inputs.consumer_recheck_version }}
+ path: artifacts/nuget-packages
+ run-id: ${{ inputs.consumer_recheck_run_id }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ - id: image
+ name: Resolve pinned ARM64 .NET image
+ shell: bash
+ run: |
+ image="$(python3 - <<'PY'
+ import json
+ from pathlib import Path
+ lock = json.loads(Path("packaging/WebScene.NativeEngine.Runtime/linux-build-lock.json").read_text())
+ finalizer = lock["arm64Finalizer"]
+ print(f'{finalizer["image"]}@{finalizer["digest"]}')
+ PY
+ )"
+ echo "reference=$image" >> "$GITHUB_OUTPUT"
+ - name: Restore, build, and run ARM64 package consumer
+ shell: bash
+ run: |
+ docker run --rm \
+ --platform linux/arm64 \
+ --user "$(id -u):$(id -g)" \
+ --env HOME=/tmp/webscene-home \
+ --env DOTNET_CLI_HOME=/tmp/webscene-home \
+ --env NUGET_PACKAGES=/tmp/webscene-home/.nuget/packages \
+ --volume "$GITHUB_WORKSPACE:/workspace" \
+ --workdir /workspace \
+ '${{ steps.image.outputs.reference }}' \
+ bash -euo pipefail -c '
+ dotnet restore tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
+ -p:TargetFramework=net10.0 \
+ -p:RuntimeIdentifier=linux-arm64 \
+ -p:WebScenePackageVersion=${{ inputs.consumer_recheck_version }} \
+ --configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
+ --disable-build-servers
+ dotnet restore tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
+ -p:RuntimeIdentifier=linux-arm64 \
+ -p:WebScenePackageVersion=${{ inputs.consumer_recheck_version }} \
+ --configfile tests/WebScene.Runtime.PackageSmoke/NuGet.config \
+ --disable-build-servers
+ dotnet run --project tests/WebScene.Runtime.PackageSmoke/WebScene.Runtime.PackageSmoke.csproj \
+ -c Release -f net10.0 -r linux-arm64 --no-restore --disable-build-servers \
+ -p:WebScenePackageVersion=${{ inputs.consumer_recheck_version }}
+ dotnet run --project tests/WebScene.Uno.PackageSmoke/WebScene.Uno.PackageSmoke.csproj \
+ -c Release -r linux-arm64 --no-restore --disable-build-servers \
+ -p:WebScenePackageVersion=${{ inputs.consumer_recheck_version }}'
From e068e46f6619d2dbe0e00245e5350e61603a32da Mon Sep 17 00:00:00 2001
From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com>
Date: Tue, 22 Sep 2026 18:22:05 +0100
Subject: [PATCH 41/41] Stabilize async save test under Rosetta
---
.../native_v8_runtime_browser_dom_tests.inc | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc
index 1b9a508ca..9355b55ea 100644
--- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc
+++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_browser_dom_tests.inc
@@ -1387,7 +1387,7 @@ void test_async_save_acknowledgement_publishes_without_pointer_input()
state.pending = false;
state.acknowledgements++;
if (!shouldFail) label.textContent = '';
- }, 250);
+ }, 1000);
});
})()
)JS", "async-save-publication-setup.js");
@@ -1396,6 +1396,21 @@ void test_async_save_acknowledgement_publishes_without_pointer_input()
const auto consumed_before = consumed_input_count(engine);
pointer_button(engine, WEBSCENE_INPUT_POINTER_DOWN, 30, 20, 9102U, true);
pointer_button(engine, WEBSCENE_INPUT_POINTER_UP, 30, 20, 9103U, false);
+ wait_for_consumed_inputs(
+ engine,
+ consumed_before + 2U,
+ "initial async-save activation was not consumed");
+ const auto initial_state = evaluate_until_equals(engine, R"JS(({ activations: __saveState.activations,
+ requests: __saveState.requests,
+ pending: __saveState.pending
+ }))JS", "async-save-initial-activation.js",
+ R"JSON({"activations":1,"requests":1,"pending":true})JSON",
+ 1000);
+ require(
+ initial_state
+ == R"JSON({"activations":1,"requests":1,"pending":true})JSON",
+ "initial activation did not start an asynchronous save: "
+ + initial_state);
pointer_button(engine, WEBSCENE_INPUT_POINTER_DOWN, 30, 20, 9104U, true);
pointer_button(engine, WEBSCENE_INPUT_POINTER_UP, 30, 20, 9105U, false);
wait_for_consumed_inputs(