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(