From 9375222b4710c67c7d16d50694f004800e12b778 Mon Sep 17 00:00:00 2001 From: geruipu Date: Mon, 5 Oct 2026 13:09:17 +0800 Subject: [PATCH] Isolate manual artifact validation without package publication --- .github/workflows/nuget-avalonia-native.yml | 298 --------- .../nuget-static-graphics-repack.yml | 2 +- .github/workflows/nuget-static-graphics.yml | 117 ++-- .../static-graphics-linux-debian.yml | 28 + .github/workflows/static-graphics-linux.yml | 62 +- .github/workflows/static-graphics-macos.yml | 72 +-- .github/workflows/static-graphics-musl.yml | 91 +-- .../workflows/static-graphics-preflight.yml | 22 +- .github/workflows/static-graphics-windows.yml | 90 +-- .../workflows/static-graphics-zig-cross.yml | 111 ++++ .github/workflows/zig-recent-regression.yml | 55 ++ .github/workflows/zig-toolchain-image.yml | 57 ++ ACTION-TESTING.md | 32 + DAILY-RELEASE.md | 9 + .../StaticLink.Avalonia.Native.csproj | 2 + NuGet/StaticGraphics/README.md | 4 +- .../StaticGraphics/StaticLink.Avalonia.csproj | 5 +- .../StaticLink.Avalonia.targets | 15 + README.md | 2 + build/daily-release-validation.json | 72 +++ build/linux-toolchain/CROSS-COMPILE.md | 60 ++ build/linux-toolchain/Dockerfile | 36 ++ build/linux-toolchain/Dockerfile.cross-probe | 57 ++ build/linux-toolchain/README.md | 20 + build/linux-toolchain/angle-linux.gn.in | 23 + build/linux-toolchain/angle-mac.gn.in | 21 + build/linux-toolchain/angle-win.gn.in | 25 + .../build-avalonia-native-cross.py | 43 ++ build/linux-toolchain/build-cross.py | 242 +++++++ .../linux-toolchain/cross-toolchain.lock.json | 10 + build/linux-toolchain/fetch-linux-sysroots.py | 39 ++ build/linux-toolchain/link-cross.py | 100 +++ .../linux-sysroots-musl.lock.json | 474 ++++++++++++++ .../linux-toolchain/linux-sysroots.lock.json | 594 ++++++++++++++++++ .../local-cross-validation.json | 103 +++ .../multi-version-validation.json | 56 ++ build/linux-toolchain/package-zig.py | 27 + build/linux-toolchain/patches/.gitattributes | 2 + .../patches/BUILD.gn-zig.patch | 28 + .../patches/BUILDCONFIG.gn-zig.patch | 20 + .../patches/angle-chromium-7922.patch | 64 ++ .../patches/angle-static-targets.patch | 21 + .../patches/find_sdk-linux.patch | 15 + .../patches/gcc-toolchain-objc.patch | 38 ++ .../patches/sdk_info-linux.patch | 14 + .../patches/skia-xps-linkage.patch | 11 + build/linux-toolchain/probe-cross.py | 81 +++ .../recent-version-validation.json | 68 ++ build/linux-toolchain/skia-linux.gn.in | 30 + build/linux-toolchain/skia-mac.gn.in | 27 + build/linux-toolchain/skia-win.gn.in | 28 + build/linux-toolchain/skia_compat.py | 123 ++++ build/linux-toolchain/smoke-angle.c | 3 + build/linux-toolchain/smoke-raster.c | 31 + build/linux-toolchain/sources.lock.json | 7 + build/linux-toolchain/sync-skia-deps.py | 89 +++ build/linux-toolchain/versions/2.88.9.json | 7 + build/linux-toolchain/versions/3.119.1.json | 7 + build/linux-toolchain/versions/3.119.2.json | 7 + build/linux-toolchain/versions/3.119.4.json | 7 + build/linux-toolchain/versions/4.150.1.json | 7 + build/linux-toolchain/versions/4.150.2.json | 7 + build/linux-toolchain/versions/4.150.3.json | 7 + build/linux-toolchain/versions/4.150.5.json | 7 + build/linux-toolchain/versions/4.153.1.json | 7 + build/linux-toolchain/x86-thunks.py | 69 ++ build/linux-toolchain/zig-cross.py | 293 +++++++++ build/release-tracks.json | 5 + scripts/apply-release-version.py | 14 + scripts/build-linux-container.sh | 46 ++ scripts/build-linux-static-graphics.sh | 68 +- scripts/build-macos-font-preinit.sh | 4 +- scripts/build-macos-static-graphics.sh | 21 +- scripts/build-windows-static-graphics.ps1 | 28 +- scripts/check-upstream-releases.py | 47 ++ scripts/prepare-cross-sources.sh | 60 ++ scripts/resolve-release-lock.py | 43 ++ 77 files changed, 3899 insertions(+), 638 deletions(-) delete mode 100644 .github/workflows/nuget-avalonia-native.yml create mode 100644 .github/workflows/static-graphics-linux-debian.yml create mode 100644 .github/workflows/static-graphics-zig-cross.yml create mode 100644 .github/workflows/zig-recent-regression.yml create mode 100644 .github/workflows/zig-toolchain-image.yml create mode 100644 ACTION-TESTING.md create mode 100644 DAILY-RELEASE.md create mode 100644 build/daily-release-validation.json create mode 100644 build/linux-toolchain/CROSS-COMPILE.md create mode 100644 build/linux-toolchain/Dockerfile create mode 100644 build/linux-toolchain/Dockerfile.cross-probe create mode 100644 build/linux-toolchain/README.md create mode 100644 build/linux-toolchain/angle-linux.gn.in create mode 100644 build/linux-toolchain/angle-mac.gn.in create mode 100644 build/linux-toolchain/angle-win.gn.in create mode 100644 build/linux-toolchain/build-avalonia-native-cross.py create mode 100644 build/linux-toolchain/build-cross.py create mode 100644 build/linux-toolchain/cross-toolchain.lock.json create mode 100644 build/linux-toolchain/fetch-linux-sysroots.py create mode 100644 build/linux-toolchain/link-cross.py create mode 100644 build/linux-toolchain/linux-sysroots-musl.lock.json create mode 100644 build/linux-toolchain/linux-sysroots.lock.json create mode 100644 build/linux-toolchain/local-cross-validation.json create mode 100644 build/linux-toolchain/multi-version-validation.json create mode 100644 build/linux-toolchain/package-zig.py create mode 100644 build/linux-toolchain/patches/.gitattributes create mode 100644 build/linux-toolchain/patches/BUILD.gn-zig.patch create mode 100644 build/linux-toolchain/patches/BUILDCONFIG.gn-zig.patch create mode 100644 build/linux-toolchain/patches/angle-chromium-7922.patch create mode 100644 build/linux-toolchain/patches/angle-static-targets.patch create mode 100644 build/linux-toolchain/patches/find_sdk-linux.patch create mode 100644 build/linux-toolchain/patches/gcc-toolchain-objc.patch create mode 100644 build/linux-toolchain/patches/sdk_info-linux.patch create mode 100644 build/linux-toolchain/patches/skia-xps-linkage.patch create mode 100644 build/linux-toolchain/probe-cross.py create mode 100644 build/linux-toolchain/recent-version-validation.json create mode 100644 build/linux-toolchain/skia-linux.gn.in create mode 100644 build/linux-toolchain/skia-mac.gn.in create mode 100644 build/linux-toolchain/skia-win.gn.in create mode 100644 build/linux-toolchain/skia_compat.py create mode 100644 build/linux-toolchain/smoke-angle.c create mode 100644 build/linux-toolchain/smoke-raster.c create mode 100644 build/linux-toolchain/sources.lock.json create mode 100644 build/linux-toolchain/sync-skia-deps.py create mode 100644 build/linux-toolchain/versions/2.88.9.json create mode 100644 build/linux-toolchain/versions/3.119.1.json create mode 100644 build/linux-toolchain/versions/3.119.2.json create mode 100644 build/linux-toolchain/versions/3.119.4.json create mode 100644 build/linux-toolchain/versions/4.150.1.json create mode 100644 build/linux-toolchain/versions/4.150.2.json create mode 100644 build/linux-toolchain/versions/4.150.3.json create mode 100644 build/linux-toolchain/versions/4.150.5.json create mode 100644 build/linux-toolchain/versions/4.153.1.json create mode 100644 build/linux-toolchain/x86-thunks.py create mode 100644 build/linux-toolchain/zig-cross.py create mode 100644 build/release-tracks.json create mode 100644 scripts/apply-release-version.py create mode 100755 scripts/build-linux-container.sh create mode 100644 scripts/check-upstream-releases.py create mode 100755 scripts/prepare-cross-sources.sh create mode 100644 scripts/resolve-release-lock.py diff --git a/.github/workflows/nuget-avalonia-native.yml b/.github/workflows/nuget-avalonia-native.yml deleted file mode 100644 index 03a6173..0000000 --- a/.github/workflows/nuget-avalonia-native.yml +++ /dev/null @@ -1,298 +0,0 @@ -name: staticlink-avalonia-native - -on: - schedule: - - cron: "17 3 * * *" - workflow_dispatch: - inputs: - avalonia_version: - description: Avalonia version to build; empty checks the latest stable 11.x and 12.x versions - required: false - type: string - force_build: - description: Build even when this StaticLink.Avalonia.Native version already exists - required: true - default: false - type: boolean - publish_to_nuget: - description: Publish with NuGet Trusted Publishing after smoke tests - required: true - default: true - type: boolean - -jobs: - detect: - runs-on: ubuntu-latest - permissions: - contents: read - outputs: - versions: ${{ steps.version.outputs.versions }} - should_build: ${{ steps.version.outputs.should_build }} - steps: - - name: Detect unpublished Avalonia version - id: version - shell: bash - env: - REQUESTED_VERSION: ${{ inputs.avalonia_version }} - FORCE_BUILD: ${{ inputs.force_build }} - run: | - set -euo pipefail - - if [ -n "$REQUESTED_VERSION" ]; then - candidate_versions=("$REQUESTED_VERSION") - else - nuget_versions="$(curl --fail --silent --show-error --location \ - https://api.nuget.org/v3-flatcontainer/avalonia/index.json)" - candidate_versions=() - for major in 11 12; do - candidate_versions+=("$(jq -r --arg major "$major" \ - '[.versions[] | select(startswith($major + ".") and (contains("-") | not))] | last' \ - <<< "$nuget_versions")") - done - fi - - versions='[]' - for avalonia_version in "${candidate_versions[@]}"; do - if [ -z "$avalonia_version" ] || [ "$avalonia_version" = "null" ]; then - echo "Could not determine a stable Avalonia version." >&2 - exit 1 - fi - - package_version="$avalonia_version.1" - package_url="https://api.nuget.org/v3-flatcontainer/staticlink.avalonia.native/$package_version/staticlink.avalonia.native.$package_version.nupkg" - if [ "$FORCE_BUILD" != "true" ] && curl --fail --silent --location --output /dev/null "$package_url"; then - echo "Already published: $package_version" - continue - fi - - echo "Build required: $package_version" - versions="$(jq -cn --argjson versions "$versions" \ - --arg avalonia_version "$avalonia_version" \ - --arg package_version "$package_version" \ - '$versions + [{avalonia_version: $avalonia_version, package_version: $package_version}]')" - done - - echo "versions=$versions" >> "$GITHUB_OUTPUT" - if [ "$versions" = '[]' ]; then - echo 'should_build=false' >> "$GITHUB_OUTPUT" - else - echo 'should_build=true' >> "$GITHUB_OUTPUT" - fi - - build: - needs: detect - if: ${{ needs.detect.outputs.should_build == 'true' }} - strategy: - fail-fast: false - matrix: - version: ${{ fromJSON(needs.detect.outputs.versions) }} - rid: [osx-x64, osx-arm64] - include: - - rid: osx-x64 - runner: macos-14 - - rid: osx-arm64 - runner: macos-15 - runs-on: ${{ matrix.runner }} - permissions: - contents: read - env: - RID: ${{ matrix.rid }} - AVALONIA_VERSION: ${{ matrix.version.avalonia_version }} - WORK_DIR: ${{ github.workspace }}/External/AvaloniaNative/.work - OUTPUT_DIR: ${{ github.workspace }}/External/AvaloniaNative/${{ matrix.rid }}/native - steps: - - uses: actions/checkout@v4 - - - name: Setup .NET for Avalonia header generation - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 8.0.x - 10.0.x - - - name: Build static AvaloniaNative - run: bash scripts/build-macos-avalonia-native-static.sh - - - name: Build macOS font preinit object - run: bash scripts/build-macos-font-preinit.sh - - - name: Upload static AvaloniaNative - uses: actions/upload-artifact@v4 - with: - name: native-avalonia-${{ matrix.version.avalonia_version }}-${{ matrix.rid }} - path: | - External/AvaloniaNative/${{ matrix.rid }}/native/libAvaloniaNative.a - External/AvaloniaNative/${{ matrix.rid }}/native/macos_font_preinit.o - if-no-files-found: error - - pack: - needs: - - detect - - build - if: ${{ needs.detect.outputs.should_build == 'true' }} - strategy: - matrix: - version: ${{ fromJSON(needs.detect.outputs.versions) }} - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "10.0.x" - - - name: Download static AvaloniaNative archives - uses: actions/download-artifact@v4 - with: - pattern: native-avalonia-${{ matrix.version.avalonia_version }}-* - path: artifacts/native - merge-multiple: false - - - name: Prepare package inputs - shell: bash - run: | - set -euo pipefail - for rid in osx-x64 osx-arm64; do - mkdir -p "External/AvaloniaNative/$rid/native" - cp "artifacts/native/native-avalonia-${{ matrix.version.avalonia_version }}-$rid/libAvaloniaNative.a" "External/AvaloniaNative/$rid/native/" - cp "artifacts/native/native-avalonia-${{ matrix.version.avalonia_version }}-$rid/macos_font_preinit.o" "External/AvaloniaNative/$rid/native/" - done - - - name: Pack StaticLink.Avalonia.Native - run: | - dotnet pack NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj \ - -c Release \ - -o artifacts/nuget \ - /p:PackageVersion=${{ matrix.version.package_version }} \ - /p:Version=${{ matrix.version.package_version }} - - - name: Upload NuGet package - uses: actions/upload-artifact@v4 - with: - name: staticlink-avalonia-native-${{ matrix.version.package_version }} - path: artifacts/nuget/*.nupkg - if-no-files-found: error - - smoke: - needs: - - detect - - pack - if: ${{ needs.detect.outputs.should_build == 'true' }} - strategy: - fail-fast: false - matrix: - version: ${{ fromJSON(needs.detect.outputs.versions) }} - rid: [osx-x64, osx-arm64] - include: - - rid: osx-x64 - runner: macos-14 - - rid: osx-arm64 - runner: macos-15 - runs-on: ${{ matrix.runner }} - permissions: - contents: read - steps: - - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "10.0.x" - - - name: Download NuGet package - uses: actions/download-artifact@v4 - with: - name: staticlink-avalonia-native-${{ matrix.version.package_version }} - path: artifacts/nuget - - - name: Publish Avalonia NativeAOT smoke app - shell: pwsh - run: | - $projectDir = Join-Path $PWD "artifacts/avalonia-native-smoke-src" - pwsh ./scripts/create-avalonia-smoke.ps1 ` - -ProjectDir $projectDir ` - -NuGetSource (Resolve-Path "artifacts/nuget") ` - -AvaloniaVersion "${{ matrix.version.avalonia_version }}" ` - -StaticLinkNativeVersion "${{ matrix.version.package_version }}" - dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") ` - -c Release ` - -r "${{ matrix.rid }}" ` - --configfile (Join-Path $projectDir "NuGet.config") ` - /p:PublishDir="$PWD/artifacts/smoke-${{ matrix.rid }}/" - - - name: Verify AvaloniaNative is statically linked - shell: bash - run: | - set -euo pipefail - test ! -f "artifacts/smoke-${{ matrix.rid }}/libAvaloniaNative.dylib" - test -x "artifacts/smoke-${{ matrix.rid }}/AvaloniaStaticLinkSmoke" - - - name: Run Avalonia smoke app - shell: bash - run: | - set -euo pipefail - cd "artifacts/smoke-${{ matrix.rid }}" - python3 - <<'PY' - import subprocess - import sys - - proc = subprocess.Popen( - ["./AvaloniaStaticLinkSmoke"], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - ) - try: - output, _ = proc.communicate(timeout=15) - except subprocess.TimeoutExpired: - proc.kill() - output, _ = proc.communicate() - print(output or "") - print("Avalonia smoke app stayed alive for 15 seconds.") - sys.exit(0) - - print(output or "") - if proc.returncode == 0: - sys.exit(0) - sys.exit(proc.returncode if proc.returncode > 0 else 1) - PY - - publish: - needs: - - detect - - smoke - if: ${{ needs.detect.outputs.should_build == 'true' && (github.event_name == 'schedule' || inputs.publish_to_nuget == true) }} - strategy: - matrix: - version: ${{ fromJSON(needs.detect.outputs.versions) }} - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - steps: - - name: Download NuGet package - uses: actions/download-artifact@v4 - with: - name: staticlink-avalonia-native-${{ matrix.version.package_version }} - path: artifacts/nuget - - - name: Validate Trusted Publishing configuration - env: - NUGET_USER: ${{ vars.NUGET_USER }} - run: test -n "$NUGET_USER" - - - name: NuGet login - id: login - uses: NuGet/login@v1 - with: - user: ${{ vars.NUGET_USER }} - - - name: Publish to nuget.org - run: | - dotnet nuget push artifacts/nuget/*.nupkg \ - --source https://www.nuget.org/api/v2/package \ - --api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \ - --skip-duplicate diff --git a/.github/workflows/nuget-static-graphics-repack.yml b/.github/workflows/nuget-static-graphics-repack.yml index 52174e9..3a46b1b 100644 --- a/.github/workflows/nuget-static-graphics-repack.yml +++ b/.github/workflows/nuget-static-graphics-repack.yml @@ -6,7 +6,7 @@ on: source_version: description: Existing package version containing the static libraries required: true - default: 4.150.1-7922.1 + default: 3.119.4-7922.1 type: string publish_to_nuget: description: Publish package to nuget.org diff --git a/.github/workflows/nuget-static-graphics.yml b/.github/workflows/nuget-static-graphics.yml index 980cb65..1c8e771 100644 --- a/.github/workflows/nuget-static-graphics.yml +++ b/.github/workflows/nuget-static-graphics.yml @@ -3,6 +3,14 @@ name: staticlink-avalonia on: workflow_dispatch: inputs: + skiasharp_version: + description: Stable SkiaSharp version from this branch major; empty uses the branch version + type: string + default: '' + native_package_revision: + description: Published AvaloniaNative revision for independent graphics smoke tests + type: string + default: '1' publish_to_nuget: description: Publish package to nuget.org required: true @@ -11,7 +19,7 @@ on: publish_to_release: description: Publish NuGet package and smoke apps to GitHub Release required: true - default: true + default: false type: boolean push: tags: @@ -20,46 +28,31 @@ on: env: DOTNET_ALPINE_IMAGE: mcr.microsoft.com/dotnet/sdk:10.0.302-alpine3.23 +concurrency: + group: graphics-${{ github.ref }}-${{ inputs.skiasharp_version }} + cancel-in-progress: false + jobs: preflight: uses: ./.github/workflows/static-graphics-preflight.yml + with: + skiasharp_version: ${{ inputs.skiasharp_version }} - build-linux: + build-zig: + permissions: + contents: read + packages: write needs: preflight - uses: ./.github/workflows/static-graphics-linux.yml + uses: ./.github/workflows/static-graphics-zig-cross.yml with: skiasharp_version: ${{ needs.preflight.outputs.skiasharp_version }} - angle_branch: ${{ needs.preflight.outputs.angle_branch }} - - build-musl: - needs: preflight - uses: ./.github/workflows/static-graphics-musl.yml - with: - skiasharp_version: ${{ needs.preflight.outputs.skiasharp_version }} - angle_branch: ${{ needs.preflight.outputs.angle_branch }} - dotnet_alpine_image: mcr.microsoft.com/dotnet/sdk:10.0.302-alpine3.23 - - build-windows: - needs: preflight - uses: ./.github/workflows/static-graphics-windows.yml - with: - skiasharp_version: ${{ needs.preflight.outputs.skiasharp_version }} - angle_branch: ${{ needs.preflight.outputs.angle_branch }} - - build-macos: - needs: preflight - uses: ./.github/workflows/static-graphics-macos.yml - with: - skiasharp_version: ${{ needs.preflight.outputs.skiasharp_version }} - angle_branch: ${{ needs.preflight.outputs.angle_branch }} + source_lock: ${{ needs.preflight.outputs.source_lock }} pack: runs-on: ubuntu-24.04 needs: - - build-linux - - build-musl - - build-windows - - build-macos + - preflight + - build-zig permissions: contents: read steps: @@ -109,7 +102,13 @@ jobs: test -f External/NativeStatic/win-x86/native/skia_x86_stdcall_thunks.lib - name: Pack NuGet package - run: dotnet pack NuGet/StaticGraphics/StaticLink.Avalonia.csproj -c Release -o artifacts/nuget + env: + SOURCE_LOCK_JSON: ${{ needs.preflight.outputs.source_lock }} + PACKAGE_VERSION: ${{ needs.preflight.outputs.package_version }} + SKIASHARP_VERSION: ${{ needs.preflight.outputs.skiasharp_version }} + run: | + python3 scripts/apply-release-version.py + dotnet pack NuGet/StaticGraphics/StaticLink.Avalonia.csproj -c Release -o artifacts/nuget - name: Verify portable NuGet paths shell: bash @@ -123,6 +122,13 @@ jobs: unzip -Z1 "$package" | grep -Fx 'buildTransitive/StaticLink.Avalonia.props' unzip -Z1 "$package" | grep -Fx 'buildTransitive/StaticLink.Avalonia.targets' + - name: Upload release source lock + uses: actions/upload-artifact@v4 + with: + name: graphics-release-source-lock + path: build/linux-toolchain/release.lock.json + if-no-files-found: error + - name: Upload NuGet artifact uses: actions/upload-artifact@v4 with: @@ -171,11 +177,16 @@ jobs: with: arch: ${{ matrix.msvc_arch }} + - name: Install LLVM linker for Zig Windows archives + if: ${{ startsWith(matrix.rid, 'win-') }} + shell: pwsh + run: choco install llvm -y + - name: Install Linux publish dependencies if: ${{ startsWith(matrix.rid, 'linux-') && !startsWith(matrix.rid, 'linux-musl-') }} run: | sudo apt-get update - sudo apt-get install -y clang lld xvfb zlib1g-dev libfontconfig1-dev libfreetype6 libgl1 libice6 libsm6 libx11-6 libxcb1 libxcursor1 libxext6 libxfixes3 libxinerama1 libxrandr2 libxrender1 libxi6 + sudo apt-get install -y clang lld xvfb zlib1g-dev libfontconfig1-dev libffi-dev libwayland-dev libx11-dev libxext-dev libxcb1-dev libfreetype6 libgl1 libice6 libsm6 libx11-6 libxcb1 libxcursor1 libxext6 libxfixes3 libxinerama1 libxrandr2 libxrender1 libxi6 - name: Setup .NET uses: actions/setup-dotnet@v4 @@ -200,7 +211,9 @@ jobs: $avaloniaVersion = "${{ matrix.avalonia_version }}" if (-not $avaloniaVersion) { $avaloniaVersion = "11.3.14" } $projectDir = Join-Path $PWD "artifacts/avalonia-smoke-src" - pwsh ./scripts/create-avalonia-smoke.ps1 -ProjectDir $projectDir -NuGetSource (Resolve-Path "artifacts/nuget") -StaticLinkVersion $version -AvaloniaVersion $avaloniaVersion + $nativeVersion = "" + if ("${{ matrix.rid }}".StartsWith("osx-")) { $nativeVersion = "$avaloniaVersion.${{ inputs.native_package_revision || '1' }}" } + pwsh ./scripts/create-avalonia-smoke.ps1 -ProjectDir $projectDir -NuGetSource (Resolve-Path "artifacts/nuget") -StaticLinkVersion $version -AvaloniaVersion $avaloniaVersion -StaticLinkNativeVersion $nativeVersion dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") ` -c Release ` -r "${{ matrix.rid }}" ` @@ -226,7 +239,7 @@ jobs: -v "${{ github.workspace }}:/workspace" ` -w /workspace ` "${{ env.DOTNET_ALPINE_IMAGE }}" ` - sh -c "apk add --no-cache clang lld build-base zlib-dev fontconfig-dev && dotnet publish /workspace/artifacts/avalonia-smoke-src/AvaloniaStaticLinkSmoke.csproj -c Release -r ${{ matrix.rid }} --configfile /workspace/artifacts/avalonia-smoke-src/NuGet.config /p:PublishAot=true /p:SelfContained=true /p:PublishSingleFile=true /p:StripSymbols=true /p:PublishDir=/workspace/artifacts/smoke-${{ matrix.rid }}/" + sh -c "apk add --no-cache clang lld build-base zlib-dev fontconfig-dev libffi-dev wayland-dev libx11-dev libxext-dev libxcb-dev && dotnet publish /workspace/artifacts/avalonia-smoke-src/AvaloniaStaticLinkSmoke.csproj -c Release -r ${{ matrix.rid }} --configfile /workspace/artifacts/avalonia-smoke-src/NuGet.config /p:PublishAot=true /p:SelfContained=true /p:PublishSingleFile=true /p:StripSymbols=true /p:PublishDir=/workspace/artifacts/smoke-${{ matrix.rid }}/" - name: Verify no dynamic graphics libraries remain shell: pwsh @@ -354,11 +367,12 @@ jobs: PY - name: Upload Avalonia smoke app + if: ${{ always() }} uses: actions/upload-artifact@v4 with: name: avalonia-smoke-${{ matrix.rid }} path: artifacts/smoke-${{ matrix.rid }}/** - if-no-files-found: error + if-no-files-found: warn smoke-macos-metal: runs-on: macos-15 @@ -392,7 +406,7 @@ jobs: -NuGetSource (Resolve-Path "artifacts/nuget") ` -StaticLinkVersion $Matches[1] ` -AvaloniaVersion "12.1.0" ` - -StaticLinkNativeVersion "12.1.0.1" + -StaticLinkNativeVersion "12.1.0.${{ inputs.native_package_revision || '1' }}" dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") ` -c Release ` -r osx-arm64 ` @@ -451,7 +465,18 @@ jobs: raise SystemExit(f"Avalonia {renderer} smoke app failed with exit code {result.returncode}.") PY + - name: Upload macOS Metal smoke app + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: avalonia-smoke-osx-arm64-metal + path: artifacts/smoke-osx-arm64-metal/** + if-no-files-found: warn + publish-nuget: + permissions: + contents: read + id-token: write runs-on: ubuntu-24.04 needs: - pack @@ -469,14 +494,21 @@ jobs: name: staticlink-avalonia-nuget path: artifacts/nuget + - name: NuGet Trusted Publishing login + id: login + if: ${{ vars.NUGET_USER != '' && (startsWith(github.ref, 'refs/tags/staticlink-avalonia-v') || inputs.publish_to_nuget == true) }} + uses: NuGet/login@v1 + with: + user: ${{ vars.NUGET_USER }} + - name: Publish to nuget.org if: ${{ startsWith(github.ref, 'refs/tags/staticlink-avalonia-v') || (github.event_name == 'workflow_dispatch' && inputs.publish_to_nuget == true) }} env: - NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY || secrets.NUGET_API_KEY }} run: | if [ -z "$NUGET_API_KEY" ]; then - echo "NUGET_API_KEY secret is not configured. Skipping publish." - exit 0 + echo "NUGET_API_KEY secret is not configured." >&2 + exit 1 fi for package in artifacts/nuget/*.nupkg; do @@ -506,6 +538,12 @@ jobs: name: staticlink-avalonia-nuget path: release-artifacts/staticlink-avalonia-nuget + - name: Download release source lock + uses: actions/download-artifact@v4 + with: + name: graphics-release-source-lock + path: release-artifacts/source-lock + - name: Download smoke artifacts uses: actions/download-artifact@v4 with: @@ -516,6 +554,7 @@ jobs: - name: Prepare release files run: | mkdir -p release-files + cp release-artifacts/source-lock/release.lock.json release-files/ find release-artifacts/staticlink-avalonia-nuget -type f \( -name '*.nupkg' -o -name '*.snupkg' \) -exec cp {} release-files/ \; for dir in release-artifacts/avalonia-smoke-*; do [ -d "$dir" ] || continue diff --git a/.github/workflows/static-graphics-linux-debian.yml b/.github/workflows/static-graphics-linux-debian.yml new file mode 100644 index 0000000..a6a6314 --- /dev/null +++ b/.github/workflows/static-graphics-linux-debian.yml @@ -0,0 +1,28 @@ +name: static-graphics-linux-debian-zig + +on: + workflow_call: + inputs: + skiasharp_version: + required: true + type: string + source_lock: + type: string + default: '' + workflow_dispatch: + inputs: + skiasharp_version: + type: string + default: '3.119.4' + +permissions: + contents: read + packages: write + +jobs: + build: + uses: ./.github/workflows/static-graphics-zig-cross.yml + with: + skiasharp_version: ${{ inputs.skiasharp_version }} + source_lock: ${{ inputs.source_lock || '' }} + rids: '["linux-x64","linux-arm64","linux-musl-x64","linux-musl-arm64"]' diff --git a/.github/workflows/static-graphics-linux.yml b/.github/workflows/static-graphics-linux.yml index 7a72668..4253e0c 100644 --- a/.github/workflows/static-graphics-linux.yml +++ b/.github/workflows/static-graphics-linux.yml @@ -1,4 +1,4 @@ -name: static-graphics-linux +name: static-graphics-linux-zig on: workflow_call: @@ -6,59 +6,23 @@ on: skiasharp_version: required: true type: string - angle_branch: - required: true + source_lock: type: string + default: '' workflow_dispatch: inputs: skiasharp_version: - required: true - default: "4.150.1" - type: string - angle_branch: - required: true - default: "7922" type: string + default: '3.119.4' + +permissions: + contents: read + packages: write jobs: build: - strategy: - fail-fast: false - matrix: - include: - - rid: linux-x64 - target_cpu: x64 - runner: ubuntu-24.04 - - rid: linux-arm64 - target_cpu: arm64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - env: - SKIASHARP_VERSION: ${{ inputs.skiasharp_version }} - ANGLE_BRANCH: ${{ inputs.angle_branch }} - RID: ${{ matrix.rid }} - TARGET_CPU: ${{ matrix.target_cpu }} - LLVM_AR: llvm-ar - steps: - - uses: actions/checkout@v4 - - - name: Restore native build cache - uses: actions/cache@v4 - with: - path: External/NativeStatic/.work - key: native-work-linux-${{ matrix.rid }}-${{ inputs.skiasharp_version }}-${{ inputs.angle_branch }}-${{ hashFiles('External/NativeStatic/patches/*.patch', 'scripts/build-linux-static-graphics.sh') }} - - - name: Install native build dependencies - run: | - sudo apt-get update - sudo apt-get install -y clang lld llvm g++ ninja-build pkg-config libfontconfig1-dev libglib2.0-dev libgl1-mesa-dev libpci-dev libx11-dev libxcb1-dev libxext-dev - - - name: Build Linux static graphics libraries - run: scripts/build-linux-static-graphics.sh all - - - name: Upload Linux static archives - uses: actions/upload-artifact@v4 - with: - name: native-static-${{ matrix.rid }} - path: External/NativeStatic/${{ matrix.rid }}/* - if-no-files-found: error + uses: ./.github/workflows/static-graphics-zig-cross.yml + with: + skiasharp_version: ${{ inputs.skiasharp_version }} + source_lock: ${{ inputs.source_lock || '' }} + rids: '["linux-x64","linux-arm64"]' diff --git a/.github/workflows/static-graphics-macos.yml b/.github/workflows/static-graphics-macos.yml index 46d0986..a6ccb44 100644 --- a/.github/workflows/static-graphics-macos.yml +++ b/.github/workflows/static-graphics-macos.yml @@ -1,4 +1,4 @@ -name: static-graphics-macos +name: static-graphics-macos-zig on: workflow_call: @@ -6,69 +6,23 @@ on: skiasharp_version: required: true type: string - angle_branch: - required: true + source_lock: type: string + default: '' workflow_dispatch: inputs: skiasharp_version: - required: true - default: "4.150.1" - type: string - angle_branch: - required: true - default: "7922" type: string + default: '3.119.4' + +permissions: + contents: read + packages: write jobs: build: - strategy: - fail-fast: false - matrix: - include: - - rid: osx-x64 - target_cpu: x64 - - rid: osx-arm64 - target_cpu: arm64 - runs-on: macos-15 - env: - SKIASHARP_VERSION: ${{ inputs.skiasharp_version }} - ANGLE_BRANCH: ${{ inputs.angle_branch }} - RID: ${{ matrix.rid }} - TARGET_CPU: ${{ matrix.target_cpu }} - WORK_DIR: ${{ github.workspace }}/External/NativeStatic/.work - OUTPUT_DIR: ${{ github.workspace }}/External/NativeStatic/${{ matrix.rid }}/native - steps: - - uses: actions/checkout@v4 - - - name: Restore native build cache - uses: actions/cache@v4 - with: - path: External/NativeStatic/.work - key: native-work-macos-${{ matrix.rid }}-${{ inputs.skiasharp_version }}-${{ inputs.angle_branch }}-${{ hashFiles('External/NativeStatic/patches/*.patch', 'scripts/build-macos-static-graphics.sh') }} - - - name: Install native build dependencies - run: brew install ninja - - - name: Install Xcode Metal toolchain - run: | - set -euo pipefail - cat > /tmp/staticlink-metal-check.metal <<'EOF' - #include - using namespace metal; - kernel void staticlink_metal_check(device float* values [[buffer(0)]], uint id [[thread_position_in_grid]]) { values[id] = 0; } - EOF - if ! xcrun metal -c /tmp/staticlink-metal-check.metal -o /tmp/staticlink-metal-check.air >/dev/null 2>&1; then - sudo xcodebuild -downloadComponent MetalToolchain - fi - xcrun metal -c /tmp/staticlink-metal-check.metal -o /tmp/staticlink-metal-check.air >/dev/null - - - name: Build macOS static graphics libraries - run: bash ./scripts/build-macos-static-graphics.sh all - - - name: Upload macOS static archives - uses: actions/upload-artifact@v4 - with: - name: native-static-${{ matrix.rid }} - path: External/NativeStatic/${{ matrix.rid }}/* - if-no-files-found: error + uses: ./.github/workflows/static-graphics-zig-cross.yml + with: + skiasharp_version: ${{ inputs.skiasharp_version }} + source_lock: ${{ inputs.source_lock || '' }} + rids: '["osx-x64","osx-arm64"]' diff --git a/.github/workflows/static-graphics-musl.yml b/.github/workflows/static-graphics-musl.yml index 728b27f..d272184 100644 --- a/.github/workflows/static-graphics-musl.yml +++ b/.github/workflows/static-graphics-musl.yml @@ -1,4 +1,4 @@ -name: static-graphics-musl +name: static-graphics-musl-zig on: workflow_call: @@ -6,88 +6,23 @@ on: skiasharp_version: required: true type: string - angle_branch: - required: true - type: string - dotnet_alpine_image: - required: true + source_lock: type: string + default: '' workflow_dispatch: inputs: skiasharp_version: - required: true - default: "4.150.1" - type: string - angle_branch: - required: true - default: "7922" - type: string - dotnet_alpine_image: - required: true - default: mcr.microsoft.com/dotnet/sdk:10.0.302-alpine3.23 type: string + default: '3.119.4' + +permissions: + contents: read + packages: write jobs: build: - strategy: - fail-fast: false - matrix: - include: - - rid: linux-musl-x64 - target_cpu: x64 - runner: ubuntu-24.04 - - rid: linux-musl-arm64 - target_cpu: arm64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - env: - SKIASHARP_VERSION: ${{ inputs.skiasharp_version }} - ANGLE_BRANCH: ${{ inputs.angle_branch }} - DOTNET_ALPINE_IMAGE: ${{ inputs.dotnet_alpine_image }} - steps: - - uses: actions/checkout@v4 - - - name: Restore native build cache - uses: actions/cache@v4 - with: - path: External/NativeStatic/.work - key: native-work-musl-${{ matrix.rid }}-${{ inputs.skiasharp_version }}-${{ inputs.angle_branch }}-${{ hashFiles('External/NativeStatic/patches/*.patch', 'scripts/build-linux-static-graphics.sh') }} - - - name: Build Linux musl static graphics libraries - run: | - docker run --rm \ - -v "$PWD:/workspace" \ - -w /workspace \ - -e RID="${{ matrix.rid }}" \ - -e TARGET_CPU="${{ matrix.target_cpu }}" \ - -e OUTPUT_DIR="/workspace/External/NativeStatic/${{ matrix.rid }}/native" \ - -e SKIASHARP_VERSION="$SKIASHARP_VERSION" \ - -e ANGLE_BRANCH="$ANGLE_BRANCH" \ - -e LLVM_AR=llvm-ar \ - -e DEPOT_TOOLS_BOOTSTRAP_PYTHON3=0 \ - -e VPYTHON_BYPASS="manually managed python not supported by chrome operations" \ - "$DOTNET_ALPINE_IMAGE" \ - sh -lc ' - set -e - apk add --no-cache bash ca-certificates git python3 py3-httplib2 py3-pysocks py3-six clang compiler-rt lld llvm g++ ninja pkgconf fontconfig-dev glib-dev mesa-dev libx11-dev libxcb-dev libxext-dev pciutils-dev linux-headers perl curl tar xz patch gcompat libc6-compat - git config --global --add safe.directory "*" - python3 - <<'"'"'PY'"'"' - import pathlib - import httplib2 - - path = pathlib.Path(httplib2.__file__).with_name("socks.py") - if not path.exists(): - path.write_text("from socks import *\n") - PY - bash scripts/build-linux-static-graphics.sh all - ' - - - name: Normalize native build cache ownership - run: sudo chown -R "$(id -u):$(id -g)" External/NativeStatic/.work - - - name: Upload Linux musl static archives - uses: actions/upload-artifact@v4 - with: - name: native-static-${{ matrix.rid }} - path: External/NativeStatic/${{ matrix.rid }}/* - if-no-files-found: error + uses: ./.github/workflows/static-graphics-zig-cross.yml + with: + skiasharp_version: ${{ inputs.skiasharp_version }} + source_lock: ${{ inputs.source_lock || '' }} + rids: '["linux-musl-x64","linux-musl-arm64"]' diff --git a/.github/workflows/static-graphics-preflight.yml b/.github/workflows/static-graphics-preflight.yml index 6519768..ff9ea5b 100644 --- a/.github/workflows/static-graphics-preflight.yml +++ b/.github/workflows/static-graphics-preflight.yml @@ -2,7 +2,13 @@ name: static-graphics-preflight on: workflow_call: + inputs: + skiasharp_version: + type: string + default: '' outputs: + source_lock: + value: ${{ jobs.validate.outputs.source_lock }} package_version: value: ${{ jobs.validate.outputs.package_version }} skiasharp_version: @@ -15,6 +21,7 @@ jobs: validate: runs-on: ubuntu-24.04 outputs: + source_lock: ${{ steps.versions.outputs.source_lock }} package_version: ${{ steps.versions.outputs.package_version }} skiasharp_version: ${{ steps.versions.outputs.skiasharp_version }} angle_branch: ${{ steps.versions.outputs.angle_branch }} @@ -29,7 +36,14 @@ jobs: - name: Read and validate versions id: versions shell: bash + env: + REQUESTED_VERSION: ${{ inputs.skiasharp_version }} run: | + if [ "${GITHUB_REF_TYPE:-}" = tag ] && [ -z "$REQUESTED_VERSION" ]; then + tag_version="${GITHUB_REF_NAME#staticlink-avalonia-v}" + REQUESTED_VERSION="${tag_version%%-*}" + fi + python3 scripts/resolve-release-lock.py --version "$REQUESTED_VERSION" python3 - <<'PY' import os import pathlib @@ -37,7 +51,10 @@ jobs: project = pathlib.Path("NuGet/StaticGraphics/StaticLink.Avalonia.csproj") root = ET.parse(project).getroot() + import json + lock = json.loads(pathlib.Path("build/linux-toolchain/release.lock.json").read_text()) package_version = root.findtext(".//Version") + package_version = lock["skiasharp_version"] + "-" + package_version.split("-", 1)[1] skia_reference = next( item.attrib["Version"] for item in root.findall(".//PackageReference") @@ -45,7 +62,7 @@ jobs: ) version_base, revision = package_version.rsplit(".", 1) skiasharp_version, angle_branch = version_base.split("-", 1) - if not revision.isdigit() or skiasharp_version != skia_reference: + if not revision.isdigit() or skiasharp_version != lock["skiasharp_version"]: raise SystemExit(f"Inconsistent package version: {package_version}, SkiaSharp: {skia_reference}") if os.environ.get("GITHUB_REF_TYPE") == "tag": expected_tag = f"staticlink-avalonia-v{package_version}" @@ -53,6 +70,7 @@ jobs: raise SystemExit(f"Release tag must be {expected_tag}") with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: + output.write("source_lock=" + json.dumps(lock, separators=(",", ":")) + "\n") output.write(f"package_version={package_version}\n") output.write(f"skiasharp_version={skiasharp_version}\n") output.write(f"angle_branch={angle_branch}\n") @@ -65,7 +83,7 @@ jobs: shell: bash run: | set -euo pipefail - git ls-remote --exit-code --heads https://github.com/mono/SkiaSharp.git "refs/heads/release/${SKIASHARP_VERSION}" + git ls-remote --exit-code --tags https://github.com/mono/SkiaSharp.git "refs/tags/v${SKIASHARP_VERSION}" git clone --depth 1 --branch "chromium/${ANGLE_BRANCH}" https://github.com/google/angle.git /tmp/staticlink-angle git -C /tmp/staticlink-angle apply --check "$GITHUB_WORKSPACE/External/NativeStatic/patches/angle-chromium-${ANGLE_BRANCH}.patch" diff --git a/.github/workflows/static-graphics-windows.yml b/.github/workflows/static-graphics-windows.yml index a6799e0..62bfd91 100644 --- a/.github/workflows/static-graphics-windows.yml +++ b/.github/workflows/static-graphics-windows.yml @@ -1,4 +1,4 @@ -name: static-graphics-windows +name: static-graphics-windows-zig on: workflow_call: @@ -6,87 +6,23 @@ on: skiasharp_version: required: true type: string - angle_branch: - required: true + source_lock: type: string + default: '' workflow_dispatch: inputs: skiasharp_version: - required: true - default: "4.150.1" - type: string - angle_branch: - required: true - default: "7922" type: string + default: '3.119.4' + +permissions: + contents: read + packages: write jobs: build: - strategy: - fail-fast: false - matrix: - include: - - rid: win-x64 - target_cpu: x64 - msvc_arch: x64 - - rid: win-x86 - target_cpu: x86 - msvc_arch: x86 - - rid: win-arm64 - target_cpu: arm64 - msvc_arch: amd64_arm64 - runs-on: windows-2022 - env: - SKIASHARP_VERSION: ${{ inputs.skiasharp_version }} - ANGLE_BRANCH: ${{ inputs.angle_branch }} - steps: - - uses: actions/checkout@v4 - - - name: Restore native build cache - uses: actions/cache@v4 - with: - path: External/NativeStatic/.work - key: native-work-windows-${{ matrix.rid }}-${{ inputs.skiasharp_version }}-${{ inputs.angle_branch }}-${{ hashFiles('External/NativeStatic/patches/*.patch', 'scripts/build-windows-static-graphics.ps1') }} - - - name: Setup MSVC environment - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: ${{ matrix.msvc_arch }} - - - name: Configure Chromium Visual Studio environment - shell: pwsh - run: | - "DEPOT_TOOLS_WIN_TOOLCHAIN=0" >> $env:GITHUB_ENV - "GYP_MSVS_VERSION=17.0" >> $env:GITHUB_ENV - "GYP_MSVS_OVERRIDE_PATH=$($env:VSINSTALLDIR.TrimEnd('\', '/'))" >> $env:GITHUB_ENV - - - name: Install native build dependencies - shell: pwsh - run: | - git config --global core.longpaths true - choco install ninja llvm -y - - - name: Validate Windows ANGLE inputs - shell: pwsh - env: - RID: ${{ matrix.rid }} - TARGET_CPU: ${{ matrix.target_cpu }} - DEPOT_TOOLS_WIN_TOOLCHAIN: 0 - GYP_MSVS_VERSION: 17.0 - run: .\scripts\build-windows-static-graphics.ps1 angle-preflight - - - name: Build Windows static graphics libraries - shell: pwsh - env: - RID: ${{ matrix.rid }} - TARGET_CPU: ${{ matrix.target_cpu }} - DEPOT_TOOLS_WIN_TOOLCHAIN: 0 - GYP_MSVS_VERSION: 17.0 - run: .\scripts\build-windows-static-graphics.ps1 all - - - name: Upload Windows static archives - uses: actions/upload-artifact@v4 - with: - name: native-static-${{ matrix.rid }} - path: External/NativeStatic/${{ matrix.rid }}/* - if-no-files-found: error + uses: ./.github/workflows/static-graphics-zig-cross.yml + with: + skiasharp_version: ${{ inputs.skiasharp_version }} + source_lock: ${{ inputs.source_lock || '' }} + rids: '["win-x64","win-x86","win-arm64"]' diff --git a/.github/workflows/static-graphics-zig-cross.yml b/.github/workflows/static-graphics-zig-cross.yml new file mode 100644 index 0000000..20f95bf --- /dev/null +++ b/.github/workflows/static-graphics-zig-cross.yml @@ -0,0 +1,111 @@ +name: static-graphics-zig-cross-debian12 + +# All native archives are compiled with Zig on Linux; platform runners validate consumer apps. +on: + workflow_call: + inputs: + skiasharp_version: + type: string + required: true + toolchain_image: + type: string + default: '' + rids: + type: string + default: '["linux-x64","linux-arm64","linux-musl-x64","linux-musl-arm64","win-x64","win-x86","win-arm64","osx-x64","osx-arm64"]' + artifact_prefix: + type: string + default: native-static + source_lock: + type: string + required: true + workflow_dispatch: + inputs: + skiasharp_version: + description: Locked SkiaSharp release profile + type: string + default: '3.119.4' + +permissions: + contents: read + packages: write + +jobs: + image: + if: ${{ inputs.toolchain_image == '' }} + uses: ./.github/workflows/zig-toolchain-image.yml + + build: + needs: image + if: ${{ always() && (needs.image.result == 'success' || inputs.toolchain_image != '') }} + permissions: + contents: read + packages: read + env: + TOOLCHAIN_IMAGE: ${{ inputs.toolchain_image || needs.image.outputs.image }} + timeout-minutes: 180 + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + rid: ${{ fromJSON(inputs.rids || '["linux-x64","linux-arm64","linux-musl-x64","linux-musl-arm64","win-x64","win-x86","win-arm64","osx-x64","osx-arm64"]') }} + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Resolve immutable build inputs + env: + SOURCE_LOCK_JSON: ${{ inputs.source_lock }} + REQUESTED_VERSION: ${{ inputs.skiasharp_version }} + run: | + if [ -n "$SOURCE_LOCK_JSON" ]; then + python3 - <<'PY' + import json, os, pathlib + lock = json.loads(os.environ['SOURCE_LOCK_JSON']) + if lock['skiasharp_version'] != os.environ['REQUESTED_VERSION']: + raise SystemExit('Source lock does not match requested version') + pathlib.Path('build/linux-toolchain/release.lock.json').write_text(json.dumps(lock, indent=2) + '\n') + PY + else + python3 scripts/resolve-release-lock.py --version "$REQUESTED_VERSION" + fi + - name: Restore exact target cache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: External/NativeStatic/.work + key: zig-debian12-v3-${{ inputs.skiasharp_version }}-${{ matrix.rid }}-${{ hashFiles('build/linux-toolchain/**', 'scripts/prepare-cross-sources.sh', 'scripts/build-linux-static-graphics.sh', 'External/NativeStatic/patches/*.patch') }} + - name: Pull immutable toolchain image + env: + GH_TOKEN: ${{ github.token }} + ACTOR: ${{ github.actor }} + run: | + printf '%s' "$GH_TOKEN" | docker login ghcr.io -u "$ACTOR" --password-stdin + docker pull "$TOOLCHAIN_IMAGE" + - name: Prepare locked sources and build native libraries + env: + CROSS_RID: ${{ matrix.rid }} + SKIASHARP_PROFILE: ${{ inputs.skiasharp_version }} + run: | + mkdir -p External/NativeStatic/.work/home + docker run --rm --user "$(id -u):$(id -g)" \ + -e HOME=/src/External/NativeStatic/.work/home -e CROSS_RID -e SKIASHARP_PROFILE \ + -e CCACHE_DIR=/src/External/NativeStatic/.work/ccache -e CCACHE_MAXSIZE=1G \ + -e ZIG_GLOBAL_CACHE_DIR=/src/External/NativeStatic/.work/zig-global \ + -v "$PWD:/src" -w /src --entrypoint bash "$TOOLCHAIN_IMAGE" -c ' + set -euo pipefail + export SOURCE_LOCK="/src/build/linux-toolchain/release.lock.json" + source scripts/prepare-cross-sources.sh + python3 build/linux-toolchain/build-cross.py \ + --target "$CROSS_RID" \ + --sources-manifest "$WORK_DIR/cross-sources.json" \ + --work /src/External/NativeStatic/.work/toolchains \ + --output "/src/External/NativeStatic/zig-cross/$SKIASHARP_PROFILE/$CROSS_RID" --jobs 4 + python3 build/linux-toolchain/package-zig.py \ + --rid "$CROSS_RID" \ + --input "/src/External/NativeStatic/zig-cross/$SKIASHARP_PROFILE/$CROSS_RID" \ + --output "/src/External/NativeStatic/$CROSS_RID/native" + ' + - name: Upload archives, link tests and input manifest + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: ${{ inputs.artifact_prefix || 'native-static' }}-${{ matrix.rid }} + path: External/NativeStatic/${{ matrix.rid }}/** + if-no-files-found: error diff --git a/.github/workflows/zig-recent-regression.yml b/.github/workflows/zig-recent-regression.yml new file mode 100644 index 0000000..949c267 --- /dev/null +++ b/.github/workflows/zig-recent-regression.yml @@ -0,0 +1,55 @@ +name: zig-recent-release-regression + +on: + workflow_dispatch: + inputs: + versions: + description: JSON array of stable SkiaSharp versions from this branch major + type: string + default: '["3.119.2","3.119.4"]' + +permissions: + contents: read + packages: write + +jobs: + resolve: + runs-on: ubuntu-24.04 + outputs: + releases: ${{ steps.locks.outputs.releases }} + steps: + - uses: actions/checkout@v4 + - id: locks + env: + VERSIONS: ${{ inputs.versions }} + run: | + python3 - <<'PY' + import json, os, pathlib, subprocess + versions = json.loads(os.environ['VERSIONS']) + if not isinstance(versions, list) or not 1 <= len(versions) <= 5: + raise SystemExit('Expected one to five stable versions') + releases = [] + for version in versions: + subprocess.run(['python3', 'scripts/resolve-release-lock.py', '--version', version], check=True) + lock = json.loads(pathlib.Path('build/linux-toolchain/release.lock.json').read_text()) + releases.append({'version': version, 'lock': json.dumps(lock)}) + with open(os.environ['GITHUB_OUTPUT'], 'a') as output: + output.write('releases=' + json.dumps(releases, separators=(',', ':')) + '\n') + PY + + toolchain: + needs: resolve + uses: ./.github/workflows/zig-toolchain-image.yml + + build: + needs: [resolve, toolchain] + strategy: + fail-fast: false + matrix: + release: ${{ fromJSON(needs.resolve.outputs.releases) }} + uses: ./.github/workflows/static-graphics-zig-cross.yml + with: + skiasharp_version: ${{ matrix.release.version }} + source_lock: ${{ matrix.release.lock }} + toolchain_image: ${{ needs.toolchain.outputs.image }} + artifact_prefix: regression-${{ matrix.release.version }} diff --git a/.github/workflows/zig-toolchain-image.yml b/.github/workflows/zig-toolchain-image.yml new file mode 100644 index 0000000..9de3660 --- /dev/null +++ b/.github/workflows/zig-toolchain-image.yml @@ -0,0 +1,57 @@ +name: pinned-zig-toolchain-image + +on: + workflow_call: + outputs: + image: + description: Immutable Debian 12 Zig toolchain image reference + value: ${{ jobs.image.outputs.image }} + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + image: + runs-on: ubuntu-24.04 + timeout-minutes: 60 + outputs: + image: ${{ steps.publish.outputs.image }} + steps: + - uses: actions/checkout@v4 + - name: Publish or reuse exact toolchain image + id: publish + env: + GH_TOKEN: ${{ github.token }} + ACTOR: ${{ github.actor }} + REPOSITORY: ${{ github.repository }} + BASE_HASH: ${{ hashFiles('build/linux-toolchain/Dockerfile') }} + RECIPE_HASH: ${{ hashFiles('build/linux-toolchain/Dockerfile*', 'build/linux-toolchain/*.py', 'build/linux-toolchain/*.c', 'build/linux-toolchain/*.gn.in', 'build/linux-toolchain/*lock.json', 'build/linux-toolchain/versions/*.json', 'build/linux-toolchain/patches/**') }} + run: | + set -euo pipefail + printf '%s' "$GH_TOKEN" | docker login ghcr.io -u "$ACTOR" --password-stdin + repository="ghcr.io/${REPOSITORY,,}-zig" + image="$repository:recipe-$RECIPE_HASH" + base="$repository:debian12-$BASE_HASH" + if ! docker pull "$image"; then + if ! docker pull "$base"; then + docker build --platform linux/amd64 \ + --label "org.opencontainers.image.source=https://github.com/$REPOSITORY" \ + -t "$base" build/linux-toolchain + docker push "$base" + docker pull "$base" + fi + base_digest="$(docker image inspect --format '{{index .RepoDigests 0}}' "$base")" + docker build --platform linux/amd64 \ + --build-arg "TOOLCHAIN_IMAGE=$base_digest" \ + --label "org.opencontainers.image.source=https://github.com/$REPOSITORY" \ + -f build/linux-toolchain/Dockerfile.cross-probe \ + -t "$image" build/linux-toolchain + docker push "$image" + docker pull "$image" + fi + digest="$(docker image inspect --format '{{index .RepoDigests 0}}' "$image")" + case "$digest" in "$repository"@sha256:*) ;; *) echo 'No immutable image digest' >&2; exit 1 ;; esac + echo "image=$digest" >> "$GITHUB_OUTPUT" + echo "Toolchain: $digest" >> "$GITHUB_STEP_SUMMARY" diff --git a/ACTION-TESTING.md b/ACTION-TESTING.md new file mode 100644 index 0000000..aca5b6f --- /dev/null +++ b/ACTION-TESTING.md @@ -0,0 +1,32 @@ +# Manual artifact validation + +Daily schedules are disabled during validation. No NuGet.org publication or +GitHub Release is requested by the manual dispatcher on main. + +- skia3: nuget-static-graphics.yml; latest stable SkiaSharp 3. +- skia4: nuget-static-graphics.yml; latest stable SkiaSharp 4. +- avalonia-native: nuget-avalonia-native.yml; latest stable Avalonia 11 and 12. + +Download staticlink-avalonia-nuget and avalonia-smoke- from each graphics run. +Download staticlink-avalonia-native- and +avalonia-native-smoke-- from the native run. +Native archives and source locks are also retained as Actions artifacts. + +Graphics macOS smoke tests use the already published AvaloniaNative revision 1 +by default, so the graphics builds do not require the experimental native +revision 2 to be published. The native branch validates its own local revision 2 +package separately. To test both experimental packages together, put both +downloaded packages in a local NuGet feed and reference their exact versions. + +Smoke executables are retained even if a later runtime validation fails. +An artifact is not evidence that all validation gates passed; check job results. + +The daily-native-release workflow on main currently supports manual dispatch +only. Its three matrix jobs check out their own branch and dispatch independently. +Use force_build=true to rebuild published versions for testing. +After validation, enable the schedule on main only; non-default branches cannot +run their own GitHub cron schedules. Publication remains an explicit opt-in. +While publication is disabled, an unpublished package may be rebuilt on every +check; add successful-run tracking before enabling artifact-only daily builds +if avoiding repeated builds is required. + diff --git a/DAILY-RELEASE.md b/DAILY-RELEASE.md new file mode 100644 index 0000000..c76e9cb --- /dev/null +++ b/DAILY-RELEASE.md @@ -0,0 +1,9 @@ +# Daily releases (not enabled) + +This branch currently runs manual artifact validation only. The independent +three-branch dispatcher is on main and has no schedule yet. NuGet.org and +GitHub Release publication are disabled for the initial runs. + +See [ACTION-TESTING.md](ACTION-TESTING.md) for artifact names, the macOS native +package used by graphics smoke tests, and the steps required before daily +builds are enabled. diff --git a/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj b/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj index 639f7f2..ea37910 100644 --- a/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj +++ b/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj @@ -19,6 +19,8 @@ + + diff --git a/NuGet/StaticGraphics/README.md b/NuGet/StaticGraphics/README.md index 2aa6a42..81f0a9e 100644 --- a/NuGet/StaticGraphics/README.md +++ b/NuGet/StaticGraphics/README.md @@ -11,13 +11,13 @@ Choose the static graphics package that matches the Avalonia and SkiaSharp major | 11 | 2.88.9 | `2.88.9-7151.10` | | 11 | 3.119.4 | `3.119.4-7922.1` | | 12 | 3.119.4 | `3.119.4-7922.1` | -| 12 | 4.150.1 | `4.150.1-7922.1` | +| 12 | 3.119.4 | `3.119.4-7922.1` | Example: ```xml - + ``` diff --git a/NuGet/StaticGraphics/StaticLink.Avalonia.csproj b/NuGet/StaticGraphics/StaticLink.Avalonia.csproj index 69ca529..4ff94ab 100644 --- a/NuGet/StaticGraphics/StaticLink.Avalonia.csproj +++ b/NuGet/StaticGraphics/StaticLink.Avalonia.csproj @@ -6,7 +6,7 @@ false false StaticLink.Avalonia - 4.150.1-7922.1 + 3.119.4-7922.2 greepar greepar Static SkiaSharp, HarfBuzzSharp, and ANGLE libraries for Avalonia NativeAOT single-file publishing. @@ -19,11 +19,12 @@ - + + diff --git a/NuGet/StaticGraphics/buildTransitive/StaticLink.Avalonia.targets b/NuGet/StaticGraphics/buildTransitive/StaticLink.Avalonia.targets index 59cefce..2ac3d4c 100644 --- a/NuGet/StaticGraphics/buildTransitive/StaticLink.Avalonia.targets +++ b/NuGet/StaticGraphics/buildTransitive/StaticLink.Avalonia.targets @@ -163,4 +163,19 @@ + + + lld-link + + + + + + + + + + diff --git a/README.md b/README.md index 3dfaa7c..0528547 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +> 3/4 分支构建更新:原生图形库与 macOS AvaloniaNative 已统一为 Linux 上的 Zig 入口;Windows/macOS runner 用于消费者运行验收。配置与真实编译的验证范围见 [CROSS-COMPILE.md](build/linux-toolchain/CROSS-COMPILE.md),每日发布说明见 [DAILY-RELEASE.md](DAILY-RELEASE.md)。完整九平台回归与 Docker 镜像尚待 Action 实跑,不能视为已全部通过。 + # StaticLink.Avalonia Static native libraries for Avalonia single-file NativeAOT publishing. diff --git a/build/daily-release-validation.json b/build/daily-release-validation.json new file mode 100644 index 0000000..cb5310d --- /dev/null +++ b/build/daily-release-validation.json @@ -0,0 +1,72 @@ +{ + "checked_at_utc": "2026-10-04", + "live_plan": [ + { + "ref": "4.150.1-7922.1", + "skiasharp_version": "4.153.1", + "package_version": "4.153.1-7922.1" + } + ], + "source_lock_checks": { + "3.119.2": { + "skiasharp_version": "3.119.2", + "skiasharp_revision": "2888c737ad016d584c74525e2d35db5097ea8576", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "927041a58f130e0dd0562ba86cb4170989ad39e9" + }, + "3.119.4": { + "skiasharp_version": "3.119.4", + "skiasharp_revision": "f568ac94dd768ef9a2f593537cfde2dd0d348ef5", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "7dbfc07dd33181f84e0958afb7ee805c6c769f0b" + }, + "4.150.2": { + "skiasharp_version": "4.150.2", + "skiasharp_revision": "fd1fdd5239f826514651a4ada5d7bb3dcec2fb43", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "b70519562e2d26d3cf56286d3a13ca25152cfae4" + }, + "4.150.3": { + "skiasharp_version": "4.150.3", + "skiasharp_revision": "f8a404ff3375872d042404a29189ceb8e3001a41", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "eb86e4b3ee034085b62d3980b3d94629862441e6" + }, + "4.150.5": { + "skiasharp_version": "4.150.5", + "skiasharp_revision": "16f82682f756217654eb1a152cdb639e358b8976", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "579fa81eeed6906732e27b7d95149538584dd20b" + }, + "4.153.1": { + "skiasharp_version": "4.153.1", + "skiasharp_revision": "4783f51448f9b070dda4f87b83e941c9599e466e", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "45afab4f1f0921f3feb97f58cf89b136fffa85e6" + } + }, + "checks_passed": [ + "actionlint", + "python-json-yaml-bash-syntax", + "numeric-stable-version-selection", + "skip-published-version", + "freeze-skia2", + "real-nuget-discovery", + "immutable-tag-and-skia-gitlink-resolution", + "generated-package-version-and-exact-dependency" + ], + "not_run": [ + "full-build-of-the-six-new-versions", + "github-actions", + "docker-cold-build", + "windows-macos-runtime", + "dotnet-pack", + "nuget-publish" + ] +} diff --git a/build/linux-toolchain/CROSS-COMPILE.md b/build/linux-toolchain/CROSS-COMPILE.md new file mode 100644 index 0000000..dcb38a6 --- /dev/null +++ b/build/linux-toolchain/CROSS-COMPILE.md @@ -0,0 +1,60 @@ +# Zig cross compilation for SkiaSharp 3 / 4 + +All nine native targets use Zig 0.14.1 from Linux: linux-x64, linux-arm64, +linux-musl-x64, linux-musl-arm64, win-x64, win-x86, win-arm64, osx-x64, osx-arm64. +Windows uses the GNU ABI, without MSVC. macOS uses a checksum-pinned real SDK, +without Xcode. AvaloniaNative 11/12 also compiles with Zig on Linux. + +## Local evidence (2026-10-04) + +| Check | Result | +| --- | --- | +| 3.119.2, 3.119.4, 4.150.3, 4.150.5, 4.153.1 × nine GN configurations | 45 passed; these are configuration checks | +| All five versions, Windows x64 Skia/SkiaSharp/HarfBuzz compilation and executable link | Passed | +| 3.119.4 Windows x86 Skia and stdcall thunks | Compilation and raster/EGL links passed; fixed ANGLE archives reused | +| 3.119.4 Linux x64 | All six archives and both links passed; raster/UTF-8 execution passed | +| 3.119.4 Linux ARM64 | Three Skia archives and raster link passed; raster/UTF-8 execution passed under QEMU | +| 4.153.1 macOS ARM64 | Three Skia archives and raster executable link passed | +| AvaloniaNative 11.3.14 / 12.1.0, both macOS architectures | Four full compilations and SDK libc++ executable links passed | + +Exact scope is recorded in `recent-version-validation.json`. Earlier baseline +results are retained separately. The raster probe checks 64 pixels and UTF-8 +buffer ingestion; it does not prove complete font shaping or GPU operation. + +There is no local Docker daemon or Windows/macOS runtime. The complete Debian +image, five releases × nine full builds, NativeAOT consumer applications and +actual Actions/NuGet publication have not been verified locally. Local builds +used the Linux host and pinned cross tools, not a running Debian container. + +## Repeatable inputs and failure policy + +`Dockerfile` pins Debian 12 by digest, apt by snapshot and depot_tools by commit. +`Dockerfile.cross-probe` is the production cross image despite its historical +name. It pins Zig, the macOS SDK, WinRT headers, availability runtime and Linux +sysroot packages by checksum. SDK extraction supports pinned Debian Python 3.11. + +`zig-toolchain-image.yml` builds/reuses GHCR images by recipe digest and passes +an immutable image digest to downstream builds. Repository package write +permission is required. Source profiles pin SkiaSharp, Skia and ANGLE commits. +The existing exact per-target source/build cache has no fallback restores; +ccache additionally checks compiler contents. Cold downloads remain necessary. + +`build-cross.py` adapts declared GN capabilities, rejects unknown compiler flags, +checks static targets, builds six archives and links raster and EGL/GLES probes. +`package-zig.py` requires archive hashes and successful links before packaging. +Windows x86 gets generated stdcall thunks. Captured Zig runtime libraries are +included for consumer linking. A successful C link does not establish NativeAOT +compatibility; the release workflow gates publication on consumer OS tests. + +## Run + +Use the `zig-recent-regression` manual Action for recent-version full matrices. +Use `static-graphics-zig-cross-debian12` for one pinned release and nine RIDs. +For already synced sources, run `build/linux-toolchain/build-cross.py --help`; +`--configure-only` configures without compiling, while `--component skia` checks +only the three Skia libraries and their executable link. + +This common driver supports the tested source revisions. Future compiler, +SDK, GN or source changes can still require deliberate updates. Daily releases +fail closed and publish only after all required jobs pass. SkiaSharp 2 is frozen +and excluded from the new regression/release tracks. diff --git a/build/linux-toolchain/Dockerfile b/build/linux-toolchain/Dockerfile new file mode 100644 index 0000000..8c5b5a2 --- /dev/null +++ b/build/linux-toolchain/Dockerfile @@ -0,0 +1,36 @@ +# Multi-architecture index; deliberately update together with the snapshot/cache key. +FROM debian:bookworm-slim@sha256:3783cc01769c7b2b1b83a5c5ad96c815348e28ed7da68e2e3687004faa906251 + +ARG DEBIAN_SNAPSHOT=20260901T000000Z +ARG DEPOT_TOOLS_REVISION=8a5434051036b32412a2ecb10c213a72e3f3ccb9 +ENV DEBIAN_FRONTEND=noninteractive \ + DEPOT_TOOLS_UPDATE=0 \ + DEPOT_TOOLS_REVISION=${DEPOT_TOOLS_REVISION} \ + LLVM_AR=llvm-ar-16 \ + BUILD_JOBS=4 + +# HTTP bootstrap uses Debian's signed Release metadata. Do not disable signature checks. +RUN rm -f /etc/apt/sources.list.d/debian.sources \ + && printf 'deb [check-valid-until=no] http://snapshot.debian.org/archive/debian/%s bookworm main\ndeb [check-valid-until=no] http://snapshot.debian.org/archive/debian-security/%s bookworm-security main\n' "$DEBIAN_SNAPSHOT" "$DEBIAN_SNAPSHOT" > /etc/apt/sources.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates git curl python3 python3-six python3-requests python3-httplib2 \ + build-essential ccache clang-16 lld-16 llvm-16 libclang-rt-16-dev ninja-build \ + pkg-config cmake binutils file unzip xz-utils bzip2 patch \ + libfontconfig1-dev libglib2.0-dev libgl1-mesa-dev libpci-dev \ + libx11-dev libxcb1-dev libxext-dev libwayland-dev libexpat1-dev libicu72 libssl3 \ + && for tool in clang clang++ llvm-ar llvm-ranlib llvm-nm; do \ + ln -s "/usr/bin/$tool-16" "/usr/local/bin/$tool"; \ + done \ + && mkdir -p /opt/staticlink \ + && dpkg-query -W > /opt/staticlink/packages.lock \ + && rm -rf /var/lib/apt/lists/* + +# Store the pinned checkout inside the image so the runner never selects depot_tools HEAD. +RUN git init /opt/depot_tools \ + && git -C /opt/depot_tools remote add origin https://chromium.googlesource.com/chromium/tools/depot_tools.git \ + && git -C /opt/depot_tools fetch --depth 1 origin "$DEPOT_TOOLS_REVISION" \ + && git -C /opt/depot_tools checkout --detach FETCH_HEAD +ENV DEPOT_TOOLS_DIR=/opt/depot_tools +WORKDIR /src +CMD ["bash"] diff --git a/build/linux-toolchain/Dockerfile.cross-probe b/build/linux-toolchain/Dockerfile.cross-probe new file mode 100644 index 0000000..ee33caf --- /dev/null +++ b/build/linux-toolchain/Dockerfile.cross-probe @@ -0,0 +1,57 @@ +# Build this local base from Dockerfile first; it contains the pinned Debian toolchain. +ARG TOOLCHAIN_IMAGE=staticlink-debian12:probe-base +FROM mcr.microsoft.com/dotnet/sdk:10.0.302@sha256:72dd743782f2ae7e5476fd64f6a460045e3998dc862218b80e6944cba79a01b0 AS dotnet10 +FROM mcr.microsoft.com/dotnet/sdk:8.0.416@sha256:8b5eece40d8a8050c09332eab1e65563fd4c928c5881e9e72bca7a4b0339f881 AS dotnet8 +FROM ${TOOLCHAIN_IMAGE} + +ARG TARGETARCH +RUN case "$TARGETARCH" in \ + amd64) zig_arch=x86_64; zig_sha=24aeeec8af16c381934a6cd7d95c807a8cb2cf7df9fa40d359aa884195c4716c ;; \ + arm64) zig_arch=aarch64; zig_sha=f7a654acc967864f7a050ddacfaa778c7504a0eca8d2b678839c21eea47c992b ;; \ + *) echo "Unsupported host architecture: $TARGETARCH" >&2; exit 1 ;; \ + esac \ + && curl --fail --location --retry 3 \ + "https://ziglang.org/download/0.14.1/zig-$zig_arch-linux-0.14.1.tar.xz" -o /tmp/zig.tar.xz \ + && printf '%s /tmp/zig.tar.xz\n' "$zig_sha" | sha256sum --check \ + && mkdir /opt/zig \ + && tar -xJf /tmp/zig.tar.xz -C /opt/zig --strip-components=1 \ + && ln -s /opt/zig/zig /usr/local/bin/zig \ + && rm /tmp/zig.tar.xz + +ARG MACOS_SDK_SHA256=c15cf0f3f17d714d1aa5a642da8e118db53d79429eb015771ba816aa7c6c1cbd +# Architecture-independent MinGW WinRT headers supplement Zig's SDK. Extract +# files only: do not install Ubuntu binaries or change Debian package resolution. +RUN curl --fail --location --retry 3 \ + https://snapshot.ubuntu.com/ubuntu/20260828T000000Z/pool/universe/m/mingw-w64/mingw-w64-common_11.0.1-3build1_all.deb \ + -o /tmp/mingw.deb \ + && printf '%s /tmp/mingw.deb\n' \ + 4a42fd30fbc3838c58c929d8a4882682fc12408bb7a0e6cafa913c0f0a57c187 | sha256sum --check \ + && mkdir /opt/mingw && dpkg-deb --extract /tmp/mingw.deb /opt/mingw \ + && rm /tmp/mingw.deb + +RUN curl --fail --location --retry 3 \ + https://github.com/joseluisq/macosx-sdks/releases/download/15.5/MacOSX15.5.sdk.tar.xz \ + -o /tmp/macos-sdk.tar.xz \ + && printf '%s /tmp/macos-sdk.tar.xz\n' "$MACOS_SDK_SHA256" | sha256sum --check \ + && mkdir /opt/macos-sdk \ + && tar -xJf /tmp/macos-sdk.tar.xz -C /opt/macos-sdk \ + && rm /tmp/macos-sdk.tar.xz + +RUN mkdir -p /opt/staticlink \ + && curl --fail --location --retry 3 \ + https://raw.githubusercontent.com/llvm/llvm-project/llvmorg-19.1.7/compiler-rt/lib/builtins/os_version_check.c \ + -o /opt/staticlink/os_version_check.c \ + && printf '%s /opt/staticlink/os_version_check.c\n' \ + bc0b9f10e8d1a1ac5905d710f1c9fead23878e31233748d580393edc4e3bfc18 | sha256sum --check + +COPY *.py smoke-*.c sources.lock.json cross-toolchain.lock.json *.gn.in /opt/staticlink/ +COPY patches/ /opt/staticlink/patches/ +COPY versions/ /opt/staticlink/versions/ +ENTRYPOINT ["python3", "/opt/staticlink/probe-cross.py"] + +COPY --from=dotnet8 /usr/share/dotnet /opt/dotnet +COPY --from=dotnet10 /usr/share/dotnet /opt/dotnet +ENV DOTNET_ROOT=/opt/dotnet PATH=/opt/dotnet:${PATH} +COPY fetch-linux-sysroots.py linux-sysroots*.lock.json /opt/staticlink/ +RUN python3 /opt/staticlink/fetch-linux-sysroots.py --output /opt/sysroots \ + --locks /opt/staticlink/linux-sysroots.lock.json /opt/staticlink/linux-sysroots-musl.lock.json diff --git a/build/linux-toolchain/README.md b/build/linux-toolchain/README.md new file mode 100644 index 0000000..66b2745 --- /dev/null +++ b/build/linux-toolchain/README.md @@ -0,0 +1,20 @@ +# Debian 12 Zig cross toolchain + +The production native-build path is the pinned Debian 12 image plus Zig 0.14.1, +covering all nine GNU Linux, musl Linux, Windows and macOS targets. musl uses +checksum-locked target sysroots inside the same Debian image. + +Read [CROSS-COMPILE.md](CROSS-COMPILE.md) for current build commands, exact local +validation scope and remaining Docker/runtime/NativeAOT verification. Read +[DAILY-RELEASE.md](../../DAILY-RELEASE.md) for daily 3/4 release configuration. + +`Dockerfile` pins the base digest, apt snapshot and source-preparation tools. +`Dockerfile.cross-probe` adds Zig, macOS SDK, Windows headers, target Linux SDKs +and .NET SDKs. The historical filename is retained for existing references. +`zig-toolchain-image.yml` reuses recipe-tagged GHCR images and downstream jobs +pull their immutable digest. Source locks and archive/link manifests provide +traceability. No complete local Docker image build has been run. + +Legacy platform helper scripts are retained for reference; production graphics +Actions now delegate to the common Zig cross-build workflow. SkiaSharp 2 remains +outside the new daily tracks. diff --git a/build/linux-toolchain/angle-linux.gn.in b/build/linux-toolchain/angle-linux.gn.in new file mode 100644 index 0000000..bbc5064 --- /dev/null +++ b/build/linux-toolchain/angle-linux.gn.in @@ -0,0 +1,23 @@ +target_os = "linux" +target_cpu = "@CPU@" +is_debug = false +is_component_build = false +is_clang = true +treat_warnings_as_errors = false +use_custom_libcxx = false +use_custom_libcxx_for_host = false +use_sysroot = false +use_lld = false +use_thin_lto = false +symbol_level = 0 +angle_build_tests = false +build_angle_deqp_tests = false +angle_enable_swiftshader = false +angle_enable_vulkan = false +angle_enable_wgpu = false +clang_use_chrome_plugins = false +enable_rust = false +clang_version = "19" + +# Chromium UI/ATK is not part of the standalone ANGLE library targets. +use_glib = false diff --git a/build/linux-toolchain/angle-mac.gn.in b/build/linux-toolchain/angle-mac.gn.in new file mode 100644 index 0000000..5d8cd60 --- /dev/null +++ b/build/linux-toolchain/angle-mac.gn.in @@ -0,0 +1,21 @@ +target_os = "mac" +target_cpu = "@CPU@" +is_debug = false +is_component_build = false +is_clang = true +treat_warnings_as_errors = false +use_custom_libcxx = false +use_custom_libcxx_for_host = false +use_sysroot = false +use_lld = false +use_thin_lto = false +symbol_level = 0 +angle_build_tests = false +build_angle_deqp_tests = false +angle_enable_swiftshader = false +angle_enable_vulkan = false +angle_enable_wgpu = false +clang_use_chrome_plugins = false +enable_rust = false +use_system_xcode = true +clang_version = "19" diff --git a/build/linux-toolchain/angle-win.gn.in b/build/linux-toolchain/angle-win.gn.in new file mode 100644 index 0000000..5867e65 --- /dev/null +++ b/build/linux-toolchain/angle-win.gn.in @@ -0,0 +1,25 @@ +target_os = "win" +target_cpu = "@CPU@" +is_debug = false +is_component_build = false +is_clang = true +treat_warnings_as_errors = false +use_custom_libcxx = false +use_custom_libcxx_for_host = false +use_sysroot = false +use_lld = false +use_thin_lto = false +symbol_level = 0 +angle_build_tests = false +build_angle_deqp_tests = false +angle_enable_swiftshader = false +angle_enable_vulkan = false +angle_enable_wgpu = false +clang_use_chrome_plugins = false +enable_rust = false + +visual_studio_version = "2022" +windows_sdk_version = "10.0.22621.0" +clang_version = "19" +use_clang_modules = false +angle_copy_compiler_dll = false diff --git a/build/linux-toolchain/build-avalonia-native-cross.py b/build/linux-toolchain/build-avalonia-native-cross.py new file mode 100644 index 0000000..dc8b4a7 --- /dev/null +++ b/build/linux-toolchain/build-avalonia-native-cross.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Build Avalonia's pinned macOS native sources with Zig on Linux.""" +import argparse,subprocess,re,json,os,hashlib +from pathlib import Path +p=argparse.ArgumentParser();p.add_argument('--source',type=Path,required=True);p.add_argument('--rid',choices=['osx-x64','osx-arm64'],required=True);p.add_argument('--sdk',type=Path,required=True);p.add_argument('--zig',default='zig');p.add_argument('--output',type=Path,required=True);p.add_argument('--work',type=Path,required=True);a=p.parse_args() +a.source=a.source.resolve();a.output=a.output.resolve();a.work=a.work.resolve();a.output.mkdir(parents=True,exist_ok=True);a.work.mkdir(parents=True,exist_ok=True) +repo=Path(__file__).resolve().parents[2] +tools=a.work/'tools' +subprocess.run(['python3',str(repo/'build/linux-toolchain/zig-cross.py'),'--zig',a.zig,'--target',a.rid,'--sdk',str(a.sdk),'--output',str(tools)],check=True) +descriptor=tools/'toolchain.json' +config=json.loads(descriptor.read_text());config['target']=config['target'].split('-macos.')[0]+'-macos.12.0.0';descriptor.write_text(json.dumps(config,indent=2)+'\n') +native=a.source/'native/Avalonia.Native';project=native/'src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj' +headers=native/'inc' +if not (headers/'avalonia-native.h').is_file(): + header_project=native/'Avalonia.Native.macOS.proj' + if header_project.is_file(): + subprocess.run(['dotnet','build',str(header_project),'-t:GenerateMicroComItems'],cwd=a.source,check=True) + else: + subprocess.run(['bash',str(native/'generate-headers.sh')],cwd=a.source,check=True) +# Match the authoritative Xcode Sources build phases, rather than guessing every .mm in the repo. +project_text=project.read_text() +names=set(re.findall(r'/\* (.+?\.(?:mm|m|cpp|cc|c)) in Sources \*/',project_text)) +file_flags={} +for name,flags in re.findall(r'/\* (.+?\.(?:mm|m|cpp|cc|c)) in Sources \*/[^\n]*COMPILER_FLAGS = \"([^\"]+)\"',project_text): + import shlex + file_flags[name]=shlex.split(flags) +if not names:raise SystemExit('No authoritative Xcode source list found') +objects=[];source_hashes={} +for name in sorted(names): + matches=list(native.rglob(name)) + if len(matches)!=1:raise SystemExit('Ambiguous or missing Xcode source '+name+': '+str(matches)) + source=matches[0];obj=a.work/(hashlib.sha256(str(source.relative_to(native)).encode()).hexdigest()+'.o') + command=[str(tools/('cc' if source.suffix in ('.c','.m') else 'cxx')),'-O2','-fPIC','-fblocks','-mmacosx-version-min=12.0','-I'+str(headers),'-I'+str(native/'src/OSX'),'-I'+str(native/'src'),'-c',str(source),'-o',str(obj)] + if source.suffix in ('.mm','.m'):command+=['-fobjc-arc'] + if source.suffix in ('.mm','.cpp','.cc'):command+=['-std=c++17'] + command+=file_flags.get(name,[]) + subprocess.run(command,check=True);objects.append(obj);source_hashes[str(source.relative_to(native))]=hashlib.sha256(source.read_bytes()).hexdigest() +subprocess.run([str(tools/'ar'),'rcs',str(a.output/'libAvaloniaNative.a'),*map(str,objects)],check=True) +# The macOS font pre-initializer is compiled by the same cross compiler. +env=dict(os.environ,RID=a.rid,OUTPUT_DIR=str(a.output),CC=str(tools/'cc'),STATICLINK_ZIG_CROSS='1') +subprocess.run(['bash',str(repo/'scripts/build-macos-font-preinit.sh')],env=env,check=True) +commit=subprocess.check_output(['git','-C',str(a.source),'rev-parse','HEAD'],text=True).strip() +(a.output/'avalonia-native-build.json').write_text(json.dumps({'source_commit':commit,'rid':a.rid,'toolchain':json.loads((tools/'toolchain.json').read_text()),'sources':source_hashes,'validation':'compiled archive; macOS AOT/runtime validation runs separately'},indent=2)+'\n') diff --git a/build/linux-toolchain/build-cross.py b/build/linux-toolchain/build-cross.py new file mode 100644 index 0000000..17941f2 --- /dev/null +++ b/build/linux-toolchain/build-cross.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +"""Build pinned, already dependency-synced SkiaSharp/ANGLE sources from Linux.""" +import argparse +import hashlib +import json +import os +from pathlib import Path +import platform +import shutil +import subprocess +import sys +from skia_compat import archive, configure, parse_args, repair_xps, repair_skcms + +HERE = Path(__file__).resolve().parent + + +def run(args, cwd=None, env=None): + print('+', ' '.join(map(str, args)), flush=True) + subprocess.run(list(map(str, args)), cwd=cwd, env=env, check=True) + + +def patch(source, filename): + path = HERE / 'patches' / filename + cmd = ['git', '-C', str(source), 'apply'] + if subprocess.run(cmd + ['--reverse', '--check', str(path)], capture_output=True).returncode == 0: + return + run(cmd + ['--check', path]) + run(cmd + [path]) + + +def main(): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument('--target', choices=['linux-x64', 'linux-arm64', 'linux-musl-x64', 'linux-musl-arm64', 'win-x64', 'win-x86', 'win-arm64', 'osx-x64', 'osx-arm64'], required=True) + p.add_argument('--sources-manifest', type=Path, help='cross-sources.json from prepare-cross-sources.sh') + p.add_argument('--source-lock', type=Path, help='Exact version profile; defaults to sources.lock.json') + p.add_argument('--skia', type=Path, help='SkiaSharp externals/skia, with its pinned dependencies synced') + p.add_argument('--angle', type=Path, help='ANGLE source with pinned DEPS synced, including tools/win') + p.add_argument('--angle-gn', type=Path, help='GN from the pinned ANGLE DEPS') + p.add_argument('--zig', default='zig') + p.add_argument('--sdk', type=Path, default=Path('/opt/macos-sdk/MacOSX15.5.sdk')) + p.add_argument('--mingw-headers', type=Path, default=Path('/opt/mingw/usr/share/mingw-w64/include')) + p.add_argument('--availability-source', type=Path, default=Path('/opt/staticlink/os_version_check.c')) + p.add_argument('--linux-sysroots', type=Path, default=Path('/opt/sysroots')) + p.add_argument('--linux-headers', type=Path, default=Path('/opt/staticlink/linux-headers')) + p.add_argument('--work', type=Path, required=True) + p.add_argument('--output', type=Path, required=True) + p.add_argument('--jobs', type=int, default=4) + p.add_argument('--configure-only', action='store_true') + p.add_argument('--component', choices=['all', 'skia'], default='all', help='Build Skia alone when validating a new SkiaSharp release') + a = p.parse_args() + if a.sources_manifest: + sources = json.loads(a.sources_manifest.read_text()) + if a.source_lock is None and sources.get('source_lock'): + a.source_lock = Path(sources['source_lock']) + for name in ('skia', 'angle', 'angle_gn'): + if getattr(a, name) is None: + setattr(a, name, Path(sources[name])) + if any(getattr(a, name) is None for name in ('skia', 'angle', 'angle_gn')): + p.error('Provide --sources-manifest or all of --skia, --angle and --angle-gn') + if platform.system() != 'Linux' or a.jobs < 1: + p.error('Requires Linux and positive --jobs') + for name in ('skia', 'angle', 'angle_gn', 'sdk', 'work', 'output', 'availability_source', 'mingw_headers'): + setattr(a, name, getattr(a, name).resolve()) + lock = json.loads((a.source_lock or HERE / 'sources.lock.json').read_text()) + for src, key in ((a.skia, 'skia_revision'), (a.angle, 'angle_revision')): + revision = subprocess.check_output(['git', '-C', str(src), 'rev-parse', 'HEAD'], text=True).strip() + if revision != lock[key]: + raise SystemExit(f'{src}: expected {lock[key]}, got {revision}; update the lock and revalidate first') + if 'skiasharp_build("SkiaSharp")' not in (a.skia / 'BUILD.gn').read_text(): + raise SystemExit('--skia must be the SkiaSharp-patched Skia source, not vanilla Google Skia') + a.work.mkdir(parents=True, exist_ok=True) + a.output.mkdir(parents=True, exist_ok=True) + windows = a.target.startswith('win') + mac = a.target.startswith('osx') + linux = a.target.startswith('linux') + cpu = a.target.rsplit('-', 1)[1] + os_name = 'win' if windows else ('mac' if mac else 'linux') + if windows and not (a.mingw_headers / 'windows.ui.composition.h').is_file(): + raise SystemExit('Windows ANGLE requires --mingw-headers containing the pinned WinRT headers') + recipe = hashlib.sha256() + for path in sorted(HERE.rglob('*')): + if path.is_file() and (path.suffix in ('.py', '.in', '.patch', '.c') or path.name in ('cross-toolchain.lock.json', 'linux-sysroots.lock.json', 'linux-sysroots-musl.lock.json')): + recipe.update(str(path.relative_to(HERE)).encode()) + recipe.update(path.read_bytes()) + variant = hashlib.sha256((json.dumps(lock, sort_keys=True) + recipe.hexdigest()).encode()).hexdigest()[:16] + shared = {'angle_revision': lock['angle_revision'], 'recipe': recipe.hexdigest(), 'toolchain': json.loads((HERE / 'cross-toolchain.lock.json').read_text())} + angle_variant = hashlib.sha256(json.dumps(shared, sort_keys=True).encode()).hexdigest()[:16] + tools = a.work / angle_variant / a.target + host_cpu = 'arm64' if platform.machine() == 'aarch64' else 'x64' + host_tools = a.work / angle_variant / ('linux-' + host_cpu) + for target, output in ((a.target, tools), ('linux-' + host_cpu, host_tools)): + command = [sys.executable, HERE / 'zig-cross.py', '--zig', a.zig, '--target', target, + '--output', output, '--sdk', a.sdk, '--mingw-headers', a.mingw_headers] + if target.startswith('linux'): + command += ['--linux-sysroot', a.linux_sysroots / target, '--linux-headers', a.linux_headers] + run(command) + config = json.loads((tools / 'toolchain.json').read_text()) + zig = config['zig'] + zig_lib = Path(json.loads(subprocess.check_output([zig, 'env'], text=True))['lib_dir']) + print('skcms Clang compatibility:', repair_skcms(a.skia), flush=True) + if windows: + print('XPS compatibility:', repair_xps(a.skia), flush=True) + # First apply the project's static ANGLE target patch if necessary. + if 'angle_static_library("libANGLE_static")' not in (a.angle / 'BUILD.gn').read_text(): + patch(a.angle, 'angle-static-targets.patch') + for filename in ('BUILDCONFIG.gn-zig.patch', 'BUILD.gn-zig.patch', + 'sdk_info-linux.patch', 'find_sdk-linux.patch', 'gcc-toolchain-objc.patch'): + patch(a.angle, filename) + run([sys.executable, a.angle / 'build/util/lastchange.py', '-o', a.angle / 'build/util/LASTCHANGE'], cwd=a.angle) + # Separate runtime paths prevent architectures from sharing an incompatible cache. + clang = tools / 'clang' + runtime = clang / 'lib/clang/19/lib' / ('windows' if windows else ('darwin' if mac else 'linux')) / ( + 'clang_rt.builtins-' + {'x64': 'x86_64', 'x86': 'i386', 'arm64': 'aarch64'}[cpu] + '.lib' + if windows else ('libclang_rt.osx.a' if mac else 'libclang_rt.builtins-' + ('x86_64' if cpu == 'x64' else 'aarch64') + '.a')) + runtime.parent.mkdir(parents=True, exist_ok=True) + run([zig, 'build-lib', zig_lib / 'compiler_rt.zig', '-target', config['target'], + '-O', 'ReleaseFast', '-fPIC', '-femit-bin=' + str(runtime)]) + if linux: + for runtime_cpu, runtime_target in ((cpu, config['target']), (host_cpu, json.loads((host_tools / 'toolchain.json').read_text())['target'])): + triple = 'x86_64-unknown-linux-gnu' if runtime_cpu == 'x64' else 'aarch64-unknown-linux-gnu' + alias = clang / 'lib/clang/19/lib' / triple / 'libclang_rt.builtins.a' + alias.parent.mkdir(parents=True, exist_ok=True) + run([zig, 'build-lib', zig_lib / 'compiler_rt.zig', '-target', runtime_target, + '-O', 'ReleaseFast', '-fPIC', '-femit-bin=' + str(alias)]) + availability = None + if mac: + if hashlib.sha256(a.availability_source.read_bytes()).hexdigest() != 'bc0b9f10e8d1a1ac5905d710f1c9fead23878e31233748d580393edc4e3bfc18': + raise SystemExit('LLVM availability source checksum mismatch') + availability = tools / 'os_version_check.o' + run([tools / 'cc', '-c', a.availability_source, '-o', availability]) + run([tools / 'ar', 'rcs', runtime, availability]) + env = os.environ.copy() + env['PATH'] = str(tools) + os.pathsep + env['PATH'] + env['CCACHE_BASEDIR'] = str(a.skia) + env['CCACHE_NOHASHDIR'] = 'true' + env['CCACHE_COMPILERCHECK'] = 'content' + env['STATICLINK_MACOS_SDK'] = str(a.sdk) + env['STATICLINK_DARWIN_TOOLS'] = str(tools) + skia_out = a.skia / 'out' / ('staticlink-zig-' + a.target + '-' + variant) + angle_out = a.angle / 'out' / ('staticlink-zig-' + a.target + '-' + angle_variant) + for out in (skia_out, angle_out): + out.mkdir(parents=True, exist_ok=True) + skia_args = (HERE / ('skia-' + os_name + '.gn.in')).read_text().replace('@CPU@', cpu) + if mac: + skia_args += f'min_macos_version = "{"11.0" if cpu == "arm64" else "10.13"}"\n' + skia_args += f'xcode_sysroot = {json.dumps(str(a.sdk))}\n' + for name in ('cc', 'cxx', 'ar'): + skia_args += name + ' = ' + json.dumps(str(tools / name)) + '\n' + if linux: + for key in ('cc', 'cxx', 'ar'): + skia_args = skia_args.replace('@' + key.upper() + '@', str(tools / key)) + capabilities = configure(a.skia, skia_out, parse_args(skia_args), a.skia / 'bin/gn', env) + for target in ('skia', 'SkiaSharp', 'HarfBuzzSharp'): + description = json.loads(subprocess.check_output([str(a.skia / 'bin/gn'), 'desc', str(skia_out.relative_to(a.skia)), + '//:' + target, '--format=json'], cwd=a.skia, env=env, text=True)) + kind = next(iter(description.values()))['type'] + if kind != 'static_library': + raise SystemExit(f'{target} is {kind}; expected static_library') + if windows: + # Upstream Skia fixes Windows to the MSVC rules; retain its graph and + # replace compiler/archive commands explicitly with the GNU adapters. + path = skia_out / 'toolchain.ninja' + body = path.read_text() + index = body.index('subninja ') + rules = '' + for name, flags in (('cc', 'cflags_c'), ('cxx', 'cflags_cc')): + rules += f'rule {name}\n command = {tools / name} -MD -MF ${{out}}.d ${{defines}} ${{include_dirs}} ${{cflags}} ${{{flags}}} -c ${{in}} -o ${{out}}\n depfile = ${{out}}.d\n deps = gcc\n' + rules += f'rule alink\n command = {tools / "ar"} rcs ${{out}} @${{out}}.rsp\n rspfile = ${{out}}.rsp\n rspfile_content = ${{in_newline}}\n' + rules += 'rule stamp\n command = touch ${out}\nrule copy\n command = cp ${in} ${out}\nrule asm\n command = false\n' + path.write_text(rules + '\n' + body[index:]) + sdk_link = angle_out / 'sdk/xcode_links/MacOSX15.5.sdk' + if mac: + sdk_link.parent.mkdir(parents=True, exist_ok=True) + if sdk_link.is_symlink(): + sdk_link.unlink() + sdk_link.symlink_to(a.sdk) + # Each architecture gets an independent toolchain file; concurrent builds + # do not rewrite another target's generated GN toolchain. + toolchain_dir = a.angle / ('staticlink_zig_' + a.target.replace('-', '_') + '_' + angle_variant) + toolchain_dir.mkdir(exist_ok=True) + text = 'import("//build/toolchain/gcc_toolchain.gni")\n' + for name, folder, target_os, target_cpu in (('target', tools, os_name, cpu), ('host', host_tools, 'linux', host_cpu)): + text += f'gcc_toolchain("{name}") {{\n' + for var in ('cc', 'cxx', 'ar'): + text += ' ' + var + ' = ' + json.dumps(str(folder / var)) + '\n' + text += f' ld = cxx\n nm = "nm"\n readelf = "readelf"\n toolchain_args = {{ current_os = "{target_os}" current_cpu = "{target_cpu}" is_clang = true use_remoteexec = false }}\n}}\n' + (toolchain_dir / 'BUILD.gn').write_text(text) + angle_args = (HERE / ('angle-' + os_name + '.gn.in')).read_text().replace('@CPU@', cpu) + angle_args += f'custom_toolchain = "//{toolchain_dir.name}:target"\nhost_toolchain = "//{toolchain_dir.name}:host"\n' + angle_args += 'clang_base_path = ' + json.dumps(str(clang)) + '\n' + if linux: + angle_args += 'pkg_config = ' + json.dumps(str(tools / 'pkg-config')) + '\n' + angle_args += 'host_pkg_config = ' + json.dumps(str(host_tools / 'pkg-config')) + '\n' + if windows: + # These mandatory GN metadata paths refer to the real MinGW headers. + # The SDK/version strings satisfy GN; MSVC/Windows SDK are not used. + for name, path in (('visual_studio_path', Path(zig).parent), ('wdk_path', Path(zig).parent), + ('windows_sdk_path', zig_lib / 'libc/include/any-windows-any')): + angle_args += name + ' = ' + json.dumps(str(path)) + '\n' + (angle_out / 'args.gn').write_text(angle_args) + run([a.angle_gn, 'gen', angle_out.relative_to(a.angle)], cwd=a.angle, env=env) + if a.configure_only: + print(f'Configured {lock["skiasharp_version"]} {a.target}: all three Skia targets are static libraries') + return + run(['ninja', '-C', skia_out, '-j', a.jobs, 'skia', 'SkiaSharp', 'HarfBuzzSharp'], env=env) + if a.component == 'all': + run(['ninja', '-C', angle_out, '-j', a.jobs, 'libANGLE_static', 'libGLESv2_static', 'libEGL_static'], env=env) + if availability: + run([tools / 'ar', 'rcs', archive(skia_out, 'skia'), availability]) + files = [(archive(skia_out, name), name) + for name in ('skia', 'SkiaSharp', 'HarfBuzzSharp')] + if a.component == 'all': + files += [(angle_out / ('obj/lib' + name + '_static.a'), name) for name in ('ANGLE', 'GLESv2', 'EGL')] + manifest = dict(target=a.target, sources=lock, toolchain=config, archives={}) + manifest['build_variant'] = variant + manifest['skia_capabilities'] = capabilities + if linux: + manifest['linux_sdk_packages'] = json.loads((HERE / ('linux-sysroots-musl.lock.json' if 'musl' in a.target else 'linux-sysroots.lock.json')).read_text())[a.target] + manifest['toolchain_inputs'] = json.loads((HERE / 'cross-toolchain.lock.json').read_text()) + manifest['gn_sha256'] = {str(path): hashlib.sha256(path.read_bytes()).hexdigest() + for path in (a.skia / 'bin/gn', a.angle_gn)} + manifest['recipe_sha256'] = {str(path.relative_to(HERE)): hashlib.sha256(path.read_bytes()).hexdigest() + for path in sorted(HERE.rglob('*')) if path.is_file() and (path.suffix in ('.py', '.in', '.patch', '.c') or path.name in ('cross-toolchain.lock.json', 'linux-sysroots.lock.json', 'linux-sysroots-musl.lock.json'))} + packages = Path('/opt/staticlink/packages.lock') + if packages.is_file(): + shutil.copy2(packages, a.output / 'toolchain-packages.lock') + manifest['packages_sha256'] = hashlib.sha256(packages.read_bytes()).hexdigest() + for source, name in files: + dest = a.output / ('lib' + name + '.a') + shutil.copy2(source, dest) + manifest['archives'][dest.name] = hashlib.sha256(dest.read_bytes()).hexdigest() + run([sys.executable, HERE / 'link-cross.py', '--toolchain', tools, '--skia', a.skia, + '--angle', a.angle, '--libraries', a.output, '--output', a.output / 'link-tests', '--component', a.component]) + if a.target == 'win-x86': + run([sys.executable, HERE / 'x86-thunks.py', '--source', a.skia.parent.parent, + '--toolchain', tools, '--libraries', a.output]) + manifest['link_tests'] = 'passed; target runtime tests still required' + (a.output / 'cross-build.json').write_text(json.dumps(manifest, indent=2) + '\n') + + +if __name__ == '__main__': + main() diff --git a/build/linux-toolchain/cross-toolchain.lock.json b/build/linux-toolchain/cross-toolchain.lock.json new file mode 100644 index 0000000..429e064 --- /dev/null +++ b/build/linux-toolchain/cross-toolchain.lock.json @@ -0,0 +1,10 @@ +{ + "zig_version": "0.14.1", + "macos_sdk_version": "15.5", + "macos_sdk_sha256": "c15cf0f3f17d714d1aa5a642da8e118db53d79429eb015771ba816aa7c6c1cbd", + "mingw_headers_version": "11.0.1-3build1", + "mingw_package_sha256": "4a42fd30fbc3838c58c929d8a4882682fc12408bb7a0e6cafa913c0f0a57c187", + "llvm_availability_revision": "llvmorg-19.1.7", + "llvm_availability_sha256": "bc0b9f10e8d1a1ac5905d710f1c9fead23878e31233748d580393edc4e3bfc18", + "angle_gn_revision": "e331ddb6e93389abfc75c611690bb82f5274029e" +} diff --git a/build/linux-toolchain/fetch-linux-sysroots.py b/build/linux-toolchain/fetch-linux-sysroots.py new file mode 100644 index 0000000..fd499a7 --- /dev/null +++ b/build/linux-toolchain/fetch-linux-sysroots.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Materialize target SDK libraries from immutable, checksum-locked packages.""" +import argparse,json,hashlib,urllib.request,tarfile,subprocess +from pathlib import Path +p=argparse.ArgumentParser();p.add_argument('--output',type=Path,required=True);p.add_argument('--locks',type=Path,nargs='+',required=True);a=p.parse_args() +for lock in a.locks: + for rid,packages in json.loads(lock.read_text()).items(): + dest=a.output/rid;dest.mkdir(parents=True,exist_ok=True) + for package in packages: + with urllib.request.urlopen(package['url'],timeout=90) as response:data=response.read() + if hashlib.sha256(data).hexdigest()!=package['sha256']:raise SystemExit('SDK checksum mismatch: '+package['name']) + path=a.output/(rid+'-'+Path(package['url']).name);path.write_bytes(data) + if path.suffix=='.deb':subprocess.run(['dpkg-deb','--extract',str(path),str(dest)],check=True) + else: + with tarfile.open(path,mode='r:gz',ignore_zeros=True) as tar: + members=[m for m in tar.getmembers() if m.name.startswith(('lib/','usr/lib/','usr/include/','etc/fonts/'))] + for m in members: + if m.issym() and m.linkname.startswith('/'): + import posixpath + m.linkname=posixpath.relpath(m.linkname.lstrip('/'),posixpath.dirname(m.name)) + # Debian 12's pinned Python 3.11 has no extraction-filter API. + # Validate paths and links explicitly before extracting locked APK files. + root=dest.resolve() + for m in members: + target=(root/m.name).resolve() + if not target.is_relative_to(root):raise SystemExit('Unsafe SDK archive path: '+m.name) + if not (m.isfile() or m.isdir() or m.issym() or m.islnk()):raise SystemExit('Unsupported SDK archive entry: '+m.name) + if m.issym() or m.islnk(): + link=(target.parent/m.linkname if m.issym() else root/m.linkname).resolve() + if not link.is_relative_to(root):raise SystemExit('Unsafe SDK archive link: '+m.name) + tar.extractall(dest,members=members) + path.unlink() + print(rid,package['name'],package['version'],flush=True) +# Copy only platform headers; never replace Zig's target libc headers with the host's. +import shutil +headers=a.output.parent/'staticlink/linux-headers';headers.mkdir(parents=True,exist_ok=True) +for name in ('fontconfig','X11','xcb','GL','KHR','EGL','freetype2','libdrm'): + src=a.output/'linux-x64/usr/include'/name + if src.is_dir():shutil.copytree(src,headers/name,dirs_exist_ok=True,symlinks=True) diff --git a/build/linux-toolchain/link-cross.py b/build/linux-toolchain/link-cross.py new file mode 100644 index 0000000..bd2c45c --- /dev/null +++ b/build/linux-toolchain/link-cross.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Link raster/HarfBuzz and EGL/GLES executables against cross-built archives.""" +import argparse +import json +from pathlib import Path +import subprocess +import re + +HERE = Path(__file__).resolve().parent + + +def main(): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument('--toolchain', type=Path, required=True) + p.add_argument('--skia', type=Path, required=True) + p.add_argument('--angle', type=Path, required=True) + p.add_argument('--libraries', type=Path, required=True) + p.add_argument('--output', type=Path, required=True) + p.add_argument('--component', choices=['all', 'skia'], default='all') + a = p.parse_args() + config = json.loads((a.toolchain / 'toolchain.json').read_text()) + windows = 'windows' in config['target'] + a.output.mkdir(parents=True, exist_ok=True) + target = config['target'] + flags = [] + mac = 'macos' in target + linux = 'linux' in target + if mac: + # ANGLE's pinned Chromium build has a macOS 13 deployment minimum. + target = target.split('-macos.')[0] + '-macos.13.0' + sdk = config['sdk'] + flags = ['-isysroot', sdk, '-L' + sdk + '/usr/lib', '-F' + sdk + '/System/Library/Frameworks'] + for name in (('raster', 'angle') if a.component == 'all' else ('raster',)): + obj = a.output / ('smoke-' + name + '.o') + includes = [a.skia, a.skia / 'third_party/externals/harfbuzz/src', a.angle / 'include'] + command = [str(a.toolchain / 'cc')] + if name == 'angle': + command.extend(('-DANGLE_STATIC=1', '-DKHRONOS_STATIC')) + else: + header = (a.skia / 'include/core/SkMilestone.h').read_text() + match = re.search(r'^\s*#define\s+SK_MILESTONE\s+(\d+)', header, re.MULTILINE) + if not match: + raise SystemExit('Cannot discover the Skia milestone; inspect the new source layout') + command.append('-DSTATICLINK_EXPECTED_MILESTONE=' + match[1]) + command += ['-I' + str(path) for path in includes] + command += ['-c', str(HERE / ('smoke-' + name + '.c')), '-o', str(obj)] + subprocess.run(command, check=True) + libs = ('SkiaSharp', 'skia', 'HarfBuzzSharp') if name == 'raster' else ('EGL', 'GLESv2', 'ANGLE') + command = [config['zig'], 'c++', '-target', target, '-O2', *flags, str(obj)] + command += [str(a.libraries / ('lib' + library + '.a')) for library in libs] + if windows: + command += ['-l' + lib for lib in ('dwrite', 'gdi32', 'ole32', 'oleaut32', 'user32', 'uuid', + 'windowscodecs', 'dxguid', 'dxgi', 'd3d11', 'd3d9', 'dwmapi', 'shlwapi', 'setupapi', 'version', 'advapi32', 'api-ms-win-core-synch-l1-2-0')] + elif mac: + for framework in ('CoreFoundation', 'CoreGraphics', 'CoreText', 'Foundation', 'AppKit', + 'Metal', 'QuartzCore', 'OpenGL', 'IOKit', 'IOSurface'): + command += ['-framework', framework] + command += ['-lobjc'] + availability = a.toolchain / 'os_version_check.o' + if availability.is_file(): + command.append(str(availability)) + elif linux: + sysroot = Path(config.get('linux_sysroot') or '/') + for path in (sysroot / 'usr/lib', sysroot / 'lib', sysroot / 'usr/lib' / ('x86_64-linux-gnu' if target.startswith('x86_64') else 'aarch64-linux-gnu')): + if path.is_dir(): + command += ['-L' + str(path)] + command += ['-lfontconfig', '-ldl', '-lm', '-lpthread'] if name == 'raster' else ['-lX11', '-lXext', '-lxcb', '-lwayland-client', '-lwayland-egl', '-lffi', '-ldl', '-lm', '-lpthread'] + command += ['-v'] + command += ['-o', str(a.output / ('smoke-' + name + ('.exe' if windows else '')))] + result = subprocess.run(command, capture_output=True, text=True) + print(result.stdout, end='') + print(result.stderr, end='') + result.check_returncode() + # Keep the exact runtime archives chosen by this Zig target. AOT consumes + # these without substituting the consumer machine's C++ toolchain. + import shlex, shutil + runtimes = {} + project_archives = {path.resolve() for path in a.libraries.glob('lib*.a')} + for line in (result.stdout + '\n' + result.stderr).splitlines(): + try: + tokens = shlex.split(line) + except ValueError: + continue + for token in tokens: + path = Path(token) + if path.suffix in ('.a', '.lib') and path.is_file() and path.resolve() not in project_archives and not path.resolve().is_relative_to(a.libraries.resolve()): + dest = a.libraries / 'zig-runtime' / path.name + dest.parent.mkdir(exist_ok=True) + shutil.copy2(path, dest) + runtimes[path.name] = str(path) + descriptor = a.libraries / 'zig-runtime' / 'inputs.json' + if descriptor.exists(): + runtimes = dict(json.loads(descriptor.read_text()), **runtimes) + descriptor.parent.mkdir(exist_ok=True) + descriptor.write_text(json.dumps(runtimes, indent=2) + '\n') + print('Executable link tests passed. Run them on the target OS to validate behavior.') + + +if __name__ == '__main__': + main() diff --git a/build/linux-toolchain/linux-sysroots-musl.lock.json b/build/linux-toolchain/linux-sysroots-musl.lock.json new file mode 100644 index 0000000..e6dba55 --- /dev/null +++ b/build/linux-toolchain/linux-sysroots-musl.lock.json @@ -0,0 +1,474 @@ +{ + "linux-musl-x64": [ + { + "name": "brotli-dev", + "version": "1.2.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/brotli-dev-1.2.0-r0.apk", + "sha256": "f61bd37140c76a22e97ad9d7461becda5d41526bbee1a8bc2523edad5ae6f35e" + }, + { + "name": "brotli-libs", + "version": "1.2.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/brotli-libs-1.2.0-r0.apk", + "sha256": "09fbf1ddf0c71c6c4ab7bc12aef3452d8ace5adeab10c4c30214fc70f5e0e087" + }, + { + "name": "bzip2-dev", + "version": "1.0.8-r6", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/bzip2-dev-1.0.8-r6.apk", + "sha256": "59a73f8712a9c0c16553a759149ee5ecf87a6160ef021aeb922ddb0c0b3a695b" + }, + { + "name": "expat", + "version": "2.8.5-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/expat-2.8.5-r0.apk", + "sha256": "19f3d19d7deb82e357a70cb30b38dd81fa1cbecdb7e0a834071e993d5df18246" + }, + { + "name": "expat-dev", + "version": "2.8.5-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/expat-dev-2.8.5-r0.apk", + "sha256": "5a31977673fbdacf195d372114501c06b18eb1f24553c99b92e47e0e24478f9e" + }, + { + "name": "fontconfig", + "version": "2.17.1-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/fontconfig-2.17.1-r0.apk", + "sha256": "f44cea53076f18428dad0c27c75ca717ebfc3d2f0f4c8d4428ce70b62e25adf1" + }, + { + "name": "fontconfig-dev", + "version": "2.17.1-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/fontconfig-dev-2.17.1-r0.apk", + "sha256": "e819dd0eb3f2a2a4f802a16911959e1232b323c4e2db9ce020c3d52e9bfd5053" + }, + { + "name": "freetype", + "version": "2.14.3-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/freetype-2.14.3-r0.apk", + "sha256": "670c9922ab4eb9df65c0e4ad7839bbe3f486016fc1fb637c46d657b74d99a24f" + }, + { + "name": "freetype-dev", + "version": "2.14.3-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/freetype-dev-2.14.3-r0.apk", + "sha256": "85a90244c039722c409996e5e5e65d4751664984ed1843a00c5d751577627972" + }, + { + "name": "libbsd", + "version": "0.12.2-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libbsd-0.12.2-r0.apk", + "sha256": "1252092dea853063252bbdaf9fa604819055b004ba3114b88b842fbfe44659e9" + }, + { + "name": "libbz2", + "version": "1.0.8-r6", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libbz2-1.0.8-r6.apk", + "sha256": "9995840a76ac97ec006259391b7a81b73b73c5cf2f462bf12bb2250afbf665b9" + }, + { + "name": "libexpat", + "version": "2.8.5-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libexpat-2.8.5-r0.apk", + "sha256": "5216046085b92da88a6b107dbbb558f69d49c347a6335291fcf199ebd1670a8f" + }, + { + "name": "libffi", + "version": "3.5.2-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libffi-3.5.2-r0.apk", + "sha256": "0b3473a1468bed5fbb913a1ffa121e253080b21b013fe47e0300bd7e85224e76" + }, + { + "name": "libffi-dev", + "version": "3.5.2-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libffi-dev-3.5.2-r0.apk", + "sha256": "737a9109fc4a763465cc117ca20edd18d4ea3fe9ed6f717251aa744b8ebbaba6" + }, + { + "name": "libmd", + "version": "1.1.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libmd-1.1.0-r0.apk", + "sha256": "704446100fb39f2699d848d97f09463dd6fdd35f3a1acb038483712e1e37f766" + }, + { + "name": "libpng", + "version": "1.6.59-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libpng-1.6.59-r0.apk", + "sha256": "148a10e3d6419f787b16edbc2d0bb19d12a2a078225baaa416c895c14f47fc1e" + }, + { + "name": "libpng-dev", + "version": "1.6.59-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libpng-dev-1.6.59-r0.apk", + "sha256": "01b89d3cea124fed78359b71f77e4d545de6e3163077167947e6adf84bdcb595" + }, + { + "name": "libx11", + "version": "1.8.12-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libx11-1.8.12-r1.apk", + "sha256": "cbc478a642226812ab13688bfe9ec69c86cf0e8e037dba7cfd055e8d7741f30c" + }, + { + "name": "libx11-dev", + "version": "1.8.12-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libx11-dev-1.8.12-r1.apk", + "sha256": "91eac12015dffbeb06b2755eae52722b1740018d775db33fbe68abc353b951df" + }, + { + "name": "libxau", + "version": "1.0.12-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libxau-1.0.12-r0.apk", + "sha256": "61ee983ec9d47821cae9b3e1c8245a5b0b228361839be9b596a42c817b691a9f" + }, + { + "name": "libxau-dev", + "version": "1.0.12-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libxau-dev-1.0.12-r0.apk", + "sha256": "2af8a78f1deaf398e96964f17d5640e3448fc5e935392e3550283e8d7c54a6cc" + }, + { + "name": "libxcb", + "version": "1.17.0-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libxcb-1.17.0-r1.apk", + "sha256": "f73934506edfdcb6895f04e313481ca48592ac4cec59cdb8fc073a7681bfbd31" + }, + { + "name": "libxcb-dev", + "version": "1.17.0-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libxcb-dev-1.17.0-r1.apk", + "sha256": "8735d97a346eae3999a599e49eb533481478d0ef9466e6e5a5453dd1e3ae8f3d" + }, + { + "name": "libxdmcp", + "version": "1.1.5-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libxdmcp-1.1.5-r1.apk", + "sha256": "f134d28babb070834066aa716c303768ed0ede32ac086f3dfbd8cbbac62380ee" + }, + { + "name": "libxdmcp-dev", + "version": "1.1.5-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libxdmcp-dev-1.1.5-r1.apk", + "sha256": "d8a304762ea4295287ef1adf9d93d64f04bdfc4e845b453698b3276b12e8fc10" + }, + { + "name": "libxext", + "version": "1.3.6-r2", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libxext-1.3.6-r2.apk", + "sha256": "404beb855daf09fdb8d15f177a3608da62825a44497a805675a186171c04eb6c" + }, + { + "name": "libxext-dev", + "version": "1.3.6-r2", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libxext-dev-1.3.6-r2.apk", + "sha256": "ebb6262372a918a6bee4ff2137dacdb1fa13be78d0e3544a082d6530ce8e05b6" + }, + { + "name": "libxml2", + "version": "2.13.9-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/libxml2-2.13.9-r1.apk", + "sha256": "1af363248efb54914993bbd516c89878c2b5b0c53be0c78bb43d5a3e9a8ceda6" + }, + { + "name": "mesa-dev", + "version": "25.2.7-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/mesa-dev-25.2.7-r1.apk", + "sha256": "da17f767ae27fc8ff9a958ff82d217ee3a890d888b43e6a62171a402b48156a1" + }, + { + "name": "musl", + "version": "1.2.5-r23", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/musl-1.2.5-r23.apk", + "sha256": "4f3c4a7bf9f51d2c91007e333b17459362ffd881b4a343e8da07b6c50c4f4a0d" + }, + { + "name": "wayland-dev", + "version": "1.24.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/wayland-dev-1.24.0-r0.apk", + "sha256": "5afcb3e3c414d2c0e602bc9ef1b4868fd7df9e53aec55f3f722ce4897019f014" + }, + { + "name": "wayland-libs-client", + "version": "1.24.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/wayland-libs-client-1.24.0-r0.apk", + "sha256": "020313c601f85a9e91d1ae0d01e9f1e9905d1127b78dce9c278a75cfd52416d2" + }, + { + "name": "wayland-libs-cursor", + "version": "1.24.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/wayland-libs-cursor-1.24.0-r0.apk", + "sha256": "e87fb03656132b550182b019b7f3218f336ff9637dc574ef0f22a0ad148f8577" + }, + { + "name": "wayland-libs-egl", + "version": "1.24.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/wayland-libs-egl-1.24.0-r0.apk", + "sha256": "2d9165a79e7e779b52dd203959f64eb8f7caad460f3e6d76097efc432e115936" + }, + { + "name": "wayland-libs-server", + "version": "1.24.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/wayland-libs-server-1.24.0-r0.apk", + "sha256": "c30fd5c6f081732670e7dd69316df73cb3939b8c53237314ca1ec3f991b2a1f7" + }, + { + "name": "xorgproto", + "version": "2024.1-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/xorgproto-2024.1-r0.apk", + "sha256": "96e2b99a0227d301170ae840c37693d65c767dd0a15a1243ced45325850ad424" + }, + { + "name": "xz-libs", + "version": "5.8.4-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/xz-libs-5.8.4-r0.apk", + "sha256": "3c8cc415e88c2a097543d9ee828081ec1905e7d0f57a319c8896269b0e00a672" + }, + { + "name": "zlib", + "version": "1.3.2-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/zlib-1.3.2-r0.apk", + "sha256": "f56dea63692059bd65854bb179b7551971a441000b0d98c5b031291ca0450b56" + }, + { + "name": "zlib-dev", + "version": "1.3.2-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/zlib-dev-1.3.2-r0.apk", + "sha256": "c811e48ea401e05479a74d93f8ac820bbb6af44b2adf0335769316e5cc2b72c4" + } + ], + "linux-musl-arm64": [ + { + "name": "brotli-dev", + "version": "1.2.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/brotli-dev-1.2.0-r0.apk", + "sha256": "012c9917e3a3f37efbb255fdff240d4fe0ee83a2e4e5d7511eddcce57a2daa30" + }, + { + "name": "brotli-libs", + "version": "1.2.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/brotli-libs-1.2.0-r0.apk", + "sha256": "c8a9113b0007d40291e55cfc196744b64004253707e37b3c07e188166fb2d0a0" + }, + { + "name": "bzip2-dev", + "version": "1.0.8-r6", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/bzip2-dev-1.0.8-r6.apk", + "sha256": "69d79bb497c25e81fce8695653f448666c08bc8b28fa51506b74b38946a0a03a" + }, + { + "name": "expat", + "version": "2.8.5-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/expat-2.8.5-r0.apk", + "sha256": "b5b520d2bd7a89e8fbad6a59667f70d4656326dd8a4df84c3e5ef4b5c0056243" + }, + { + "name": "expat-dev", + "version": "2.8.5-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/expat-dev-2.8.5-r0.apk", + "sha256": "2aad86f9f50aeb5b3c7a0e4022693dd810f51ec9c7a28037f3d2e71dbd9ecb1e" + }, + { + "name": "fontconfig", + "version": "2.17.1-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/fontconfig-2.17.1-r0.apk", + "sha256": "38e815b8f898482e3c8111ac158208ba0a6a9aa13dbef49ccc36b36df77c793e" + }, + { + "name": "fontconfig-dev", + "version": "2.17.1-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/fontconfig-dev-2.17.1-r0.apk", + "sha256": "5661ba91c9da743151b333a69b05f467f5c4145395f3d54567e7e9999aa8d842" + }, + { + "name": "freetype", + "version": "2.14.3-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/freetype-2.14.3-r0.apk", + "sha256": "b1012afa50bdebe872fab255b1c6db8c2c05b0dc4fc274c48d714e1887401b4e" + }, + { + "name": "freetype-dev", + "version": "2.14.3-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/freetype-dev-2.14.3-r0.apk", + "sha256": "cd8d91f81c9477f0f76fd8db8158237fa99b1e0f73f6ec6e929bfc46b076b51d" + }, + { + "name": "libbsd", + "version": "0.12.2-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libbsd-0.12.2-r0.apk", + "sha256": "358715f27bfe492dcb11f3589c8f106c37071780f55adafc52b2c5cfd3771e4d" + }, + { + "name": "libbz2", + "version": "1.0.8-r6", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libbz2-1.0.8-r6.apk", + "sha256": "ccd478f4d5346a25911d4ee977690e2695e0ca3acf3e6b1a8c1300b2f4311e2f" + }, + { + "name": "libexpat", + "version": "2.8.5-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libexpat-2.8.5-r0.apk", + "sha256": "18e5a4f1715dd9fee420d250d14be9e59936352929085939f33da01c3fbde28d" + }, + { + "name": "libffi", + "version": "3.5.2-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libffi-3.5.2-r0.apk", + "sha256": "ebf430b246a6b1167350431e2af59a09f4edbc42dc3e03f3b9d74800ba5f9e7f" + }, + { + "name": "libffi-dev", + "version": "3.5.2-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libffi-dev-3.5.2-r0.apk", + "sha256": "96830dcf534e196258838122fcaae6582ea02c957413a31e53a86ae403db0478" + }, + { + "name": "libmd", + "version": "1.1.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libmd-1.1.0-r0.apk", + "sha256": "8818536cdc38dd3c252d6c6915f65ddd49390aa209cc7f70b87c446adedf820c" + }, + { + "name": "libpng", + "version": "1.6.59-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libpng-1.6.59-r0.apk", + "sha256": "087102199da459dd8985c118a67d6d6c463de6798f243424feb239d1087b1064" + }, + { + "name": "libpng-dev", + "version": "1.6.59-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libpng-dev-1.6.59-r0.apk", + "sha256": "9f7a8ceb60638786dd675ccc526668516a8ca4df4b54daa05f526df12f76c7bd" + }, + { + "name": "libx11", + "version": "1.8.12-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libx11-1.8.12-r1.apk", + "sha256": "7f53f6f26293abac468b9563f2a90bc9a34306ec8a677af23d01d7455e3db943" + }, + { + "name": "libx11-dev", + "version": "1.8.12-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libx11-dev-1.8.12-r1.apk", + "sha256": "114785b13b6df10d7997e9e8e12bf95fd5f0e6644fd420fa84dcfc0956cdecb9" + }, + { + "name": "libxau", + "version": "1.0.12-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libxau-1.0.12-r0.apk", + "sha256": "ddfad479041b53e5f57a2fdfac12454993ae3937f6f893d67cd98cbd63abfcf0" + }, + { + "name": "libxau-dev", + "version": "1.0.12-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libxau-dev-1.0.12-r0.apk", + "sha256": "bed89cb8971efccf37f0a77d6cddcd31f122a322d04fc75117cddb48804c2290" + }, + { + "name": "libxcb", + "version": "1.17.0-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libxcb-1.17.0-r1.apk", + "sha256": "8609cadcd7af03c1aade3663fdf3bbe27e4180fe0edbe98d3f12f0ae6e95aedc" + }, + { + "name": "libxcb-dev", + "version": "1.17.0-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libxcb-dev-1.17.0-r1.apk", + "sha256": "f6d1488df536002017c10a435caa32f9527ec9ad5aa86cbab639cde4258a2b67" + }, + { + "name": "libxdmcp", + "version": "1.1.5-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libxdmcp-1.1.5-r1.apk", + "sha256": "f95a668a5b8888aadbee24e8ffb30f60e4d40cdf8ef61b35383e7ecf49182c66" + }, + { + "name": "libxdmcp-dev", + "version": "1.1.5-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libxdmcp-dev-1.1.5-r1.apk", + "sha256": "fa1dd28326caf9e3299bf36a94fa5d62c6f448588044fcf95ddcf2bcaabd9aff" + }, + { + "name": "libxext", + "version": "1.3.6-r2", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libxext-1.3.6-r2.apk", + "sha256": "bb7e84c1d38e5dcdfdb56e543394469f9c884cb13e09f5e07986863a9bea62e7" + }, + { + "name": "libxext-dev", + "version": "1.3.6-r2", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libxext-dev-1.3.6-r2.apk", + "sha256": "9b70dc8e575dbc28feed289a4336bc181cc7327886364aefe4bb3bb81a98c49b" + }, + { + "name": "libxml2", + "version": "2.13.9-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/libxml2-2.13.9-r1.apk", + "sha256": "91d3e730edbd5214c501696725e7af9a93e12a9dd6a296e719dca2f3de0697d5" + }, + { + "name": "mesa-dev", + "version": "25.2.7-r1", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/mesa-dev-25.2.7-r1.apk", + "sha256": "a9759357562a1365b35b01a2be05cc33ec3ab0f3a1c1bd079a0b68e4c29ef129" + }, + { + "name": "musl", + "version": "1.2.5-r23", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/musl-1.2.5-r23.apk", + "sha256": "6a3edd924ead1fad88a69e28c5775809af3026b322f58428001cd02fedc5299e" + }, + { + "name": "wayland-dev", + "version": "1.24.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/wayland-dev-1.24.0-r0.apk", + "sha256": "681397cc91e05bb6cc63af4b37a59aa510aaab37539c9e6629d7fbfbcb1f0e57" + }, + { + "name": "wayland-libs-client", + "version": "1.24.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/wayland-libs-client-1.24.0-r0.apk", + "sha256": "47634dcc2befcae378f9f60681d2af1864db85e57f65e11a3557a43f77911073" + }, + { + "name": "wayland-libs-cursor", + "version": "1.24.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/wayland-libs-cursor-1.24.0-r0.apk", + "sha256": "a6f8e01ec409b183693169af006d1edc8b673af801bd540820fdb7808cb5e27b" + }, + { + "name": "wayland-libs-egl", + "version": "1.24.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/wayland-libs-egl-1.24.0-r0.apk", + "sha256": "4f2ae7b94f0b6c95dae885c0c24ac59eea5fd2b7a4680cec43dd8366c86564d2" + }, + { + "name": "wayland-libs-server", + "version": "1.24.0-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/wayland-libs-server-1.24.0-r0.apk", + "sha256": "c6bc1243dd337ba638834cd1e7a5ccfd10b6e2315e63af02f441bff581c35c66" + }, + { + "name": "xorgproto", + "version": "2024.1-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/xorgproto-2024.1-r0.apk", + "sha256": "8d53e4b89f3067a4d24f7645127b100a4d46a567eccb82428fe5256c17316b0d" + }, + { + "name": "xz-libs", + "version": "5.8.4-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/xz-libs-5.8.4-r0.apk", + "sha256": "55cec0880e1d06049147cd295a7c8a2909a668fc58a00c187741f3a16c25c55d" + }, + { + "name": "zlib", + "version": "1.3.2-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/zlib-1.3.2-r0.apk", + "sha256": "ecda4cc94fd18f90182f1d3a615889df5e0db9cf78926d11627dd23e06d2e6e8" + }, + { + "name": "zlib-dev", + "version": "1.3.2-r0", + "url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/main/aarch64/zlib-dev-1.3.2-r0.apk", + "sha256": "18a7f594ad7439629cb5889e4f0ad71075dd9f0cd4729a0461ba1501e21bc583" + } + ] +} diff --git a/build/linux-toolchain/linux-sysroots.lock.json b/build/linux-toolchain/linux-sysroots.lock.json new file mode 100644 index 0000000..f122324 --- /dev/null +++ b/build/linux-toolchain/linux-sysroots.lock.json @@ -0,0 +1,594 @@ +{ + "linux-x64": [ + { + "name": "libbrotli-dev", + "version": "1.0.9-2+b6", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/b/brotli/libbrotli-dev_1.0.9-2+b6_amd64.deb", + "sha256": "932de11d4d9caee3300319b2fdf6ca505ca739706be48737df5e40ee90ccb6ca" + }, + { + "name": "libbrotli1", + "version": "1.0.9-2+b6", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/b/brotli/libbrotli1_1.0.9-2+b6_amd64.deb", + "sha256": "563b4caec1aa5e876bd3355b36e7a38e1484baf5a293b48d1e8bd22db786e4d7" + }, + { + "name": "libbsd0", + "version": "0.11.7-2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libb/libbsd/libbsd0_0.11.7-2_amd64.deb", + "sha256": "bb31cc8b40f962a85b2cec970f7f79cc704a1ae4bad24257a822055404b2c60b" + }, + { + "name": "libbz2-1.0", + "version": "1.0.8-5+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/b/bzip2/libbz2-1.0_1.0.8-5+b1_amd64.deb", + "sha256": "54149da3f44b22d523b26b692033b84503d822cc5122fed606ea69cc83ca5aeb" + }, + { + "name": "libbz2-dev", + "version": "1.0.8-5+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/b/bzip2/libbz2-dev_1.0.8-5+b1_amd64.deb", + "sha256": "df7204e188f7514ceae9c6f57c6c8b25ce8a642746fe284c6e89122ef26b7e57" + }, + { + "name": "libc6", + "version": "2.36-9+deb12u14", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/g/glibc/libc6_2.36-9+deb12u14_amd64.deb", + "sha256": "ba4f88f73dbc3ae9055f3c20f4523bfdbaf1ad13ff95e258924f77d20b4fbedf" + }, + { + "name": "libegl-dev", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libegl-dev_1.6.0-1_amd64.deb", + "sha256": "0ec9bffb024527dfb61eaeae44966e2b5b3114b3dc315b4117b0df95b1c96ef8" + }, + { + "name": "libegl1", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libegl1_1.6.0-1_amd64.deb", + "sha256": "fe4d8b39f6e6fe1a32ab1efd85893553eaa9cf3866aa668ccf355f585b37d523" + }, + { + "name": "libexpat1", + "version": "2.5.0-1+deb12u2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/e/expat/libexpat1_2.5.0-1+deb12u2_amd64.deb", + "sha256": "2255e62fc22a86d2c544b8a3f516da9aee19383ad5742722ab4ce7f66a30dbc8" + }, + { + "name": "libexpat1-dev", + "version": "2.5.0-1+deb12u2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/e/expat/libexpat1-dev_2.5.0-1+deb12u2_amd64.deb", + "sha256": "757279c1ce56ca88ca519e4c01cffd94e8480ce936f53fe9b0d61dc1bab20394" + }, + { + "name": "libffi-dev", + "version": "3.4.4-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libf/libffi/libffi-dev_3.4.4-1_amd64.deb", + "sha256": "89fb890aee5148f4d308a46cd8980a54fd44135f068f05b38a6ad06800bf6df3" + }, + { + "name": "libffi8", + "version": "3.4.4-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libf/libffi/libffi8_3.4.4-1_amd64.deb", + "sha256": "6d9f6c25c30efccce6d4bceaa48ea86c329a3432abb360a141f76ac223a4c34a" + }, + { + "name": "libfontconfig-dev", + "version": "2.14.1-4", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/f/fontconfig/libfontconfig-dev_2.14.1-4_amd64.deb", + "sha256": "74f4bbde5795e4fb4c453308bd0eb8ef45d7ade3cf11b3c18394f9e2c15d28df" + }, + { + "name": "libfontconfig1", + "version": "2.14.1-4", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/f/fontconfig/libfontconfig1_2.14.1-4_amd64.deb", + "sha256": "16ee38d374e064f534116dc442b086ef26f9831f1c0af7e5fb4fe4512e700649" + }, + { + "name": "libfreetype-dev", + "version": "2.12.1+dfsg-5+deb12u4", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/f/freetype/libfreetype-dev_2.12.1+dfsg-5+deb12u4_amd64.deb", + "sha256": "5fa327ae78e645cff5a2aadad92ca3640abc5cee7ad5368d7551bea8930202af" + }, + { + "name": "libfreetype6", + "version": "2.12.1+dfsg-5+deb12u4", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/f/freetype/libfreetype6_2.12.1+dfsg-5+deb12u4_amd64.deb", + "sha256": "8043e479f73f29992d652e3f9dfe8b17f9780c7ea6330afe379ec5f9f188ac44" + }, + { + "name": "libgl-dev", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libgl-dev_1.6.0-1_amd64.deb", + "sha256": "ebc12df48ae53924e114d9358ef3da4306d7ef8f7179300af52f1faef8b5db3e" + }, + { + "name": "libgl1", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libgl1_1.6.0-1_amd64.deb", + "sha256": "6f89b1702c48e9a2437bb3c1ffac8e1ab2d828fc28b3d14b2eecd4cc19b2c790" + }, + { + "name": "libglvnd0", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libglvnd0_1.6.0-1_amd64.deb", + "sha256": "b6da5b153dd62d8b5e5fbe25242db1fc05c068707c365db49abda8c2427c75f8" + }, + { + "name": "libglx0", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libglx0_1.6.0-1_amd64.deb", + "sha256": "95f568df73dedf43ae66834a75502112e0d4f3ad7124f3dbfa790b739383b896" + }, + { + "name": "libmd0", + "version": "1.0.4-2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libm/libmd/libmd0_1.0.4-2_amd64.deb", + "sha256": "03539fd30c509e27101d13a56e52eda9062bdf1aefe337c07ab56def25a13eab" + }, + { + "name": "libpng-dev", + "version": "1.6.39-2+deb12u5", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libp/libpng1.6/libpng-dev_1.6.39-2+deb12u5_amd64.deb", + "sha256": "25649c413d02092ae7b1093a3c3ee54cbd2d4fdb0a0494787e434ccf0905903f" + }, + { + "name": "libpng16-16", + "version": "1.6.39-2+deb12u5", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libp/libpng1.6/libpng16-16_1.6.39-2+deb12u5_amd64.deb", + "sha256": "a56d64bfaa9da12aafb83347909e62e6fd5fd251e6b34c194065911a30359978" + }, + { + "name": "libpthread-stubs0-dev", + "version": "0.4-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_amd64.deb", + "sha256": "54632f160e1e8a43656a87195a547391038c4ca0f53291b849cd4457ba5dfde9" + }, + { + "name": "libuuid1", + "version": "2.38.1-5+deb12u3", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/u/util-linux/libuuid1_2.38.1-5+deb12u3_amd64.deb", + "sha256": "5fff398321da62d28a842bcbabd193d72ae21724962c029dc32b1dc6b2deaeaf" + }, + { + "name": "libwayland-client0", + "version": "1.21.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/w/wayland/libwayland-client0_1.21.0-1_amd64.deb", + "sha256": "1f002d028b8b79eec9847c636d8886d10dfe8c884cc2bebe18086b1391c5a28d" + }, + { + "name": "libwayland-cursor0", + "version": "1.21.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/w/wayland/libwayland-cursor0_1.21.0-1_amd64.deb", + "sha256": "0b684467ec795024fcaae99ea4384959cc4052b2da5adec38c9b76dfed9bcf41" + }, + { + "name": "libwayland-dev", + "version": "1.21.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/w/wayland/libwayland-dev_1.21.0-1_amd64.deb", + "sha256": "063ac40e71069a1a92c2be32799f4a2d234f3d361ba98cea2e3137a7ab6804d1" + }, + { + "name": "libwayland-egl1", + "version": "1.21.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/w/wayland/libwayland-egl1_1.21.0-1_amd64.deb", + "sha256": "cb4225f0e15e00a3570d4cbc288c87ff477d73e741c3f1c5a6d47cf350d45378" + }, + { + "name": "libwayland-server0", + "version": "1.21.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/w/wayland/libwayland-server0_1.21.0-1_amd64.deb", + "sha256": "df0396221d7b794496a687ec61fae82b6465648bc0ab6501ba0a5ed7f56eb8d6" + }, + { + "name": "libx11-6", + "version": "2:1.8.4-2+deb12u2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libx11/libx11-6_1.8.4-2+deb12u2_amd64.deb", + "sha256": "d88c973e79fd9b65838d77624142952757e47a6eb1a58602acf0911cf35989f4" + }, + { + "name": "libx11-dev", + "version": "2:1.8.4-2+deb12u2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libx11/libx11-dev_1.8.4-2+deb12u2_amd64.deb", + "sha256": "8493220d4309af1907a1f2f6eeb204c8103dafcc368394fbc4a0858c28612ff9" + }, + { + "name": "libx11-xcb1", + "version": "2:1.8.4-2+deb12u2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libx11/libx11-xcb1_1.8.4-2+deb12u2_amd64.deb", + "sha256": "f5da45e1d881a793250a96613f28c471a248877f1a0f18a5c90e2a620a76c898" + }, + { + "name": "libxau-dev", + "version": "1:1.0.9-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxau/libxau-dev_1.0.9-1_amd64.deb", + "sha256": "d1a7f5d484e0879b3b2e8d512894744505e53d078712ce65903fef2ecfd824bb" + }, + { + "name": "libxau6", + "version": "1:1.0.9-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxau/libxau6_1.0.9-1_amd64.deb", + "sha256": "679db1c4579ec7c61079adeaae8528adeb2e4bf5465baa6c56233b995d714750" + }, + { + "name": "libxcb1", + "version": "1.15-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxcb/libxcb1_1.15-1_amd64.deb", + "sha256": "fdc61332a3892168f3cc9cfa1fe9cf11a91dc3e0acacbc47cbc50ebaa234cc71" + }, + { + "name": "libxcb1-dev", + "version": "1.15-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxcb/libxcb1-dev_1.15-1_amd64.deb", + "sha256": "c078c024114fdada06d3158af1771d7ed8763ab434cfbcbe6a334aa8a9cae358" + }, + { + "name": "libxdmcp-dev", + "version": "1:1.1.2-3", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxdmcp/libxdmcp-dev_1.1.2-3_amd64.deb", + "sha256": "c6733e5f6463afd261998e408be6eb37f24ce0a64b63bed50a87ddb18ebc1699" + }, + { + "name": "libxdmcp6", + "version": "1:1.1.2-3", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxdmcp/libxdmcp6_1.1.2-3_amd64.deb", + "sha256": "ecb8536f5fb34543b55bb9dc5f5b14c9dbb4150a7bddb3f2287b7cab6e9d25ef" + }, + { + "name": "libxext-dev", + "version": "2:1.3.4-1+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxext/libxext-dev_1.3.4-1+b1_amd64.deb", + "sha256": "591456aba90eeed7a1c1b044d469fd7704bf7d83af9dc574bbe2efc4a2fd1dba" + }, + { + "name": "libxext6", + "version": "2:1.3.4-1+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxext/libxext6_1.3.4-1+b1_amd64.deb", + "sha256": "504b7be9d7df4f6f4519e8dd4d6f9d03a9fb911a78530fa23a692fba3058cba6" + }, + { + "name": "libxfixes3", + "version": "1:6.0.0-2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxfixes/libxfixes3_6.0.0-2_amd64.deb", + "sha256": "1cd616396ff2ecae77e6e8b5b7695d414f0146de2d147837a2a02165f99e1a2c" + }, + { + "name": "libxi6", + "version": "2:1.8-1+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxi/libxi6_1.8-1+b1_amd64.deb", + "sha256": "6c2fb5f241b311788990122c6e693915867cd8ed9d6da2e3524aec7ed3cdb4c0" + }, + { + "name": "libxrandr2", + "version": "2:1.5.2-2+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxrandr/libxrandr2_1.5.2-2+b1_amd64.deb", + "sha256": "e2e10b672085ddf1d294cd689ed21bde5899325fd13626c2614b7d7b2d49a808" + }, + { + "name": "libxrender1", + "version": "1:0.9.10-1.1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxrender/libxrender1_0.9.10-1.1_amd64.deb", + "sha256": "b2a6d160578f359220101947c8d67059b44704a251e688de1d0b744fefedfd1a" + }, + { + "name": "x11proto-dev", + "version": "2022.1-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/x/xorgproto/x11proto-dev_2022.1-1_all.deb", + "sha256": "3cab66a6591774188a568f9b54c4e7956f18da76e0e0fc4a779db5f6bcc3f148" + }, + { + "name": "xtrans-dev", + "version": "1.4.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/x/xtrans/xtrans-dev_1.4.0-1_all.deb", + "sha256": "9ce1af9464faee0c679348dd11cdf63934c12e734a64e0903692b0cb5af38e06" + }, + { + "name": "zlib1g", + "version": "1:1.2.13.dfsg-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/z/zlib/zlib1g_1.2.13.dfsg-1_amd64.deb", + "sha256": "d7dd1d1411fedf27f5e27650a6eff20ef294077b568f4c8c5e51466dc7c08ce4" + }, + { + "name": "zlib1g-dev", + "version": "1:1.2.13.dfsg-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/z/zlib/zlib1g-dev_1.2.13.dfsg-1_amd64.deb", + "sha256": "f9ce531f60cbd5df37996af9370e0171be96902a17ec2bdbd8d62038c354094f" + } + ], + "linux-arm64": [ + { + "name": "libbrotli-dev", + "version": "1.0.9-2+b6", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/b/brotli/libbrotli-dev_1.0.9-2+b6_arm64.deb", + "sha256": "68f334eb61694c2e5ca756174d111109f72193d1e3c14a25a65833c6dfcd98b7" + }, + { + "name": "libbrotli1", + "version": "1.0.9-2+b6", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/b/brotli/libbrotli1_1.0.9-2+b6_arm64.deb", + "sha256": "3137de7f71952e710ee9f0df05026b3c6f463be9369a502d57c7696732f1ed22" + }, + { + "name": "libbsd0", + "version": "0.11.7-2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libb/libbsd/libbsd0_0.11.7-2_arm64.deb", + "sha256": "ef00f132ddd268ee67d756998723ee18543db27ab34930c0a9f1cff75cf55382" + }, + { + "name": "libbz2-1.0", + "version": "1.0.8-5+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/b/bzip2/libbz2-1.0_1.0.8-5+b1_arm64.deb", + "sha256": "d3a96ece03326498b39ff093a76800dfcbcb1d4049d6ae6e9f6fa1aa7a590ad6" + }, + { + "name": "libbz2-dev", + "version": "1.0.8-5+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/b/bzip2/libbz2-dev_1.0.8-5+b1_arm64.deb", + "sha256": "c9827ab40420f1259b1a10983978ec8de87c11fc1a6b3c192adea1c4c490754f" + }, + { + "name": "libc6", + "version": "2.36-9+deb12u14", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/g/glibc/libc6_2.36-9+deb12u14_arm64.deb", + "sha256": "01f4330719fd4f65580e16ea5a0527f372fca750e8f588d26deaf09f2d3b1cf4" + }, + { + "name": "libegl-dev", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libegl-dev_1.6.0-1_arm64.deb", + "sha256": "8a3004f892785a5fa4c9f969c116f567a36d1c8e9b3d5fd0403055151e36c7d3" + }, + { + "name": "libegl1", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libegl1_1.6.0-1_arm64.deb", + "sha256": "707097a275155c600e2e9251c4ee7cdfdb2d8f50a678a850a2526a7bc9664166" + }, + { + "name": "libexpat1", + "version": "2.5.0-1+deb12u2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/e/expat/libexpat1_2.5.0-1+deb12u2_arm64.deb", + "sha256": "8dc1976735acf825e3a922d82f171e20feec7394da2b8c616091983e38706275" + }, + { + "name": "libexpat1-dev", + "version": "2.5.0-1+deb12u2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/e/expat/libexpat1-dev_2.5.0-1+deb12u2_arm64.deb", + "sha256": "9987e4aa214b0ec23fc2ba6a99b8f10d2dc68f2de347722215ae3f960802e3d3" + }, + { + "name": "libffi-dev", + "version": "3.4.4-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libf/libffi/libffi-dev_3.4.4-1_arm64.deb", + "sha256": "8ff6335f76752ef8659762435d0d88129ef0090a587520853238809bd10ef392" + }, + { + "name": "libffi8", + "version": "3.4.4-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libf/libffi/libffi8_3.4.4-1_arm64.deb", + "sha256": "80b5c36177dc0e29d531c7eddbed3cc7355cb490e49f8cfa5959572d161f27b3" + }, + { + "name": "libfontconfig-dev", + "version": "2.14.1-4", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/f/fontconfig/libfontconfig-dev_2.14.1-4_arm64.deb", + "sha256": "7da35e69d4f43b337aa9508da8e9089de80e75285141be3d7bec1055a0501aa4" + }, + { + "name": "libfontconfig1", + "version": "2.14.1-4", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/f/fontconfig/libfontconfig1_2.14.1-4_arm64.deb", + "sha256": "4836d9deca824883f0de33d4f8f3718ba776c6e7c80cd7c418b5a0f86c009b9e" + }, + { + "name": "libfreetype-dev", + "version": "2.12.1+dfsg-5+deb12u4", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/f/freetype/libfreetype-dev_2.12.1+dfsg-5+deb12u4_arm64.deb", + "sha256": "4e5057ac9107c69c0f49f4a8989056a7c37184c94c73ee48ba7023c5fb118db7" + }, + { + "name": "libfreetype6", + "version": "2.12.1+dfsg-5+deb12u4", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/f/freetype/libfreetype6_2.12.1+dfsg-5+deb12u4_arm64.deb", + "sha256": "b8d32e54ebc9020637f8fa16b6afa64a5731e40845a6e01880d38d6f632237c9" + }, + { + "name": "libgl-dev", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libgl-dev_1.6.0-1_arm64.deb", + "sha256": "56add9e710c072cf56504beed50a639f9f7c64f4e31e66958fa65a566084888c" + }, + { + "name": "libgl1", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libgl1_1.6.0-1_arm64.deb", + "sha256": "f4043ab47f52325973d2b608514b3469b055b65c744861c7f378755a32e799d6" + }, + { + "name": "libglvnd0", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libglvnd0_1.6.0-1_arm64.deb", + "sha256": "d5063205fab5322ee686f33f87c6315b3fcd1335ea4e7bc8202b48ed4f9c9e83" + }, + { + "name": "libglx0", + "version": "1.6.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libg/libglvnd/libglx0_1.6.0-1_arm64.deb", + "sha256": "523f9b71f75e48ae605d8ef298a4b03ade26ade9a808a85329a3bd82a54b9b0d" + }, + { + "name": "libmd0", + "version": "1.0.4-2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libm/libmd/libmd0_1.0.4-2_arm64.deb", + "sha256": "99a8c7dd591fae9fb37d8bf8dfdffa850e207fa405b3198c5b24711a5f972381" + }, + { + "name": "libpng-dev", + "version": "1.6.39-2+deb12u5", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libp/libpng1.6/libpng-dev_1.6.39-2+deb12u5_arm64.deb", + "sha256": "f02dc017801713dfd11aaa7e72d705f747570e029fd242c29af072d64474ddfc" + }, + { + "name": "libpng16-16", + "version": "1.6.39-2+deb12u5", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libp/libpng1.6/libpng16-16_1.6.39-2+deb12u5_arm64.deb", + "sha256": "69aba3963ddfb11b98e5bf4b0d022f76b797436568884b10f2da32e0f4b8c2dd" + }, + { + "name": "libpthread-stubs0-dev", + "version": "0.4-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_arm64.deb", + "sha256": "acd58ed5d26d3f4d4b0856ebec6bc43f9c4357601a532eee0d2de839a8b8952e" + }, + { + "name": "libuuid1", + "version": "2.38.1-5+deb12u3", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/u/util-linux/libuuid1_2.38.1-5+deb12u3_arm64.deb", + "sha256": "3b2d19df0f3190108cf54228c4cfc822226c06d8f3615a5ab16a7dffb1f58aed" + }, + { + "name": "libwayland-client0", + "version": "1.21.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/w/wayland/libwayland-client0_1.21.0-1_arm64.deb", + "sha256": "ae390cc04c2eb1de90b9a6373505b22c730ada5e72daa50c507b7f99c12faf06" + }, + { + "name": "libwayland-cursor0", + "version": "1.21.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/w/wayland/libwayland-cursor0_1.21.0-1_arm64.deb", + "sha256": "8c8d1a3942bc3fc9dd3ea7c679c1a314e59cdaaa700883d54d1bb3ec89db256b" + }, + { + "name": "libwayland-dev", + "version": "1.21.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/w/wayland/libwayland-dev_1.21.0-1_arm64.deb", + "sha256": "932405c58195d299495230792e374ad304adadf3d3ffdc07eda3466b756adc33" + }, + { + "name": "libwayland-egl1", + "version": "1.21.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/w/wayland/libwayland-egl1_1.21.0-1_arm64.deb", + "sha256": "c08124a4d9af24f058b45c88867a84697882e755ea94965795d152fe26fe57c9" + }, + { + "name": "libwayland-server0", + "version": "1.21.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/w/wayland/libwayland-server0_1.21.0-1_arm64.deb", + "sha256": "4d99e7e0503fdaa999e67d47d8612c0465e775eafa47819573da50bdb09064f2" + }, + { + "name": "libx11-6", + "version": "2:1.8.4-2+deb12u2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libx11/libx11-6_1.8.4-2+deb12u2_arm64.deb", + "sha256": "d1d533e983582282a9ea82c87ac5ce715a9b67bd6d1acbd2439a11c63c36549b" + }, + { + "name": "libx11-dev", + "version": "2:1.8.4-2+deb12u2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libx11/libx11-dev_1.8.4-2+deb12u2_arm64.deb", + "sha256": "34bfc833561de983cfb4475523fedc13abb4faee14a876300de659591779add8" + }, + { + "name": "libx11-xcb1", + "version": "2:1.8.4-2+deb12u2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libx11/libx11-xcb1_1.8.4-2+deb12u2_arm64.deb", + "sha256": "21c7e6f19909bb1e02bb3c2089789791858c54d37e86cef9d7872782c1cb711e" + }, + { + "name": "libxau-dev", + "version": "1:1.0.9-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxau/libxau-dev_1.0.9-1_arm64.deb", + "sha256": "21db3761a8f388fc83707c00fac645d4332737a859dd727571e1e6ede003b048" + }, + { + "name": "libxau6", + "version": "1:1.0.9-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxau/libxau6_1.0.9-1_arm64.deb", + "sha256": "36c2bf400641a80521093771dc2562c903df4065f9eb03add50d90564337ea6c" + }, + { + "name": "libxcb1", + "version": "1.15-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxcb/libxcb1_1.15-1_arm64.deb", + "sha256": "041d9a68415c3ccf3ce8f4f8b88e4bbfb5dc1f0d97013c6ef8423e620ea50f84" + }, + { + "name": "libxcb1-dev", + "version": "1.15-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxcb/libxcb1-dev_1.15-1_arm64.deb", + "sha256": "87b5c3f1bb130727df68295ecfaadc305ee10c150afeb1a183e95b9150e18db5" + }, + { + "name": "libxdmcp-dev", + "version": "1:1.1.2-3", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxdmcp/libxdmcp-dev_1.1.2-3_arm64.deb", + "sha256": "6fd0bc51f3a4e6b7f944f24f15524a4c4ba68a4b0aa136c18bdb96a2d36988f2" + }, + { + "name": "libxdmcp6", + "version": "1:1.1.2-3", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxdmcp/libxdmcp6_1.1.2-3_arm64.deb", + "sha256": "e92569ac33247261aa09adfadc34ced3994ac301cf8b58de415a2d5dbf15ccfc" + }, + { + "name": "libxext-dev", + "version": "2:1.3.4-1+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxext/libxext-dev_1.3.4-1+b1_arm64.deb", + "sha256": "07f5e3efeba9564071885b2d0d2ba277692fa309a9f05ef862688af0022b1fcf" + }, + { + "name": "libxext6", + "version": "2:1.3.4-1+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxext/libxext6_1.3.4-1+b1_arm64.deb", + "sha256": "5e9c0ad606eb4674c645fe8e0e64330c47d2729f7a59ed569848610efd5d5b62" + }, + { + "name": "libxfixes3", + "version": "1:6.0.0-2", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxfixes/libxfixes3_6.0.0-2_arm64.deb", + "sha256": "d47bda8fed01b19b41d503e2df05d9166c58e30e2376f2f8784ceb7a834befe6" + }, + { + "name": "libxi6", + "version": "2:1.8-1+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxi/libxi6_1.8-1+b1_arm64.deb", + "sha256": "b26383c9929d8bfba051eee22301e064d17039ed022bbf31990feb0d7a03b36c" + }, + { + "name": "libxrandr2", + "version": "2:1.5.2-2+b1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxrandr/libxrandr2_1.5.2-2+b1_arm64.deb", + "sha256": "f0bc7f79fee182caae176652fdb1bf349d13b9591b10ad4dc1c896521da8e49f" + }, + { + "name": "libxrender1", + "version": "1:0.9.10-1.1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/libx/libxrender/libxrender1_0.9.10-1.1_arm64.deb", + "sha256": "f260193deb4eaf0b410a1c61e1f84804366ee3372b8bad9f38005f7dbcd89aae" + }, + { + "name": "x11proto-dev", + "version": "2022.1-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/x/xorgproto/x11proto-dev_2022.1-1_all.deb", + "sha256": "3cab66a6591774188a568f9b54c4e7956f18da76e0e0fc4a779db5f6bcc3f148" + }, + { + "name": "xtrans-dev", + "version": "1.4.0-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/x/xtrans/xtrans-dev_1.4.0-1_all.deb", + "sha256": "9ce1af9464faee0c679348dd11cdf63934c12e734a64e0903692b0cb5af38e06" + }, + { + "name": "zlib1g", + "version": "1:1.2.13.dfsg-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/z/zlib/zlib1g_1.2.13.dfsg-1_arm64.deb", + "sha256": "52b8b8a145bbe1956bba82034f77022cbef0c3d0885c9e32d9817a7932fe1913" + }, + { + "name": "zlib1g-dev", + "version": "1:1.2.13.dfsg-1", + "url": "https://snapshot.debian.org/archive/debian/20260901T000000Z/pool/main/z/zlib/zlib1g-dev_1.2.13.dfsg-1_arm64.deb", + "sha256": "afb60f0bae432f2f0edfe2d674e99c1a13a147c9aa2b52f2f23e3df19ece76f5" + } + ] +} diff --git a/build/linux-toolchain/local-cross-validation.json b/build/linux-toolchain/local-cross-validation.json new file mode 100644 index 0000000..560e766 --- /dev/null +++ b/build/linux-toolchain/local-cross-validation.json @@ -0,0 +1,103 @@ +{ + "tested_date_utc": "2026-10-04", + "host": "Ubuntu 24.04 Linux x64", + "zig_version": "0.14.1", + "sources": { + "skiasharp_version": "4.150.1", + "skiasharp_revision": "c3e4f4c20e1f23ab74d31a8838a5bd6dc55365f2", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "0aa2d542e833ffd1d4d1b68a5152375e7f65ce11" + }, + "target_runtime_tested": false, + "docker_build_tested": false, + "cold_source_preparation_tested": false, + "avalonia_nativeaot_tested": false, + "targets": { + "win-x64": { + "six_archives_compiled": true, + "raster_harfbuzz_link": "passed", + "egl_gles_link": "passed", + "archives_sha256": { + "libANGLE.a": "9b4af0208fef7773dc91f684cdf676b156f48eb40821c99eb55b531f4b262214", + "libEGL.a": "dc1d8568d8d3bab0d5e6956f97db88a1d3c5887a5f2437dfd29fa5abb2a48c97", + "libGLESv2.a": "a78c019c732a356691b849d8f632dab9f1c3254452ef98fc3ed963fd33e7049c", + "libHarfBuzzSharp.a": "71602aa223348bc34f754438bd2f9b90ba025547c8305b0ad45e23fbd4d0a882", + "libSkiaSharp.a": "3aa2b79953dfcd3b8dde7996701c3a29878e61fb589a6398f0cccacd1c33f4f2", + "libskia.a": "40418692d55d26e55f1882f8386ce341410a0712ae1691d47a26b3033bd7e9a8" + }, + "executables_sha256": { + "smoke-angle.exe": "bc54570c5f9cdca0a28e2f9ceb2bdb1a118cabfdf6516557d1d31fd777a7d4c5", + "smoke-raster.exe": "289f963e2180bd12cdca298b931451ebc7698bef71930318899adc4505fbccef" + } + }, + "win-x86": { + "six_archives_compiled": true, + "raster_harfbuzz_link": "passed", + "egl_gles_link": "passed", + "archives_sha256": { + "libANGLE.a": "27f715956b9625ffca27ebb10e4135d173ed03970745c257fe6437fd8db23daf", + "libEGL.a": "ddab449877aa244333f43c4a79705d3c1347c5cfae778c46ca707cd78e9ed8ed", + "libGLESv2.a": "adf90968b2a8984c9bceefbbce1edacb1a06038fdc9b2bfc962e9227a9c947bd", + "libHarfBuzzSharp.a": "8483f2e5cc92561884163f317f496a2e4b6b61242774176fe8b6370bbc54c695", + "libSkiaSharp.a": "565c25b03b4f574a7db5dca98327b5f7b8eea07c1f0dab5e09d6c731ad71fd29", + "libskia.a": "97e086493216fef2d372f555c62c2fbaf28e4d6a61398ec37334164408bcc31f" + }, + "executables_sha256": { + "smoke-angle.exe": "5e3a6a7951f038fa1e1a9889e8f3212de285a9464b4b722700da259b1bb9e7bb", + "smoke-raster.exe": "9b1b4dbc3686caa89ae03cb19c5d29b3cde16ab8c0381f7c1c8b339a8f5169e5" + } + }, + "win-arm64": { + "six_archives_compiled": true, + "raster_harfbuzz_link": "passed", + "egl_gles_link": "passed", + "archives_sha256": { + "libANGLE.a": "d496f595573d62e64ad1fad9339f64249ddd3904d37146ad3d1e1c58b032d797", + "libEGL.a": "8f87692e53a3843eb8131e51f0cbc03c7eef02039d22cc25348ecacbc12028d7", + "libGLESv2.a": "cea510c2d0bbed66bd67d0070953408da23a39a0e5a3be959ed4cec6069973cb", + "libHarfBuzzSharp.a": "662f4174d0bc0ee0760fdeacebb7ce8f187118b524239a745058547d4c25ca10", + "libSkiaSharp.a": "17d4ed43351f0b97b6c816de41a3e2469dd42d4df42acc6201fd30a75b8e25df", + "libskia.a": "52d4b8ec673881d4beb729580eac867696ca5fda4ad4c2a498f33cd8b915c76b" + }, + "executables_sha256": { + "smoke-angle.exe": "68367ceba8053f75b841d0e2b7ab6bc8f613836b91ebd13e463fe6d261b245a9", + "smoke-raster.exe": "ce758159e371ae94504ae1502dbe26034ea1c3789f33f71a0f65640991578dfb" + } + }, + "osx-x64": { + "six_archives_compiled": true, + "raster_harfbuzz_link": "passed", + "egl_gles_link": "passed", + "archives_sha256": { + "libANGLE.a": "353b6ad07961f94cdbde8f7901fc924fbb50600156da8194ec7dd5d9c9d96115", + "libEGL.a": "6ea3c6d73e1f6a3785fdd941c37e50c7d00f3f5c136edf648f27243e5698d517", + "libGLESv2.a": "e3bbb289e2be771c498eae0c4037e3d9058a0a52e100dd5b6622de42f00ecb7c", + "libHarfBuzzSharp.a": "c80b4156937b0a63ff649d7ef9ee520e342ee6f6d556a65a42e37095e91e1157", + "libSkiaSharp.a": "2a1e5d7d7737921cdb6a9679630fb3068ce1d2eecd7300ec7747898bb8132d39", + "libskia.a": "8062a85f7f5ded8f67c19db06b42c6792a526d5856c4bae467fb46d182992315" + }, + "executables_sha256": { + "smoke-angle": "1930720daf83d6a63268427b5c1a700a46fa6cf8edd93821133224fb1d456522", + "smoke-raster": "6cb98cb854b52f94339eb225422d511b6e11abc9e5299658b455352f332139c4" + } + }, + "osx-arm64": { + "six_archives_compiled": true, + "raster_harfbuzz_link": "passed", + "egl_gles_link": "passed", + "archives_sha256": { + "libANGLE.a": "3459ece2b66c8cbb096bf20016a40d4d463224ff7a0b4e29cab44c578daf1665", + "libEGL.a": "6011625f7ac8bf467e3135588a9a30e472f55fec6de3834b1b472deed4627d15", + "libGLESv2.a": "2c4e990ddc67f98abcc09401fa5541386cbe80458581c6fd621fd379a8541835", + "libHarfBuzzSharp.a": "3802662600cca36985997d24187c3bc06e1a20c258fc260778eecd34a5d791f9", + "libSkiaSharp.a": "e5bf6468713228ab9386d6f31f6556aed98a9cbc307b4be1fbbd61644f97658b", + "libskia.a": "44e3515fdf0f09c8621b35c37c23d4441d34a831d42b88391811ad8fdff710da" + }, + "executables_sha256": { + "smoke-angle": "f19386a0d04af9a89f8ec70df6f3a1205747cfc2761efd86c4e781bcf0818018", + "smoke-raster": "954dde21bf79234fa0abc30b6e5307c88558996b7777f0d459693a3588b1cc43" + } + } + } +} diff --git a/build/linux-toolchain/multi-version-validation.json b/build/linux-toolchain/multi-version-validation.json new file mode 100644 index 0000000..6892d42 --- /dev/null +++ b/build/linux-toolchain/multi-version-validation.json @@ -0,0 +1,56 @@ +{ + "date_utc": "2026-10-04", + "compiler": "Zig 0.14.1 / Clang 19", + "cross_configuration_checks": { + "2.88.9/win-x64": 0, + "2.88.9/win-x86": 0, + "2.88.9/win-arm64": 0, + "2.88.9/osx-x64": 0, + "2.88.9/osx-arm64": 0, + "3.119.1/win-x64": 0, + "3.119.1/win-x86": 0, + "3.119.1/win-arm64": 0, + "3.119.1/osx-x64": 0, + "3.119.1/osx-arm64": 0, + "4.150.1/win-x64": 0, + "4.150.1/win-x86": 0, + "4.150.1/win-arm64": 0, + "4.150.1/osx-x64": 0, + "4.150.1/osx-arm64": 0 + }, + "native_linux_configuration_checks": { + "2.88.9/x64": "passed", + "2.88.9/arm64": "passed", + "3.119.1/x64": "passed", + "3.119.1/arm64": "passed", + "4.150.1/x64": "passed", + "4.150.1/arm64": "passed" + }, + "new_full_build_checks": { + "2.88.9": { + "target": "win-x64", + "three_archives": "passed", + "raster_harfbuzz_executable_link": "passed", + "sha256": { + "libskia.a": "d972f868e4910cee774d592b09e3e52f65657ec3b10a1c38c26e6625e998196e", + "libSkiaSharp.a": "e7f22c39ce570fa74988e13a288e00646cf1b08e0be82e60a6b589bab149a8fd", + "libHarfBuzzSharp.a": "e7e117c25a16f9770f0953a260441d920b5a1e27b8c52457429f03ddacbe8855", + "smoke-raster.exe": "6a8ab77adc1444933757f702aa4dab10716a60a284ffa4777745493443909f9d" + } + }, + "3.119.1": { + "target": "win-x64", + "three_archives": "passed", + "raster_harfbuzz_executable_link": "passed", + "sha256": { + "libskia.a": "d895266910b2fc8a6a72d56715ddd0b50e1e9752b184d3b99a31b8ca6614451a", + "libSkiaSharp.a": "544685511adf5b29eac2df771ef5088ba271fb570614671d0bde68e841ac3b32", + "libHarfBuzzSharp.a": "6ac05af0bb31b42af86f003e4eb70db4c4eeb021b5a438282bbd182db0dba100", + "smoke-raster.exe": "80cfda94712bd1df0671ce96fb27a174053451557d99ab542f148ac45a951532" + } + } + }, + "runtime_tests": false, + "docker_cold_build_tested": false, + "4.150.1": "Previous five-target full archive/link checks are in local-cross-validation.json; updated win-x64 link test also passed." +} diff --git a/build/linux-toolchain/package-zig.py b/build/linux-toolchain/package-zig.py new file mode 100644 index 0000000..b8b2e12 --- /dev/null +++ b/build/linux-toolchain/package-zig.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +"""Export verified Zig archives in the layout consumed by the NuGet package.""" +import argparse,json,shutil,hashlib +from pathlib import Path +p=argparse.ArgumentParser();p.add_argument('--input',type=Path,required=True);p.add_argument('--output',type=Path,required=True);p.add_argument('--rid',required=True) +a=p.parse_args();manifest=json.loads((a.input/'cross-build.json').read_text()) +if manifest['target']!=a.rid or not manifest.get('link_tests','').startswith('passed'): + raise SystemExit('Only successfully linked target archives may be packaged') +names={'libskia.a':'skia.lib','libSkiaSharp.a':'SkiaSharp.lib','libHarfBuzzSharp.a':'libHarfBuzzSharp.lib','libANGLE.a':'libANGLE_static.lib','libGLESv2.a':'libGLESv2_static.lib','libEGL.a':'libEGL_static.lib'} if a.rid.startswith('win') else {'libskia.a':'libskia.a','libSkiaSharp.a':'libSkiaSharp.a','libHarfBuzzSharp.a':'libHarfBuzzSharp.a','libANGLE.a':'libANGLE_static.a','libGLESv2.a':'libGLESv2_static.a','libEGL.a':'libEGL_static.a'} +a.output.mkdir(parents=True,exist_ok=True) +for source,destination in names.items(): + path=a.input/source + if hashlib.sha256(path.read_bytes()).hexdigest()!=manifest['archives'][source]:raise SystemExit('Archive checksum mismatch: '+source) + shutil.copy2(path,a.output/destination) +runtime=a.input/'zig-runtime' +if not runtime.is_dir() or not [p for p in runtime.iterdir() if p.suffix in ('.a','.lib')]:raise SystemExit('Zig C++/compiler runtime archives were not captured') +(a.output/'zig-runtime').mkdir(exist_ok=True) +for path in runtime.iterdir(): + if path.suffix not in ('.a','.lib'):continue + dest=path.stem+'.lib' if a.rid.startswith('win') else path.name + shutil.copy2(path,a.output/'zig-runtime'/dest) +if a.rid=='win-x86': + path=a.input/'skia_x86_stdcall_thunks.lib' + if not path.is_file():raise SystemExit('x86 stdcall thunks are required') + shutil.copy2(path,a.output/path.name) +shutil.copy2(a.input/'cross-build.json',a.output/'zig-build.json') +print('Packaged',a.rid) diff --git a/build/linux-toolchain/patches/.gitattributes b/build/linux-toolchain/patches/.gitattributes new file mode 100644 index 0000000..710fd51 --- /dev/null +++ b/build/linux-toolchain/patches/.gitattributes @@ -0,0 +1,2 @@ +# Unified diffs contain space-prefixed empty context lines. +*.patch -whitespace diff --git a/build/linux-toolchain/patches/BUILD.gn-zig.patch b/build/linux-toolchain/patches/BUILD.gn-zig.patch new file mode 100644 index 0000000..9121d8a --- /dev/null +++ b/build/linux-toolchain/patches/BUILD.gn-zig.patch @@ -0,0 +1,28 @@ +--- a/BUILD.gn ++++ b/BUILD.gn +@@ -427,7 +427,8 @@ + ldflags = [ "-Wl,--build-id" ] + } + +-_use_copy_compiler_dll = angle_has_build && is_win ++declare_args() { angle_copy_compiler_dll = true } ++_use_copy_compiler_dll = angle_has_build && is_win && angle_copy_compiler_dll + + if (_use_copy_compiler_dll) { + copy("copy_compiler_dll") { +@@ -1534,6 +1535,7 @@ + } + + angle_static_library("libGLESv2_static") { ++ complete_static_lib = true + + sources = libglesv2_sources + public_deps = [ ":libGLESv2_thin_static" ] +@@ -1652,6 +1654,7 @@ + } + + libEGL_template("libEGL_static") { ++ complete_static_lib = true + target_type = "angle_static_library" + public_configs = [ ":angle_static_config" ] + deps = [ ":libGLESv2_static" ] diff --git a/build/linux-toolchain/patches/BUILDCONFIG.gn-zig.patch b/build/linux-toolchain/patches/BUILDCONFIG.gn-zig.patch new file mode 100644 index 0000000..a656121 --- /dev/null +++ b/build/linux-toolchain/patches/BUILDCONFIG.gn-zig.patch @@ -0,0 +1,20 @@ +--- a/build/config/BUILDCONFIG.gn ++++ b/build/config/BUILDCONFIG.gn +@@ -584,7 +584,7 @@ + } + + if (!defined(use_libcxx_modules)) { +- use_libcxx_modules = is_clang ++ use_libcxx_modules = is_clang && !(is_win && host_os == "linux") + } + + if (filter_include( +@@ -715,7 +715,7 @@ + } + + if (!defined(use_libcxx_modules)) { +- use_libcxx_modules = is_clang ++ use_libcxx_modules = is_clang && !(is_win && host_os == "linux") + } + + if (filter_include( diff --git a/build/linux-toolchain/patches/angle-chromium-7922.patch b/build/linux-toolchain/patches/angle-chromium-7922.patch new file mode 100644 index 0000000..2325737 --- /dev/null +++ b/build/linux-toolchain/patches/angle-chromium-7922.patch @@ -0,0 +1,64 @@ +diff --git a/DEPS b/DEPS +index 8ba99a6..1a5e1a5 100644 +--- a/DEPS ++++ b/DEPS +@@ -768,9 +768,4 @@ deps = { + }, + +- 'third_party/catapult': { +- 'url': Var('chromium_git') + '/catapult.git' + '@' + Var('catapult_revision'), +- 'condition': 'not build_with_chromium', +- }, +- + # Cherry is a dEQP/VK-GL-CTS management GUI written in Go. We use it for viewing test results. + 'third_party/cherry': { +@@ -795,9 +790,4 @@ deps = { + + +- 'third_party/dawn': { +- 'url': Var('dawn_git') + '/dawn.git' + '@' + Var('dawn_revision'), +- 'condition': 'checkout_angle_dawn_deps and not build_with_chromium' +- }, +- + 'third_party/depot_tools': { + 'url': Var('chromium_git') + '/chromium/tools/depot_tools.git@94e89b10b92cc9d6e58fc8d1b6474b7d29e8a114', +@@ -875,9 +865,4 @@ deps = { + }, + +- 'third_party/llvm/src': { +- 'url': Var('chromium_git') + '/external/github.com/llvm/llvm-project@67790d3fb06b184b7be9c4d3e36ab01f0ac4198e', +- 'condition': 'checkout_angle_cl_deps and not build_with_chromium', +- }, +- + 'third_party/llvm-build/Release+Asserts': { + 'dep_type': 'gcs', +@@ -1217,9 +1202,4 @@ deps = { + }, + +- 'third_party/SwiftShader': { +- 'url': Var('swiftshader_git') + '/SwiftShader@f3e464b1e56795940f826a972b39e19bd9f6ac6c', +- 'condition': 'not build_with_chromium', +- }, +- + 'third_party/turbine/cipd': { + 'packages': [ +@@ -1233,8 +1213,4 @@ deps = { + }, + +- 'third_party/VK-GL-CTS/src': { +- 'url': Var('chromium_git') + '/external/github.com/KhronosGroup/VK-GL-CTS' + '@' + Var('vk_gl_cts_revision'), +- }, +- + 'third_party/vulkan-deps': { + 'url': Var('chromium_git') + '/vulkan-deps@f2b9fece7c6772c09379b3a6938dbcd2573f2a3e', +diff --git a/src/compiler/translator/IntermNode.cpp b/src/compiler/translator/IntermNode.cpp +index 710bba9..33d4a64 100644 +--- a/src/compiler/translator/IntermNode.cpp ++++ b/src/compiler/translator/IntermNode.cpp +@@ -881,5 +881,5 @@ const TConstantUnion *TIntermAggregate::getConstantValue() const + angle::base::CheckedNumeric checkedArraySize = elementSize; + checkedArraySize *= getOutermostArraySize(); +- constArray = new TConstantUnion[checkedArraySize.ValueOrDie()]; ++ constArray = new TConstantUnion[static_cast(checkedArraySize.ValueOrDie())]; + + size_t elementOffset = 0u; diff --git a/build/linux-toolchain/patches/angle-static-targets.patch b/build/linux-toolchain/patches/angle-static-targets.patch new file mode 100644 index 0000000..039d37b --- /dev/null +++ b/build/linux-toolchain/patches/angle-static-targets.patch @@ -0,0 +1,21 @@ +--- a/BUILD.gn ++++ b/BUILD.gn +@@ -1523,7 +1523,18 @@ + public_deps = [ ":libANGLE" ] + } + ++angle_static_library("libANGLE_static") { ++ complete_static_lib = true ++ public_deps = [ ":libANGLE" ] ++} ++ ++angle_static_library("libANGLE_with_capture_static") { ++ complete_static_lib = true ++ public_deps = [ ":libANGLE_with_capture" ] ++} ++ + angle_static_library("libGLESv2_static") { ++ + sources = libglesv2_sources + public_deps = [ ":libGLESv2_thin_static" ] + } diff --git a/build/linux-toolchain/patches/find_sdk-linux.patch b/build/linux-toolchain/patches/find_sdk-linux.patch new file mode 100644 index 0000000..70b660e --- /dev/null +++ b/build/linux-toolchain/patches/find_sdk-linux.patch @@ -0,0 +1,15 @@ +--- a/build/mac/find_sdk.py ++++ b/build/mac/find_sdk.py +@@ -1,3 +1,12 @@ ++import os,sys,json ++if sys.platform == 'linux' and os.environ.get('STATICLINK_MACOS_SDK'): ++ sdk=os.environ['STATICLINK_MACOS_SDK'] ++ if '--print_sdk_path' in sys.argv:print('//'+str(__import__('pathlib').Path.cwd().relative_to(__import__('pathlib').Path(__file__).resolve().parents[2]))+'/sdk/xcode_links/MacOSX15.5.sdk') ++ if '--print_bin_path' in sys.argv:print(os.environ['STATICLINK_DARWIN_TOOLS']) ++ if '--print_sdk_build' in sys.argv:print('24F74') ++ print('15.5') ++ sys.exit(0) ++ + #!/usr/bin/env python3 + # Copyright 2012 The Chromium Authors + # Use of this source code is governed by a BSD-style license that can be diff --git a/build/linux-toolchain/patches/gcc-toolchain-objc.patch b/build/linux-toolchain/patches/gcc-toolchain-objc.patch new file mode 100644 index 0000000..8ebba0c --- /dev/null +++ b/build/linux-toolchain/patches/gcc-toolchain-objc.patch @@ -0,0 +1,38 @@ +--- a/build/toolchain/gcc_toolchain.gni ++++ b/build/toolchain/gcc_toolchain.gni +@@ -369,6 +369,35 @@ + outputs = [ output ] + } + ++ if (current_os == "mac") { ++ tool("objc") { ++ output = "$object_subdir/{{source_name_part}}.o" ++ depfile = "$output.d" ++ precompiled_header_type = "gcc" ++ ++ # {{output}} may contain .dwo file, so use $output instead of {{output}} ++ # in command. ++ command = "$coverage_wrapper$cc $md -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_objc}}${extra_cppflags}${extra_cflags}${module_name_flag} -c {{source}} -o $output" ++ depsformat = "gcc" ++ description = "OBJC {{output}}" ++ outputs = [ output ] ++ } ++ ++ tool("objcxx") { ++ output = "$object_subdir/{{source_name_part}}.o" ++ depfile = "$output.d" ++ precompiled_header_type = "gcc" ++ ++ # {{output}} may contain .dwo file, so use $output instead of {{output}} ++ # in command. ++ command = "$coverage_wrapper$cxx $md -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_objcc}}${extra_cppflags}${extra_cxxflags} {{module_deps}}${module_name_flag} -c {{source}} -o $output" ++ depsformat = "gcc" ++ description = "OBJCXX {{output}}" ++ outputs = [ output ] ++ } ++ ++ } ++ + tool("cxx_module") { + depfile = "{{output}}.d" + diff --git a/build/linux-toolchain/patches/sdk_info-linux.patch b/build/linux-toolchain/patches/sdk_info-linux.patch new file mode 100644 index 0000000..d57304f --- /dev/null +++ b/build/linux-toolchain/patches/sdk_info-linux.patch @@ -0,0 +1,14 @@ +--- a/build/config/apple/sdk_info.py ++++ b/build/config/apple/sdk_info.py +@@ -1,3 +1,11 @@ ++import os,sys,json ++if sys.platform == 'linux' and os.environ.get('STATICLINK_MACOS_SDK'): ++ sdk=os.environ['STATICLINK_MACOS_SDK'] ++ sdk='//'+str(__import__('pathlib').Path.cwd().relative_to(__import__('pathlib').Path(__file__).resolve().parents[3]))+'/sdk/xcode_links/MacOSX15.5.sdk' ++ values={'xcode_version':'1640','xcode_version_int':1640,'xcode_build':'16F6','sdk_path':sdk,'sdk_version':'15.5','sdk_build':'24F74','sdk_platform_path':sdk,'toolchains_path':os.environ['STATICLINK_DARWIN_TOOLS']} ++ for k,v in values.items():print(k+'='+json.dumps(v)) ++ sys.exit(0) ++ + #!/usr/bin/env python3 + # Copyright 2014 The Chromium Authors + # Use of this source code is governed by a BSD-style license that can be diff --git a/build/linux-toolchain/patches/skia-xps-linkage.patch b/build/linux-toolchain/patches/skia-xps-linkage.patch new file mode 100644 index 0000000..6a6889a --- /dev/null +++ b/build/linux-toolchain/patches/skia-xps-linkage.patch @@ -0,0 +1,11 @@ +--- a/src/xps/SkXPSDevice.cpp ++++ b/src/xps/SkXPSDevice.cpp +@@ -337,7 +337,7 @@ + return true; + } + +-static HRESULT subset_typeface(const SkXPSDevice::TypefaceUse& current) { ++HRESULT subset_typeface(const SkXPSDevice::TypefaceUse& current) { + //The CreateFontPackage API is only supported on desktop, not in UWP + #if defined(SK_WINUWP) || defined(SK_BUILD_FOR_WINRT) || defined(SK_BUILD_FOR_NANOSERVER) + return E_NOTIMPL; diff --git a/build/linux-toolchain/probe-cross.py b/build/linux-toolchain/probe-cross.py new file mode 100644 index 0000000..b312f06 --- /dev/null +++ b/build/linux-toolchain/probe-cross.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +"""Compile target objects and SDK API probes; this does not build release libraries.""" +import argparse +import json +import pathlib +import subprocess +import tempfile + +TARGETS = ( + "x86-windows-gnu", "x86_64-windows-gnu", "aarch64-windows-gnu", + "x86-windows-msvc", "x86_64-windows-msvc", "aarch64-windows-msvc", + "x86_64-macos", "aarch64-macos", +) +SOURCES = { + "plain": "int answer(void) { return 42; }\n", + "cpp": '#include \nextern "C" int answer(void) { std::string s="test"; return int(s.size()); }\n', + "windows": '''#include +#include +#include +// These call shapes are required by Skia 4.150.1's DirectWrite paint backend. +HRESULT paint_child(IDWritePaintReader* reader, DWRITE_PAINT_ELEMENT* element) { + return reader->MoveToFirstChild(element); +} +void paint_color(IDWritePaintReader* reader, DWRITE_COLOR_F color) { + reader->SetTextColor(color); +} +''', + "macos": '''#include +#import +int answer(void) { return sizeof(CFIndex); } +''', +} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--zig", default="zig") + parser.add_argument("--output", type=pathlib.Path, required=True) + parser.add_argument("--target", choices=TARGETS, action="append") + parser.add_argument("--macos-sdk", type=pathlib.Path) + parser.add_argument("--windows-sdk", type=pathlib.Path, + help="An xwin splat containing crt/include and sdk/include") + args = parser.parse_args() + output = args.output.resolve() + output.mkdir(parents=True, exist_ok=True) + version = subprocess.check_output([args.zig, "version"], text=True).strip() + results = {"zig_version": version, "targets": {}} + with tempfile.TemporaryDirectory(prefix="staticlink-probe-") as temporary: + source_dir = pathlib.Path(temporary) + for name, source in SOURCES.items(): + suffix = ".mm" if name == "macos" else ".cpp" + (source_dir / (name + suffix)).write_text(source) + for target in args.target or TARGETS: + destination = output / target + destination.mkdir(exist_ok=True) + flags = [] + if "macos" in target and args.macos_sdk: + sdk = args.macos_sdk.resolve() + flags += ["-isysroot", str(sdk), "-F", str(sdk / "System/Library/Frameworks")] + if target.endswith("windows-msvc") and args.windows_sdk: + sdk = args.windows_sdk.resolve() + for include in ("crt/include", "sdk/include/ucrt", "sdk/include/shared", "sdk/include/um"): + flags += ["-isystem", str(sdk / include)] + target_results = {} + for name in ("plain", "cpp", "windows" if "windows" in target else "macos"): + suffix = ".mm" if name == "macos" else ".cpp" + command = [args.zig, "c++", "-target", target, "-std=c++20", *flags, + "-c", str(source_dir / (name + suffix)), + "-o", str(destination / (name + ".o"))] + result = subprocess.run(command, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True) + (destination / (name + ".log")).write_text(result.stdout) + target_results[name] = {"exit_code": result.returncode, "command": command} + print(target, name, "PASS" if result.returncode == 0 else "FAIL", flush=True) + results["targets"][target] = target_results + (output / "results.json").write_text(json.dumps(results, indent=2) + "\n") + return int(any(test["exit_code"] for tests in results["targets"].values() for test in tests.values())) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/build/linux-toolchain/recent-version-validation.json b/build/linux-toolchain/recent-version-validation.json new file mode 100644 index 0000000..0d72bd7 --- /dev/null +++ b/build/linux-toolchain/recent-version-validation.json @@ -0,0 +1,68 @@ +{ + "date": "2026-10-04", + "zig": "0.14.1", + "versions": [ + "3.119.2", + "3.119.4", + "4.150.3", + "4.150.5", + "4.153.1" + ], + "configuration": { + "passed": 45, + "targets_per_version": 9, + "full_build": false + }, + "full_skia_build_and_executable_link": { + "win-x64": [ + "3.119.2", + "3.119.4", + "4.150.3", + "4.150.5", + "4.153.1" + ], + "win-x86": [ + "3.119.4" + ], + "linux-x64": [ + "3.119.4" + ], + "linux-arm64": [ + "3.119.4" + ], + "osx-arm64": [ + "4.153.1" + ] + }, + "angle": { + "linux-x64": "3.119.4: six archives compiled and both executable links passed", + "win-x86": "3.119.4: fixed ANGLE archives reused; both executable links passed" + }, + "execution": { + "linux-x64": "64 raster pixels and 2 UTF-8 codepoints passed", + "linux-arm64": "same raster/UTF-8 test passed under QEMU", + "gpu": "not validated; Linux ANGLE probe lacked a display" + }, + "avalonia_native": { + "versions": [ + "11.3.14", + "12.1.0" + ], + "targets": [ + "osx-x64", + "osx-arm64" + ], + "compilation": "all four passed", + "executable_link": "all four passed against macOS SDK system libc++", + "execution": "not run" + }, + "not_validated": [ + "complete Debian Docker image build", + "all five releases full nine-RID build", + "Windows/macOS runtime", + "full NativeAOT application", + "GitHub Actions execution", + "NuGet publication" + ], + "environment_note": "Local builds ran on the Linux host with pinned cross tools, not inside Docker. Disk exhaustion required resumptions; it is not recorded as source incompatibility." +} diff --git a/build/linux-toolchain/skia-linux.gn.in b/build/linux-toolchain/skia-linux.gn.in new file mode 100644 index 0000000..d0a11dd --- /dev/null +++ b/build/linux-toolchain/skia-linux.gn.in @@ -0,0 +1,30 @@ +target_os = "linux" +target_cpu = "@CPU@" +is_official_build = true +is_static_skiasharp = true +skia_enable_tools = false +skia_enable_ganesh = true +skia_enable_pdf = false +skia_enable_skottie = false +skia_use_dng_sdk = false +skia_use_fontconfig = true +skia_use_freetype = true +skia_use_harfbuzz = false +skia_use_icu = false +skia_use_piex = false +skia_use_sfntly = false +skia_use_system_expat = false +skia_use_system_freetype2 = false +skia_use_system_libjpeg_turbo = false +skia_use_system_libpng = false +skia_use_system_libwebp = false +skia_use_system_zlib = false +skia_use_vulkan = false +skia_use_xps = false +extra_cflags_cc = [ "-frtti", "-pthread" ] +cc = "@CC@" +cxx = "@CXX@" +ar = "@AR@" +extra_cflags = [ "-DSKIA_C_DLL", "-DHAVE_SYSCALL_GETRANDOM", "-DXML_DEV_URANDOM" ] +# Standalone SkiaSharp excludes the Chromium PartitionAlloc dependency. +skia_use_partition_alloc = false diff --git a/build/linux-toolchain/skia-mac.gn.in b/build/linux-toolchain/skia-mac.gn.in new file mode 100644 index 0000000..f013766 --- /dev/null +++ b/build/linux-toolchain/skia-mac.gn.in @@ -0,0 +1,27 @@ +target_os = "mac" +target_cpu = "@CPU@" +is_official_build = true +is_static_skiasharp = true +skia_enable_tools = false +skia_enable_ganesh = true +skia_use_metal = true +skia_enable_pdf = false +skia_enable_skottie = false +skia_use_dng_sdk = false +skia_use_fontconfig = false +skia_use_freetype = false +skia_use_harfbuzz = false +skia_use_icu = false +skia_use_piex = false +skia_use_sfntly = false +skia_use_system_expat = false +skia_use_system_libjpeg_turbo = false +skia_use_system_libpng = false +skia_use_system_libwebp = false +skia_use_system_zlib = false +skia_use_vulkan = false +skia_use_xps = false +extra_cflags = [ "-DSKIA_C_DLL" ] +extra_cflags_cc = [ "-frtti" ] +# Standalone SkiaSharp excludes the Chromium PartitionAlloc dependency. +skia_use_partition_alloc = false diff --git a/build/linux-toolchain/skia-win.gn.in b/build/linux-toolchain/skia-win.gn.in new file mode 100644 index 0000000..5f2a89d --- /dev/null +++ b/build/linux-toolchain/skia-win.gn.in @@ -0,0 +1,28 @@ +target_os = "win" +target_cpu = "@CPU@" +is_official_build = true +is_static_skiasharp = true +is_clang = true +skia_enable_tools = false +skia_enable_ganesh = true +skia_enable_pdf = false +skia_enable_skottie = false +skia_use_dng_sdk = false +skia_use_fontconfig = false +skia_use_freetype = false +skia_use_harfbuzz = false +skia_use_icu = false +skia_use_piex = false +skia_use_sfntly = false +skia_use_system_expat = false +skia_use_system_freetype2 = false +skia_use_system_libjpeg_turbo = false +skia_use_system_libpng = false +skia_use_system_libwebp = false +skia_use_system_zlib = false +skia_use_vulkan = false +skia_use_xps = true +extra_cflags = [ "-DSKIA_C_DLL" ] +extra_cflags_cc = [ "/GR" ] +# Standalone SkiaSharp excludes the Chromium PartitionAlloc dependency. +skia_use_partition_alloc = false diff --git a/build/linux-toolchain/skia_compat.py b/build/linux-toolchain/skia_compat.py new file mode 100644 index 0000000..dd92ef7 --- /dev/null +++ b/build/linux-toolchain/skia_compat.py @@ -0,0 +1,123 @@ +"""Discover Skia GN capabilities; keep version policy out of the build driver.""" +import json +from pathlib import Path +import re +import subprocess + + +def repair_xps(source): + path = source / 'src/xps/SkXPSDevice.cpp' + header = source / 'src/xps/SkXPSDevice.h' + if not path.exists() or not header.exists(): + return 'absent' + text = path.read_text() + if not re.search(r'friend\s+HRESULT\s+subset_typeface\s*\(', header.read_text()): + return 'not-needed' + text, count = re.subn(r'(?m)^static HRESULT (subset_typeface\s*\()', r'HRESULT \1', text) + if count > 1: + raise RuntimeError('Ambiguous XPS helper declarations; inspect the new source layout') + if count: + path.write_text(text) + return 'repaired' if count else 'already-compatible' + + +def repair_skcms(source): + repaired = [] + for relative in ('third_party/skcms/skcms.cc', 'modules/skcms/skcms.cc'): + path = source / relative + if not path.is_file(): + continue + text = path.read_text() + def replace(match): + features = match[2] + if 'avx512f' in features.split(',') and 'evex512' not in features.split(','): + return match[1] + features + ',evex512' + match[3] + return match[0] + updated = re.sub(r'(#pragma clang attribute push\(__attribute__\(\(target\(")([^"]+)("\)\)\))', replace, text) + if updated != text: + path.write_text(updated) + repaired.append(relative) + # This changes only the AVX512-dispatched functions' Clang target attribute; + # the baseline CPU path and runtime dispatch remain unchanged. + return repaired + + +def configure(source, out, desired, gn, env): + # GN itself resolves imports, defaults and conditional declarations. Text + # searches alone can incorrectly report an option from an inactive branch. + # Apply the feature policy during discovery too: Skia's default tools build + # can otherwise demand test-only dependencies before we can list options. + bootstrap = dict(desired) + out.mkdir(parents=True, exist_ok=True) + def write(values): + (out / 'args.gn').write_text(''.join(f'{key} = {value}\n' for key, value in values.items())) + write(bootstrap) + subprocess.run([str(gn), 'gen', str(out.relative_to(source))], + cwd=source, env=env, check=True) + command = [str(gn), 'args', str(out.relative_to(source)), '--list', '--json'] + result = subprocess.run(command, cwd=source, env=env, capture_output=True, text=True) + if result.returncode: + raise RuntimeError('Cannot discover Skia build arguments:\n' + result.stdout + result.stderr) + # Older GN emits warnings before the JSON array for unsupported bootstrap args. + match = re.search(r'(?m)^\[\s*\{', result.stdout) + if not match: + raise RuntimeError('GN did not return an argument list:\n' + result.stdout) + arguments = json.loads(result.stdout[match.start():]) + available = {entry['name'] for entry in arguments} + values = {key: value for key, value in desired.items() if key in available} + if 'skia_enable_ganesh' in available: + values['skia_enable_ganesh'] = 'true' + elif 'skia_enable_gpu' in available: + values['skia_enable_gpu'] = 'true' + else: + raise RuntimeError('No known Skia GPU switch; refusing to silently change the graphics feature set') + required = {'target_os', 'target_cpu', 'is_static_skiasharp', 'extra_cflags', 'extra_cflags_cc'} + required |= {key for key in ('skia_use_metal', 'skia_use_xps') if desired.get(key) == 'true'} + missing = required - available + if missing: + raise RuntimeError('Skia source lacks required build capabilities: ' + ', '.join(sorted(missing))) + write(values) + subprocess.run([str(gn), 'gen', str(out.relative_to(source)), '--fail-on-unused-args'], + cwd=source, env=env, check=True) + report = {'available_arguments': sorted(available), 'selected_arguments': values, + 'omitted_obsolete_arguments': sorted(set(desired) - available)} + (out / 'skia-capabilities.json').write_text(json.dumps(report, indent=2) + '\n') + return report + + +def parse_args(text): + # Values can include multiline lists, as used by the native Linux recipe. + assignments = list(re.finditer(r'(?m)^[ \t]*([A-Za-z_]\w*)[ \t]*=', text)) + if not assignments: + raise ValueError('No GN assignments in build recipe') + return {match[1]: text[match.end():assignments[index + 1].start() + if index + 1 < len(assignments) else len(text)].strip() + for index, match in enumerate(assignments)} + + +def archive(source, name): + candidates = [source / (name + '.lib'), source / ('lib' + name + '.a'), + source / 'obj' / (name + '.lib'), source / 'obj' / ('lib' + name + '.a')] + matches = [path for path in candidates if path.is_file()] + if len(matches) != 1: + raise RuntimeError(f'Expected one archive for {name}; found {matches}') + return matches[0] + + +if __name__ == '__main__': + import argparse + import os + p = argparse.ArgumentParser(description='Configure native or cross Skia using its actual GN capabilities') + p.add_argument('--source', type=Path, required=True) + p.add_argument('--out', type=Path, required=True) + a = p.parse_args() + source, out = a.source.resolve(), a.out.resolve() + gn = source / ('bin/gn.exe' if os.name == 'nt' else 'bin/gn') + repair_xps(source) + desired = parse_args((out / 'args.gn').read_text()) + compiler = desired.get('cc', 'clang-cl' if os.name == 'nt' else 'clang').strip('"') + version = subprocess.run([compiler, '--version'], capture_output=True, text=True, check=True) + match = re.search(r'clang version (\d+)', version.stdout) + if match and int(match[1]) >= 19: + repair_skcms(source) + configure(source, out, desired, gn, os.environ.copy()) diff --git a/build/linux-toolchain/smoke-angle.c b/build/linux-toolchain/smoke-angle.c new file mode 100644 index 0000000..030edcf --- /dev/null +++ b/build/linux-toolchain/smoke-angle.c @@ -0,0 +1,3 @@ +#include +#include +int main(void) { EGLDisplay d = eglGetDisplay(EGL_DEFAULT_DISPLAY); EGLint a,b; if (!eglInitialize(d,&a,&b)) return 1; glClear(GL_COLOR_BUFFER_BIT); return eglTerminate(d) ? 0 : 2; } diff --git a/build/linux-toolchain/smoke-raster.c b/build/linux-toolchain/smoke-raster.c new file mode 100644 index 0000000..67a52c5 --- /dev/null +++ b/build/linux-toolchain/smoke-raster.c @@ -0,0 +1,31 @@ +#include +#include +#include "include/c/sk_general.h" +#include "include/c/sk_surface.h" +#include "include/c/sk_canvas.h" +#include "hb.h" +#ifndef STATICLINK_EXPECTED_MILESTONE +#error "Pass the milestone detected from this source tree" +#endif +int main(void) { + if (sk_version_get_milestone() != STATICLINK_EXPECTED_MILESTONE) return 1; + sk_imageinfo_t info = {NULL, 8, 8, RGBA_8888_SK_COLORTYPE, PREMUL_SK_ALPHATYPE}; + sk_surface_t *surface = sk_surface_new_raster(&info, 0, NULL); + if (!surface) return 2; + sk_canvas_clear(sk_surface_get_canvas(surface), 0xff336699u); + uint8_t pixels[8*8*4]; + if (!sk_surface_read_pixels(surface, &info, pixels, 8*4, 0, 0)) return 3; + for (int i=0; i<8*8; i++) { + if (pixels[i*4] != 0x33 || pixels[i*4+1] != 0x66 || pixels[i*4+2] != 0x99 || pixels[i*4+3] != 0xff) return 4; + } + sk_surface_unref(surface); + hb_buffer_t *buffer = hb_buffer_create(); + hb_buffer_add_utf8(buffer, "A\xe4\xb8\xad", 4, 0, -1); + hb_buffer_guess_segment_properties(buffer); + unsigned length = 0; + hb_glyph_info_t *glyphs = hb_buffer_get_glyph_infos(buffer, &length); + if (length != 2 || glyphs[0].codepoint != 65 || glyphs[1].codepoint != 0x4e2d) return 5; + hb_buffer_destroy(buffer); + printf("PASS Skia=%s HarfBuzz=%s raster=64 pixels UTF8=2 codepoints\n", sk_version_get_string(), hb_version_string()); + return 0; +} diff --git a/build/linux-toolchain/sources.lock.json b/build/linux-toolchain/sources.lock.json new file mode 100644 index 0000000..fb1119d --- /dev/null +++ b/build/linux-toolchain/sources.lock.json @@ -0,0 +1,7 @@ +{ + "skiasharp_version": "3.119.4", + "skiasharp_revision": "f568ac94dd768ef9a2f593537cfde2dd0d348ef5", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "7dbfc07dd33181f84e0958afb7ee805c6c769f0b" +} diff --git a/build/linux-toolchain/sync-skia-deps.py b/build/linux-toolchain/sync-skia-deps.py new file mode 100644 index 0000000..4d02767 --- /dev/null +++ b/build/linux-toolchain/sync-skia-deps.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +"""Fetch pinned native cross-build dependencies without cloning Git history.""" +import argparse +import ast +import json +import hashlib +from pathlib import Path +import re +import subprocess +import sys + +# These dependencies correspond to features explicitly disabled by this recipe. +# Unknown/new dependencies stay enabled; do not infer a skip from their names. +DISABLED = {'dng_sdk', 'icu', 'dawn', 'angle2', 'swiftshader', 'emsdk'} + + +def evaluate(node, variables): + if isinstance(node, ast.Constant): + return node.value + if isinstance(node, ast.Dict): + return {evaluate(k, variables): evaluate(v, variables) for k, v in zip(node.keys, node.values)} + if isinstance(node, (ast.List, ast.Tuple)): + return [evaluate(n, variables) for n in node.elts] + if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add): + return evaluate(node.left, variables) + evaluate(node.right, variables) + if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == 'Var' and len(node.args) == 1: + return variables[evaluate(node.args[0], variables)] + raise ValueError('Unsupported DEPS expression: ' + ast.dump(node)) + + +def dependencies(source): + values = {} + for node in ast.parse((source / 'DEPS').read_text()).body: + if isinstance(node, ast.Assign): + for name in node.targets: + if isinstance(name, ast.Name) and name.id in ('vars', 'deps'): + values[name.id] = evaluate(node.value, values.get('vars', {})) + if not values.get('deps'): + raise ValueError('No dependency map in DEPS') + return values['deps'] + + +def main(): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument('--source', type=Path, required=True) + p.add_argument('--cache', type=Path, help='Immutable dependency checkouts shared across version profiles') + p.add_argument('--no-fetch-gn', action='store_true', help='Use an existing verified GN for a local test') + a = p.parse_args() + source = a.source.resolve() + records = {} + for relative, value in dependencies(source).items(): + if not isinstance(value, str): + continue # GN/Ninja CIPD packages are handled by upstream fetch-gn. + if Path(relative).name in DISABLED: + continue + if '..' in Path(relative).parts: + raise ValueError('Dependency path escapes source: ' + relative) + link = source / relative + path = link.absolute() + if not path.is_relative_to(source): + raise ValueError('Dependency path escapes source: ' + relative) + url, revision = value.rsplit('@', 1) + if not re.fullmatch(r'[0-9a-f]{40}', revision): + raise ValueError('Dependency is not commit-pinned: ' + value) + if a.cache: + key = hashlib.sha256((url + '@' + revision).encode()).hexdigest() + cached = a.cache.resolve() / key + if link.is_symlink(): + link.unlink() + if not link.exists(): + link.parent.mkdir(parents=True, exist_ok=True) + link.symlink_to(cached, target_is_directory=True) + path = cached + if not (path / '.git').exists(): + subprocess.run(['git', 'init', '-q', str(path)], check=True) + subprocess.run(['git', '-C', str(path), 'remote', 'add', 'origin', url], check=True) + current = subprocess.run(['git', '-C', str(path), 'rev-parse', 'HEAD'], capture_output=True, text=True) + if current.stdout.strip() != revision: + subprocess.run(['git', '-C', str(path), 'remote', 'set-url', 'origin', url], check=True) + subprocess.run(['git', '-C', str(path), 'fetch', '--depth=1', 'origin', revision], check=True) + subprocess.run(['git', '-C', str(path), 'checkout', '--detach', revision], check=True) + records[relative] = revision + if not a.no_fetch_gn: + subprocess.run([sys.executable, str(source / 'bin/fetch-gn')], cwd=source, check=True) + (source / 'native-cross-deps.json').write_text(json.dumps(records, indent=2) + '\n') + + +if __name__ == '__main__': + main() diff --git a/build/linux-toolchain/versions/2.88.9.json b/build/linux-toolchain/versions/2.88.9.json new file mode 100644 index 0000000..9334c1b --- /dev/null +++ b/build/linux-toolchain/versions/2.88.9.json @@ -0,0 +1,7 @@ +{ + "skiasharp_version": "2.88.9", + "skiasharp_revision": "9699cdf046a989423a5e5eca68e7fd813486d81c", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "4bed689c9c9eb77a120c6a9d54af6a572c85d1c2" +} diff --git a/build/linux-toolchain/versions/3.119.1.json b/build/linux-toolchain/versions/3.119.1.json new file mode 100644 index 0000000..13e0e17 --- /dev/null +++ b/build/linux-toolchain/versions/3.119.1.json @@ -0,0 +1,7 @@ +{ + "skiasharp_version": "3.119.1", + "skiasharp_revision": "cc78b5933d23e6383db5d246e70db915770d55d6", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "40f75dc0051d141913c07c20d4c19590c7da0cb7" +} diff --git a/build/linux-toolchain/versions/3.119.2.json b/build/linux-toolchain/versions/3.119.2.json new file mode 100644 index 0000000..0f3fd98 --- /dev/null +++ b/build/linux-toolchain/versions/3.119.2.json @@ -0,0 +1,7 @@ +{ + "skiasharp_version": "3.119.2", + "skiasharp_revision": "2888c737ad016d584c74525e2d35db5097ea8576", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "927041a58f130e0dd0562ba86cb4170989ad39e9" +} diff --git a/build/linux-toolchain/versions/3.119.4.json b/build/linux-toolchain/versions/3.119.4.json new file mode 100644 index 0000000..fb1119d --- /dev/null +++ b/build/linux-toolchain/versions/3.119.4.json @@ -0,0 +1,7 @@ +{ + "skiasharp_version": "3.119.4", + "skiasharp_revision": "f568ac94dd768ef9a2f593537cfde2dd0d348ef5", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "7dbfc07dd33181f84e0958afb7ee805c6c769f0b" +} diff --git a/build/linux-toolchain/versions/4.150.1.json b/build/linux-toolchain/versions/4.150.1.json new file mode 100644 index 0000000..b8f762a --- /dev/null +++ b/build/linux-toolchain/versions/4.150.1.json @@ -0,0 +1,7 @@ +{ + "skiasharp_version": "4.150.1", + "skiasharp_revision": "c3e4f4c20e1f23ab74d31a8838a5bd6dc55365f2", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "0aa2d542e833ffd1d4d1b68a5152375e7f65ce11" +} diff --git a/build/linux-toolchain/versions/4.150.2.json b/build/linux-toolchain/versions/4.150.2.json new file mode 100644 index 0000000..0db7d0d --- /dev/null +++ b/build/linux-toolchain/versions/4.150.2.json @@ -0,0 +1,7 @@ +{ + "skiasharp_version": "4.150.2", + "skiasharp_revision": "fd1fdd5239f826514651a4ada5d7bb3dcec2fb43", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "b70519562e2d26d3cf56286d3a13ca25152cfae4" +} diff --git a/build/linux-toolchain/versions/4.150.3.json b/build/linux-toolchain/versions/4.150.3.json new file mode 100644 index 0000000..e2fad4d --- /dev/null +++ b/build/linux-toolchain/versions/4.150.3.json @@ -0,0 +1,7 @@ +{ + "skiasharp_version": "4.150.3", + "skiasharp_revision": "f8a404ff3375872d042404a29189ceb8e3001a41", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "eb86e4b3ee034085b62d3980b3d94629862441e6" +} diff --git a/build/linux-toolchain/versions/4.150.5.json b/build/linux-toolchain/versions/4.150.5.json new file mode 100644 index 0000000..0ef3877 --- /dev/null +++ b/build/linux-toolchain/versions/4.150.5.json @@ -0,0 +1,7 @@ +{ + "skiasharp_version": "4.150.5", + "skiasharp_revision": "16f82682f756217654eb1a152cdb639e358b8976", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "579fa81eeed6906732e27b7d95149538584dd20b" +} diff --git a/build/linux-toolchain/versions/4.153.1.json b/build/linux-toolchain/versions/4.153.1.json new file mode 100644 index 0000000..e1852a5 --- /dev/null +++ b/build/linux-toolchain/versions/4.153.1.json @@ -0,0 +1,7 @@ +{ + "skiasharp_version": "4.153.1", + "skiasharp_revision": "4783f51448f9b070dda4f87b83e941c9599e466e", + "angle_branch": "7922", + "angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66", + "skia_revision": "45afab4f1f0921f3feb97f58cf89b136fffa85e6" +} diff --git a/build/linux-toolchain/x86-thunks.py b/build/linux-toolchain/x86-thunks.py new file mode 100644 index 0000000..eda26b4 --- /dev/null +++ b/build/linux-toolchain/x86-thunks.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +"""Generate cdecl -> stdcall adapters using the pinned managed P/Invoke ABI.""" +import argparse,re,subprocess,json +from pathlib import Path +p=argparse.ArgumentParser();p.add_argument('--source',type=Path,required=True);p.add_argument('--toolchain',type=Path,required=True);p.add_argument('--libraries',type=Path,required=True);a=p.parse_args() +files=[a.source/'binding/SkiaSharp/SkiaApi.generated.cs',a.source/'binding/HarfBuzzSharp/HarfBuzzApi.generated.cs'] +text='\n'.join(x.read_text() if x.is_file() else subprocess.check_output(['git','-C',str(a.source),'show','HEAD:'+str(x.relative_to(a.source))],text=True) for x in files) +structs={} +# Generated structs use public fields; omit properties and methods. +for match in re.finditer(r'\bstruct\s+(\w+)\s*\{',text): + pos=match.end();depth=1;end=pos + while depth: + if text[end]=='{':depth+=1 + if text[end]=='}':depth-=1 + end+=1 + block=text[pos:end-1] + structs[match[1]]=[m[1].strip() for m in re.finditer(r'^\s*public\s+(.+?)\s+\w+;\s*$',block,re.M)] +sizes={} +def layout(t): + t=re.sub(r'\b(?:ref|out|in|readonly)\b','',t).strip() + if '*' in t or t.endswith(('[]','Delegate')):return 4,4 + if t in sizes:return sizes[t],4 + if t in structs:raise KeyError(t) + if t in ['Int64','UInt64','long','ulong','Double','double']:return 8,4 + if t in ['Int16','UInt16','short','ushort']:return 2,2 + if t in ['Byte','SByte','byte','sbyte','Boolean','bool']:return 1,1 + return 4,4 # enums and generated opaque pointer aliases +pending=set(structs) +while pending: + done=set() + for name in sorted(pending): + offset=0;align=1 + try: + for t in structs[name]: + n,k=layout(t);offset=(offset+k-1)//k*k+n;align=max(align,k) + except KeyError:continue + sizes[name]=(offset+align-1)//align*align;done.add(name) + if not done:raise SystemExit('Unresolved managed struct layout: '+str(pending)) + pending-=done +symbols=set() +for path in a.libraries.glob('lib*.a'): + result=subprocess.run(['nm','--defined-only',str(path)],capture_output=True,text=True,check=True) + symbols.update(re.findall(r'\bT\s+(_(?:sk|gr|hb)_\w+)\s*$',result.stdout,re.M)) +thunks={} +def parameters(s): + result=[];depth=0;start=0 + for i,c in enumerate(s): + if c in '[<(':depth+=1 + elif c in ']> )'.replace(' ',''):depth-=1 + elif c==',' and depth==0:result.append(s[start:i]);start=i+1 + result.append(s[start:]);return [v.strip() for v in result if v.strip()] +for m in re.finditer(r'internal static (?:extern|partial)\s+.+?\s+((?:sk|gr|hb)_\w+)\s*\((.*?)\);',text,re.S): + symbol='_'+m[1] + if symbol not in symbols:continue + count=0 + for param in parameters(m[2]): + param=re.sub(r'/\*.*?\*/|\[[^\]]+\]','',param).strip();typ=param.rsplit(' ',1)[0] + n,_=layout(typ);count+=(n+3)//4*4 + thunks[symbol+'@'+str(count)]=(symbol,count) +if not thunks:raise SystemExit('No x86 exported APIs matched pinned managed bindings') +assembly=['.text','.set "@feat.00", 1'] +for name,(symbol,count) in sorted(thunks.items()): + assembly+=['.globl "'+name+'"','"'+name+'":']+['pushl '+str(count)+'(%esp)']*(count//4) + assembly+=['calll '+symbol,'addl $'+str(count)+', %esp','retl $'+str(count)] +path=a.libraries/'skia_x86_stdcall_thunks.s';path.write_text('\n'.join(assembly)+'\n');obj=path.with_suffix('.o') +subprocess.run([str(a.toolchain/'cc'),'-c',str(path),'-o',str(obj)],check=True) +subprocess.run([str(a.toolchain/'ar'),'rcs',str(a.libraries/'skia_x86_stdcall_thunks.lib'),str(obj)],check=True) +(a.libraries/'x86-thunks.json').write_text(json.dumps({'count':len(thunks),'layouts':sizes,'thunks':thunks},indent=2)+'\n') +print('Generated',len(thunks),'x86 stdcall thunks') diff --git a/build/linux-toolchain/zig-cross.py b/build/linux-toolchain/zig-cross.py new file mode 100644 index 0000000..478feba --- /dev/null +++ b/build/linux-toolchain/zig-cross.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 +"""Pinned Zig compiler adapters for static-library cross-builds on Linux.""" +import argparse +import hashlib +import json +import os +from pathlib import Path +import shlex +import shutil +import subprocess +import sys +import re + +TARGETS = { + "linux-x64": "x86_64-linux-gnu.2.36.0", "linux-arm64": "aarch64-linux-gnu.2.36.0", + "linux-musl-x64": "x86_64-linux-musl", "linux-musl-arm64": "aarch64-linux-musl", + "win-x64": "x86_64-windows-gnu", "win-x86": "x86-windows-gnu", + "win-arm64": "aarch64-windows-gnu", "osx-x64": "x86_64-macos.10.13.0", + "osx-arm64": "aarch64-macos.11.0.0", +} + + +def mingw_overlay(zig_lib, output): + source = zig_lib / "libc/include/any-windows-any/dwrite_3.h" + text = source.read_text() + # Add only non-virtual overloads/default arguments. Keep IID, virtual order, + # virtual parameter types and C vtables exactly as supplied by MinGW. + for name in ("IDWritePaintReader", "IDWriteFontFace4"): + start = text.index(name + " : public") + end = text.index("#ifdef __CRT_UUID_DECL", start) + block = text[start:end] + if name == "IDWritePaintReader": + block = block.replace("UINT32 struct_size) = 0;", + "UINT32 struct_size = sizeof(DWRITE_PAINT_ELEMENT)) = 0;") + extra = """ HRESULT SetCurrentGlyph(UINT32 glyph, DWRITE_PAINT_ELEMENT *element, + D2D_RECT_F *clip, DWRITE_PAINT_ATTRIBUTES *attributes = 0) { + return SetCurrentGlyph(glyph, element, sizeof(DWRITE_PAINT_ELEMENT), clip, attributes); + } + HRESULT SetTextColor(const DWRITE_COLOR_F &color) { return SetTextColor(&color); } +""" + else: + extra = """ HRESULT GetGlyphImageFormats(UINT16 glyph, UINT32 first, UINT32 last, + DWRITE_GLYPH_IMAGE_FORMATS *formats) { + return GetGlyphImageFormats_(glyph, first, last, formats); + } +""" + index = block.rindex("};") + block = block[:index] + extra + block[index:] + text = text[:start] + block + text[end:] + output.mkdir(parents=True, exist_ok=True) + (output / "dwrite_3.h").write_text(text) + for name in ("XpsObjectModel.h", "ObjBase.h", "DispatcherQueue.h", "VersionHelpers.h", "T2EmbApi.h", "FontSub.h"): + alias = output / name + if alias.is_symlink(): + alias.unlink() + alias.symlink_to(source.parent / name.lower()) + return hashlib.sha256(source.read_bytes()).hexdigest() + + +def compiler(config, tool, incoming): + target = config["target"] + if "--version" in incoming and "linux-gnu." in target: + target = target.split("linux-gnu.")[0] + "linux-gnu" + windows = "windows" in target + args = ["-I" + config["include"]] if windows else [ + "-isysroot", config["sdk"], "-idirafter", config["sdk"] + "/usr/include", + "-F", config["sdk"] + "/System/Library/Frameworks", "-Wno-elaborated-enum-base", + ] if "macos" in target else [] + if config.get('linux_abi_headers'): + args += ['-I' + config['linux_abi_headers']] + if config.get('linux_headers'): + args += ['-I' + config['linux_headers'], '-I' + config['linux_headers'] + '/freetype2'] + if config.get('linux_sysroot') and '-c' not in incoming and '-o' in incoming: + root = Path(config['linux_sysroot']) + cpu = 'x86_64' if target.startswith('x86_64') else 'aarch64' + for path in (root / 'usr/lib' / (cpu + '-linux-gnu'), root / 'lib' / (cpu + '-linux-gnu'), root / 'usr/lib', root / 'lib'): + args += ['-L' + str(path)] + if 'linux' in target and '-c' not in incoming and '-o' in incoming: + args.append('-fuse-ld=lld') + if windows: + # Accept Windows header/Skia spellings such as __forceinline while + # retaining the GNU target ABI and leaving _MSC_VER undefined. + args.append("-fms-extensions") + if windows and config.get("mingw_headers"): + args.extend(("-idirafter", config["mingw_headers"])) + mapping = { + "/O2": "-O2", "/fp:precise": "-ffp-model=precise", "/std:c++20": "-std=c++20", + "/std:c11": "-std=c11", "/GR": "-frtti", "/GR-": "-fno-rtti", "/w": "-w", + "/Ob2": "-finline-functions", "/arch:AVX2": "-mcpu=x86_64_v3", + "/arch:AVX512": "-mcpu=x86_64_v4+evex512", "/TC": "-xc", "/TP": "-xc++", + "/Gy": "-ffunction-sections", "/Gw": "-fdata-sections", + "/Oy-": "-fno-omit-frame-pointer", "/guard:cf": "-mguard=cf", + "/W3": "-Wall", + "-march=x86-64-v3": "-mcpu=x86_64_v3", "-march=x86-64-v4": "-mcpu=x86_64_v4+evex512", + "-march=armv8-a+crc+crypto": "-mcpu=generic+crc+aes+sha2", + } + iterator = iter(incoming) + for flag in iterator: + if flag in ("-Wl,--fatal-warnings", "-Wl,--disable-new-dtags"): + continue + if flag in ("-o", "-MF", "-MT", "-MQ", "-I", "-isystem", "-isysroot", "-F", "-idirafter", "-include"): + args.extend((flag, next(iterator))) + continue + if flag in ("-target", "--target"): + next(iterator) + continue + if flag.startswith("--target="): + continue + if flag.startswith("/clang:"): + flag = flag[7:] + if flag.startswith(("-fdiagnostics-show-inlining-chain", "-fno-lifetime-dse", + "-fsanitize-ignore-for-ubsan-feature=", "-fmsc-version=")): + continue + if flag.startswith("/D"): + flag = "-D" + flag[2:] + if re.fullmatch(r"/std:(c\+\+\d+|c\d+)", flag): + flag = "-std=" + flag[5:] + if flag in ("/arch:SSE", "/arch:SSE2", "/arch:AVX"): + flag = {"/arch:SSE": "-msse", "/arch:SSE2": "-msse2", "/arch:AVX": "-mavx"}[flag] + if flag == "/W4": + args.extend(("-Wall", "-Wextra")) + continue + if flag in ("/bigobj", "/utf-8", "/Zc:inline", "/Zc:lambda", "/Zc:twoPhase", "/FS", "/MT"): + continue + if flag.startswith(("/wd", "/we")): + continue # MSVC diagnostic numbers have no GNU Clang equivalent. + if windows and flag.startswith("/") and flag not in mapping and not Path(flag).exists(): + raise SystemExit("Unsupported MSVC option: " + flag) + if flag == '-stdlib=libstdc++': + flag = '-stdlib=libc++' + args.append(mapping.get(flag, flag)) + if any(flag.startswith("-mavx512") for flag in args): + args.append("-mevex512") + if windows: + if "-DCRC32_SIMD_SSE42_PCLMUL" in args: + args.extend(("-msse4.2", "-mpclmul")) + if "-DADLER32_SIMD_SSSE3" in args: + args.append("-mssse3") + # Older MSVC GN rules express SSE dispatch through a macro: Clang needs + # matching CPU features on those units, never on ordinary baseline units. + if target.startswith(("x86", "i386")): + levels = {"SSE2": ["-msse2"], "SSE3": ["-msse3"], "SSSE3": ["-mssse3"], + "SSE41": ["-msse4.1"], "SSE42": ["-msse4.2", "-mcrc32"]} + for level, features in levels.items(): + if "-DSK_CPU_SSE_LEVEL=SK_CPU_SSE_LEVEL_" + level in args: + args.extend(features) + return [config["zig"], "cc" if tool == "cc" else "c++", "-target", target, *args] + + +def execute(config, tool, incoming): + if tool in ("cc", "cxx"): + command = compiler(config, tool, incoming) + elif tool == "ar": + flags = [x.replace("rcsT", "rcs") for x in incoming if x not in ("/WX", "/ignore:4221", "-T", "--thin")] + command = [config["zig"], "ar"] + if "macos" in config["target"]: + command.append("--format=darwin") + command += flags + else: + output = None + inputs = [] + iterator = iter(incoming) + for flag in iterator: + if flag in ("-static", "-no_warning_for_no_symbols"): + continue + if flag == "-o": + output = next(iterator) + elif flag == "-filelist": + inputs.extend(Path(next(iterator)).read_text().splitlines()) + elif flag.startswith("@"): + inputs.extend(shlex.split(Path(flag[1:]).read_text())) + else: + inputs.append(flag) + if not output: + raise SystemExit("Darwin libtool adapter needs -o") + command = [config["zig"], "ar", "--format=darwin", "rcs", output, *inputs] + if tool in ('ar', 'libtool'): + # Never leave a truncated archive behind when disk/network/job failures + # interrupt archiving. GN response files contain a complete member list. + index = next((i for i, arg in enumerate(command[2:], 2) + if not arg.startswith('@') and Path(arg).suffix in ('.a', '.lib')), None) + if index is None: + raise SystemExit('Cannot identify archive output: ' + str(command)) + dest = Path(command[index]) + temporary = dest.with_name(dest.name + '.tmp-' + str(os.getpid())) + temporary.unlink(missing_ok=True) + objects = [x for x in command[index + 1:] if not x.startswith('-')] + if tool == 'ar' and dest.exists() and len(objects) == 1 and not objects[0].startswith('@'): + shutil.copy2(dest, temporary) # Explicit runtime/availability append. + command[index] = str(temporary) + try: + subprocess.run(command, check=True) + os.replace(temporary, dest) + finally: + temporary.unlink(missing_ok=True) + return + cache = shutil.which('ccache') + if tool in ('cc', 'cxx') and cache and os.environ.get('CCACHE_DIR') and '-c' in incoming: + os.environ['CCACHE_COMPILERTYPE'] = 'clang' + raw = str(Path(config['output']) / ('_cached-' + tool)) + os.execv(cache, [cache, raw, *command[4:]]) + os.execv(config["zig"], command) + + +def prepare(args): + zig = str(Path(shutil.which(args.zig) or args.zig).resolve()) + version = subprocess.check_output([zig, "version"], text=True).strip() + if version != "0.14.1": + raise SystemExit("This adapter is verified with Zig 0.14.1; got " + version) + lib = Path(json.loads(subprocess.check_output([zig, "env"], text=True))["lib_dir"]) + output = args.output.resolve() + output.mkdir(parents=True, exist_ok=True) + config = {"zig": zig, "target": TARGETS[args.target], "version": version} + if args.target.startswith("win"): + include = output / "include" + # Regeneration is idempotent; remove only the generated SDK case alias. + alias = include / "XpsObjectModel.h" + if alias.is_symlink(): + alias.unlink() + config["mingw_header_sha256"] = mingw_overlay(lib, include) + config["include"] = str(include) + if args.mingw_headers: + config["mingw_headers"] = str(args.mingw_headers.resolve()) + header = args.mingw_headers / "windows.ui.composition.h" + if header.is_file(): + original = header.read_text() + text = original + # WIDL 11 omitted namespace qualification on these imported + # types. Use the real declarations already imported by the + # header; preserve interface UUIDs and method/vtable order. + for name in ("DirectXAlphaMode", "DirectXPixelFormat"): + text = text.replace("enum " + name, "enum ABI::Windows::Graphics::DirectX::" + name) + text = text.replace("struct Size", "struct ABI::Windows::Foundation::Size") + for name in ("Vector2", "Vector3", "Vector4", "Matrix3x2", "Matrix4x4", "Quaternion"): + text = text.replace("struct " + name, "struct ABI::Windows::Foundation::Numerics::" + name) + (include / header.name).write_text(text) + config["composition_header_sha256"] = hashlib.sha256(header.read_bytes()).hexdigest() + elif args.target.startswith("osx"): + if not args.sdk or not (args.sdk / "SDKSettings.json").is_file(): + raise SystemExit("macOS target requires --sdk pointing to a macOS SDK") + config["sdk"] = str(args.sdk.resolve()) + settings = args.sdk / "SDKSettings.json" + if json.loads(settings.read_text()).get("Version") != "15.5": + raise SystemExit("ANGLE metadata adapter is pinned to macOS SDK 15.5") + config["sdk_settings_sha256"] = hashlib.sha256(settings.read_bytes()).hexdigest() + if args.target.startswith('linux'): + config['linux_sysroot'] = str(args.linux_sysroot.resolve()) if args.linux_sysroot else '' + config['linux_headers'] = str(args.linux_headers.resolve()) if args.linux_headers else '' + config['output'] = str(output) + if args.target.startswith('linux'): + root = Path(config['linux_sysroot']) + abi = output / 'include-linux' + abi.mkdir(exist_ok=True) + cpu = 'x86_64' if config['target'].startswith('x86_64') else 'aarch64' + for name in ('ffi.h', 'ffitarget.h'): + for prefix in (root / 'usr/include' / (cpu + '-linux-gnu'), root / 'usr/include'): + src = prefix / name + if src.is_file(): + (abi / name).write_bytes(src.read_bytes()) + break + config['linux_abi_headers'] = str(abi) + pc = output / 'pkg-config' + dirs = [root / 'usr/lib' / (cpu + '-linux-gnu') / 'pkgconfig', root / 'usr/lib/pkgconfig', root / 'usr/share/pkgconfig'] + pc.write_text('#!/bin/sh\nexport PKG_CONFIG_SYSROOT_DIR=' + shlex.quote(str(root)) + '\nexport PKG_CONFIG_LIBDIR=' + shlex.quote(':'.join(map(str, dirs))) + '\nexec /usr/bin/pkg-config "$@"\n') + pc.chmod(0o755) + for kind in ('cc', 'cxx'): + raw = output / ('_cached-' + kind) + mode = 'cc' if kind == 'cc' else 'c++' + raw.write_text('#!/bin/sh\nexec ' + shlex.quote(zig) + ' ' + mode + ' -target ' + shlex.quote(config['target']) + ' "$@"\n') + raw.chmod(0o755) + descriptor = output / "toolchain.json" + descriptor.write_text(json.dumps(config, indent=2) + "\n") + for tool in ("cc", "cxx", "ar", "libtool"): + executable = output / tool + executable.write_text("#!/bin/sh\nexec python3 " + shlex.quote(str(Path(__file__).resolve())) + + " run " + shlex.quote(str(descriptor)) + " " + tool + ' "$@"\n') + executable.chmod(0o755) + print(descriptor) + + +if __name__ == "__main__": + if len(sys.argv) > 1 and sys.argv[1] == "run": + execute(json.loads(Path(sys.argv[2]).read_text()), sys.argv[3], sys.argv[4:]) + else: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--zig", default="zig") + parser.add_argument("--target", choices=TARGETS, required=True) + parser.add_argument("--sdk", type=Path) + parser.add_argument("--mingw-headers", type=Path, help="Pinned extra WinRT headers, after Zig's headers") + parser.add_argument("--linux-sysroot", type=Path) + parser.add_argument("--linux-headers", type=Path) + parser.add_argument("--output", type=Path, required=True) + prepare(parser.parse_args()) diff --git a/build/release-tracks.json b/build/release-tracks.json new file mode 100644 index 0000000..deab72a --- /dev/null +++ b/build/release-tracks.json @@ -0,0 +1,5 @@ +{ + "branches": [ + {"major": 3, "ref": "skia3", "angle_branch": "7922", "revision": "2"} + ] +} diff --git a/scripts/apply-release-version.py b/scripts/apply-release-version.py new file mode 100644 index 0000000..6d69bcf --- /dev/null +++ b/scripts/apply-release-version.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +import json, os +from pathlib import Path +import xml.etree.ElementTree as ET +lock = json.loads(os.environ["SOURCE_LOCK_JSON"]) +path = Path("build/linux-toolchain/release.lock.json") +path.write_text(json.dumps(lock, indent=2) + "\n") +project = Path("NuGet/StaticGraphics/StaticLink.Avalonia.csproj") +root = ET.parse(project) +root.find(".//Version").text = os.environ["PACKAGE_VERSION"] +for item in root.findall(".//PackageReference"): + if item.attrib.get("Include") == "SkiaSharp": + item.set("Version", "[" + lock["skiasharp_version"] + "]") +root.write(project, encoding="unicode") diff --git a/scripts/build-linux-container.sh b/scripts/build-linux-container.sh new file mode 100755 index 0000000..8863679 --- /dev/null +++ b/scripts/build-linux-container.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +rid="${RID:-linux-x64}" +case "$rid" in + linux-x64) cpu=x64; platform=linux/amd64 ;; + linux-arm64) cpu=arm64; platform=linux/arm64 ;; + *) echo "Debian container currently supports linux-x64 and linux-arm64: $rid" >&2; exit 2 ;; +esac +source_lock="$(realpath "${SOURCE_LOCK:-$root/build/linux-toolchain/sources.lock.json}")" +case "$source_lock" in + "$root/"*) container_lock="/src/${source_lock#"$root/"}" ;; + *) echo "SOURCE_LOCK must be inside the mounted repository" >&2; exit 2 ;; +esac +lock_key="$(sha256sum "$source_lock" | cut -c1-16)" +eval "$(python3 - "$source_lock" <<'PY' +import json, shlex, sys +lock = json.load(open(sys.argv[1])) +for key, value in lock.items(): + print(f'{key.upper()}={shlex.quote(value)}') +PY +)" +image="staticlink-debian12:$(sha256sum "$root/build/linux-toolchain/Dockerfile" | cut -c1-16)" +docker build --platform "$platform" -t "$image" "$root/build/linux-toolchain" +docker run --rm --platform "$platform" \ + --user "$(id -u):$(id -g)" \ + -v "$root:/src" -w /src \ + -e HOME=/tmp/staticlink-home \ + -e RID="$rid" -e TARGET_CPU="$cpu" \ + -e BUILD_JOBS="${BUILD_JOBS:-4}" \ + -e WORK_DIR="/src/External/NativeStatic/.work/native-$lock_key" \ + -e SOURCE_LOCK="$container_lock" \ + -e SKIASHARP_VERSION="$SKIASHARP_VERSION" -e SKIASHARP_REVISION="$SKIASHARP_REVISION" \ + -e ANGLE_BRANCH="$ANGLE_BRANCH" -e ANGLE_REVISION="$ANGLE_REVISION" \ + -e DEPOT_TOOLS_DIR=/src/External/NativeStatic/.work/depot_tools \ + "$image" bash -c ' + set -euo pipefail + mkdir -p "$HOME" "$(dirname "$DEPOT_TOOLS_DIR")" + # The image checkout is read-only for a non-root user; gclient needs a writable copy. + if [[ ! -d "$DEPOT_TOOLS_DIR/.git" ]]; then + cp -a /opt/depot_tools "$DEPOT_TOOLS_DIR" + fi + scripts/build-linux-static-graphics.sh "${1:-all}" + cp /opt/staticlink/packages.lock "External/NativeStatic/$RID/toolchain-packages.lock" + cp "$SOURCE_LOCK" "External/NativeStatic/$RID/sources.lock.json" + ' bash "${1:-all}" diff --git a/scripts/build-linux-static-graphics.sh b/scripts/build-linux-static-graphics.sh index 8f8efac..55e55f9 100755 --- a/scripts/build-linux-static-graphics.sh +++ b/scripts/build-linux-static-graphics.sh @@ -53,10 +53,20 @@ ensure_tools() { } ensure_depot_tools() { - local depot_dir="$WORK_DIR/depot_tools" + local depot_dir="${DEPOT_TOOLS_DIR:-$WORK_DIR/depot_tools}" local python_bin_dir python_bin_dir="$(dirname "$(command -v python3)")" - if [[ ! -d "$depot_dir/.git" ]]; then + if [[ -n "${DEPOT_TOOLS_REVISION:-}" ]]; then + if [[ ! -d "$depot_dir/.git" ]]; then + git init "$depot_dir" + git -C "$depot_dir" remote add origin https://chromium.googlesource.com/chromium/tools/depot_tools.git + fi + if [[ "$(git -C "$depot_dir" rev-parse HEAD 2>/dev/null || true)" != "$DEPOT_TOOLS_REVISION" ]]; then + git -C "$depot_dir" fetch --depth 1 origin "$DEPOT_TOOLS_REVISION" + git -C "$depot_dir" checkout --detach FETCH_HEAD + fi + export DEPOT_TOOLS_UPDATE=0 + elif [[ ! -d "$depot_dir/.git" ]]; then git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git "$depot_dir" else git -C "$depot_dir" pull --ff-only @@ -136,7 +146,14 @@ PY sync_skiasharp() { local src="$WORK_DIR/SkiaSharp-$SKIASHARP_VERSION" - if [[ ! -d "$src/.git" ]]; then + if [[ -n "${SKIASHARP_REVISION:-}" ]]; then + if [[ ! -d "$src/.git" ]]; then + git init "$src" >&2 + git -C "$src" remote add origin https://github.com/mono/SkiaSharp.git + fi + git -C "$src" fetch --depth 1 origin "$SKIASHARP_REVISION" + git -C "$src" checkout --detach FETCH_HEAD >&2 + elif [[ ! -d "$src/.git" ]]; then git clone --depth 1 --branch "release/$SKIASHARP_VERSION" https://github.com/mono/SkiaSharp.git "$src" else git -C "$src" fetch --depth 1 origin "release/$SKIASHARP_VERSION" @@ -155,7 +172,7 @@ import sys path = pathlib.Path(sys.argv[1]) text = path.read_text() -deps_path = path.with_name("DEPS") +deps_path = path.parent.parent / "DEPS" if deps_path.exists(): deps = deps_path.read_text() deps = re.sub(r'^\s*"third_party/externals/dng_sdk"\s*:\s*"[^"]+",\s*\n', '', deps, flags=re.MULTILINE) @@ -169,21 +186,21 @@ PY sync_skia_deps() { local skia_dir="$1/externals/skia" - if [[ ! -x "$skia_dir/bin/gn" ]]; then - prepare_skia_git_sync_deps "$skia_dir" + # GN can exist after a partial/failed sync; it is not a completion marker. + # git-sync-deps reuses already-correct checkouts and repairs missing dependencies. + prepare_skia_git_sync_deps "$skia_dir" - local attempt - for attempt in $(seq 1 "$SKIA_DEPS_RETRIES"); do - if python3 "$skia_dir/tools/git-sync-deps"; then - return 0 - fi - if [[ "$attempt" == "$SKIA_DEPS_RETRIES" ]]; then - return 1 - fi - echo "git-sync-deps failed; retrying ($attempt/$SKIA_DEPS_RETRIES)..." >&2 - sleep 10 - done - fi + local attempt + for attempt in $(seq 1 "$SKIA_DEPS_RETRIES"); do + if GIT_SYNC_DEPS_SKIP_EMSDK=1 python3 "$skia_dir/tools/git-sync-deps"; then + return 0 + fi + if [[ "$attempt" == "$SKIA_DEPS_RETRIES" ]]; then + return 1 + fi + echo "git-sync-deps failed; retrying ($attempt/$SKIA_DEPS_RETRIES)..." >&2 + sleep 10 + done } build_skia() { @@ -233,7 +250,7 @@ extra_cflags_cc = [ "-frtti" ] extra_ldflags = [ "-static-libstdc++", "-static-libgcc" ] EOF_ARGS - (cd "$skia_dir" && "$skia_dir/bin/gn" gen "$out_dir") + python3 "$ROOT_DIR/build/linux-toolchain/skia_compat.py" --source "$skia_dir" --out "$out_dir" ninja -C "$out_dir" -j "$BUILD_JOBS" skia SkiaSharp HarfBuzzSharp copy_first_existing "$OUTPUT_DIR/libskia.a" \ @@ -249,7 +266,14 @@ EOF_ARGS sync_angle() { local src="$WORK_DIR/ANGLE-$ANGLE_BRANCH" - if [[ ! -d "$src/.git" ]]; then + if [[ -n "${ANGLE_REVISION:-}" ]]; then + if [[ ! -d "$src/.git" ]]; then + git init "$src" >&2 + git -C "$src" remote add origin https://github.com/google/angle.git + fi + git -C "$src" fetch --depth 1 origin "$ANGLE_REVISION" + git -C "$src" checkout --detach FETCH_HEAD >&2 + elif [[ ! -d "$src/.git" ]]; then git clone --depth 1 --branch "chromium/$ANGLE_BRANCH" https://github.com/google/angle.git "$src" else git -C "$src" fetch --depth 1 origin "chromium/$ANGLE_BRANCH" @@ -544,4 +568,6 @@ main() { esac } -main "$@" +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + main "$@" +fi diff --git a/scripts/build-macos-font-preinit.sh b/scripts/build-macos-font-preinit.sh index 36abbd3..8b7788b 100644 --- a/scripts/build-macos-font-preinit.sh +++ b/scripts/build-macos-font-preinit.sh @@ -30,7 +30,9 @@ static void StaticLinkAvaloniaPreinitializeAppKitFonts(void) } OBJC -clang -arch "$ARCH" \ +compiler=("${CC:-clang}") +if [[ "${STATICLINK_ZIG_CROSS:-0}" != 1 ]]; then compiler+=(-arch "$ARCH"); fi +"${compiler[@]}" \ -mmacosx-version-min=12.0 \ -fobjc-arc \ -c "$src" \ diff --git a/scripts/build-macos-static-graphics.sh b/scripts/build-macos-static-graphics.sh index 12a6d82..c3fe3f2 100755 --- a/scripts/build-macos-static-graphics.sh +++ b/scripts/build-macos-static-graphics.sh @@ -33,8 +33,11 @@ ensure_depot_tools() { if [[ ! -d "$depot_dir/.git" ]]; then git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git "$depot_dir" else - git -C "$depot_dir" pull --ff-only + : # The pinned checkout is refreshed below fi + git -C "$depot_dir" fetch --depth 1 origin 8a5434051036b32412a2ecb10c213a72e3f3ccb9 + git -C "$depot_dir" checkout -q --force FETCH_HEAD + export DEPOT_TOOLS_UPDATE=0 export PATH="$depot_dir:$PATH" } @@ -56,11 +59,15 @@ copy_first_existing() { sync_skiasharp() { local src="$WORK_DIR/SkiaSharp-$SKIASHARP_VERSION" if [[ ! -d "$src/.git" ]]; then - git clone --depth 1 --branch "release/$SKIASHARP_VERSION" https://github.com/mono/SkiaSharp.git "$src" + git clone --depth 1 --branch "v$SKIASHARP_VERSION" https://github.com/mono/SkiaSharp.git "$src" else - git -C "$src" fetch --depth 1 origin "release/$SKIASHARP_VERSION" + git -C "$src" fetch --depth 1 origin "${SKIASHARP_REVISION:-refs/tags/v$SKIASHARP_VERSION}" git -C "$src" checkout -q FETCH_HEAD fi + if [[ -n "${SKIASHARP_REVISION:-}" ]]; then + git -C "$src" fetch --depth 1 origin "$SKIASHARP_REVISION" + git -C "$src" checkout -q --force FETCH_HEAD + fi git -C "$src" submodule update --init --depth 1 externals/skia >&2 echo "$src" } @@ -74,7 +81,7 @@ import sys path = pathlib.Path(sys.argv[1]) text = path.read_text() -deps_path = path.with_name("DEPS") +deps_path = path.parent.parent / "DEPS" if deps_path.exists(): deps = deps_path.read_text() deps = re.sub(r'^\s*"third_party/externals/dng_sdk"\s*:\s*"[^"]+",\s*\n', '', deps, flags=re.MULTILINE) @@ -147,7 +154,7 @@ extra_cflags = [ "-DSKIA_C_DLL" ] extra_cflags_cc = [ "-frtti" ] EOF_ARGS - (cd "$skia_dir" && "$skia_dir/bin/gn" gen "$out_dir") + python3 "$ROOT_DIR/build/linux-toolchain/skia_compat.py" --source "$skia_dir" --out "$out_dir" ninja -C "$out_dir" -j "$BUILD_JOBS" skia SkiaSharp HarfBuzzSharp copy_first_existing "$OUTPUT_DIR/libskia.a" "$out_dir/libskia.a" "$out_dir/obj/libskia.a" copy_first_existing "$OUTPUT_DIR/libSkiaSharp.a" "$out_dir/libSkiaSharp.a" "$out_dir/obj/libSkiaSharp.a" @@ -162,6 +169,10 @@ sync_angle() { git -C "$src" fetch --depth 1 origin "chromium/$ANGLE_BRANCH" git -C "$src" checkout -q FETCH_HEAD fi + if [[ -n "${ANGLE_REVISION:-}" ]]; then + git -C "$src" fetch --depth 1 origin "$ANGLE_REVISION" + git -C "$src" checkout -q --force FETCH_HEAD + fi echo "$src" } diff --git a/scripts/build-windows-static-graphics.ps1 b/scripts/build-windows-static-graphics.ps1 index 96c557b..482ba78 100644 --- a/scripts/build-windows-static-graphics.ps1 +++ b/scripts/build-windows-static-graphics.ps1 @@ -56,8 +56,13 @@ function Ensure-DepotTools { if (-not (Test-Path (Join-Path $depotDir ".git"))) { $null = git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git $depotDir } else { - $null = git -C $depotDir pull --ff-only + # Refresh the pinned checkout below. } + $null = git -C $depotDir fetch --depth 1 origin 8a5434051036b32412a2ecb10c213a72e3f3ccb9 + if ($LASTEXITCODE -ne 0) { throw "Cannot fetch pinned depot_tools" } + $null = git -C $depotDir checkout -q --force FETCH_HEAD + if ($LASTEXITCODE -ne 0) { throw "Cannot checkout pinned depot_tools" } + $env:DEPOT_TOOLS_UPDATE = "0" $env:PATH = "$depotDir;$env:PATH" } @@ -344,11 +349,17 @@ function New-WinX86SkiaStdcallThunks([string[]]$InputLibraries, [string[]]$Bindi function Sync-SkiaSharp { $src = Join-Path $WorkDir "SkiaSharp-$SkiaSharpVersion" if (-not (Test-Path (Join-Path $src ".git"))) { - $null = git -c core.longpaths=true clone --depth 1 --branch "release/$SkiaSharpVersion" https://github.com/mono/SkiaSharp.git $src + $null = git -c core.longpaths=true clone --depth 1 --branch "v$SkiaSharpVersion" https://github.com/mono/SkiaSharp.git $src } else { - $null = git -C $src fetch --depth 1 origin "release/$SkiaSharpVersion" + $null = git -C $src fetch --depth 1 origin "refs/tags/v$SkiaSharpVersion" $null = git -C $src checkout -q FETCH_HEAD } + if ($env:SKIASHARP_REVISION) { + $null = git -C $src fetch --depth 1 origin $env:SKIASHARP_REVISION + if ($LASTEXITCODE -ne 0) { throw "Cannot fetch locked SkiaSharp commit" } + $null = git -C $src checkout -q --force FETCH_HEAD + if ($LASTEXITCODE -ne 0) { throw "Cannot checkout locked SkiaSharp commit" } + } $null = git -C $src submodule update --init --depth 1 externals/skia return $src } @@ -359,7 +370,9 @@ function Patch-WinX86SkiaLinker($SkiaDir) { } $linkerPath = Join-Path $SkiaDir "src\c\sk_linker.cpp" + if (-not (Test-Path $linkerPath)) { return } $text = Get-Content -Path $linkerPath -Raw + if ($text -notmatch 'skjson::ObjectValue') { return } $pattern = '(?m)^ skjson::ObjectValue\* a = nullptr;\r?\n auto r = \(\*a\)\["tmp"\]\.getType\(\);\r?$' $patched = [regex]::Replace($text, $pattern, ' int r = 0;') if ($patched -eq $text) { @@ -447,7 +460,8 @@ extra_cflags_cc = [ "/GR" ] Push-Location $skiaDir try { - & (Join-Path $skiaDir "bin\gn.exe") gen $outDir + python (Join-Path $RootDir "build\linux-toolchain\skia_compat.py") --source $skiaDir --out $outDir + if ($LASTEXITCODE -ne 0) { throw "Skia capability configuration failed" } ninja -C $outDir -j $BuildJobs skia SkiaSharp HarfBuzzSharp } finally { Pop-Location @@ -480,6 +494,12 @@ function Sync-Angle { $null = git -C $src fetch --depth 1 origin "chromium/$AngleBranch" $null = git -C $src checkout -q FETCH_HEAD } + if ($env:ANGLE_REVISION) { + $null = git -C $src fetch --depth 1 origin $env:ANGLE_REVISION + if ($LASTEXITCODE -ne 0) { throw "Cannot fetch locked ANGLE commit" } + $null = git -C $src checkout -q --force FETCH_HEAD + if ($LASTEXITCODE -ne 0) { throw "Cannot checkout locked ANGLE commit" } + } return $src } diff --git a/scripts/check-upstream-releases.py b/scripts/check-upstream-releases.py new file mode 100644 index 0000000..32f51ba --- /dev/null +++ b/scripts/check-upstream-releases.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Plan stable releases. HTTP/network errors fail closed; only 404 means absent.""" +import argparse, json, re, urllib.request, urllib.error +from pathlib import Path + +def get(url): + for attempt in range(3): + try: + with urllib.request.urlopen(url, timeout=45) as response: + return json.load(response) + except urllib.error.HTTPError as error: + if error.code == 404: + return None + if error.code < 500 and error.code != 429: + raise + if attempt == 2: + raise + except (OSError, TimeoutError): + if attempt == 2: + raise + raise RuntimeError("Unreachable") + +def plan(config, upstream, published, force=False): + result = [] + for entry in config["branches"]: + major = entry["major"] + candidates = [v for v in upstream if re.fullmatch(r"\d+\.\d+\.\d+", v) + and int(v.split(".")[0]) == major] + version = entry.get("fixed_version") or max(candidates, key=lambda v: tuple(map(int, v.split(".")))) + package = f"{version}-{entry['angle_branch']}.{entry['revision']}" + if entry.get("daily", True) and (force or package.lower() not in {v.lower() for v in published}): + result.append({"ref": entry["ref"], "skiasharp_version": version, "package_version": package}) + return result + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--config", default="build/release-tracks.json") + parser.add_argument("--output", default="release-plan.json") + parser.add_argument("--force", action="store_true") + args = parser.parse_args() + upstream = get("https://api.nuget.org/v3-flatcontainer/skiasharp/index.json") + published = get("https://api.nuget.org/v3-flatcontainer/staticlink.avalonia/index.json") + if upstream is None: + raise SystemExit("SkiaSharp package index is missing") + result = plan(json.loads(Path(args.config).read_text()), upstream["versions"], (published or {}).get("versions", []), force=args.force) + Path(args.output).write_text(json.dumps(result, indent=2) + "\n") + print(json.dumps(result)) diff --git a/scripts/prepare-cross-sources.sh b/scripts/prepare-cross-sources.sh new file mode 100755 index 0000000..74b2e89 --- /dev/null +++ b/scripts/prepare-cross-sources.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail +SCRIPT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SOURCE_LOCK="${SOURCE_LOCK:-$SCRIPT_ROOT/build/linux-toolchain/sources.lock.json}" +SOURCE_LOCK="$(realpath "$SOURCE_LOCK")" +export WORK_DIR="${WORK_DIR:-$SCRIPT_ROOT/External/NativeStatic/.work/zig-sources}" +export LLVM_AR="${LLVM_AR:-llvm-ar}" +mkdir -p "$WORK_DIR" +read -r SKIASHARP_VERSION SKIASHARP_REVISION ANGLE_BRANCH ANGLE_REVISION < <( + python3 - "$SOURCE_LOCK" <<'PY' +import json, sys +lock = json.load(open(sys.argv[1])) +print(lock['skiasharp_version'], lock['skiasharp_revision'], lock['angle_branch'], lock['angle_revision']) +PY +) +export SKIASHARP_VERSION SKIASHARP_REVISION ANGLE_BRANCH ANGLE_REVISION +export WORK_DIR="$WORK_DIR/$SKIASHARP_VERSION-$(sha256sum "$SOURCE_LOCK" | cut -c1-16)" +mkdir -p "$WORK_DIR" +source "$SCRIPT_ROOT/scripts/build-linux-static-graphics.sh" +if [[ ! -d "$WORK_DIR/depot_tools/.git" ]]; then + cp -a /opt/depot_tools "$WORK_DIR/depot_tools" +fi +export DEPOT_TOOLS_DIR="$WORK_DIR/depot_tools" +ensure_tools +ensure_depot_tools +initialize_depot_tools_system_python "$DEPOT_TOOLS_DIR" +patch_depot_tools_python_deps "$DEPOT_TOOLS_DIR" +export VPYTHON_BYPASS="manually managed python not supported by chrome operations" +skia_sharp="$(sync_skiasharp)" +python3 "$SCRIPT_ROOT/build/linux-toolchain/sync-skia-deps.py" --source "$skia_sharp/externals/skia" --cache "$SCRIPT_ROOT/External/NativeStatic/.work/skia-deps" +angle="$(sync_angle)" +project_patch="$SCRIPT_ROOT/External/NativeStatic/patches/angle-chromium-$ANGLE_BRANCH.patch" +if git -C "$angle" apply --reverse --check "$project_patch" >/dev/null 2>&1; then + : +else + git -C "$angle" apply --check "$project_patch" + git -C "$angle" apply "$project_patch" +fi +python3 - "$angle/.gclient" <<'PY' +from pathlib import Path +import sys +Path(sys.argv[1]).write_text('''solutions = [{ + "name": ".", "url": "https://chromium.googlesource.com/angle/angle.git", + "deps_file": "DEPS", "managed": False, + "custom_deps": {"third_party/llvm-build/Release+Asserts": None}, + "custom_vars": {"checkout_angle_cl_deps": False, "checkout_angle_dawn_deps": False}, +}] +target_os = ["linux", "win", "mac"] +''') +PY +# Zig and the image's SDKs supply the compiler/runtime; skip native Xcode/MSVC +# setup hooks while retaining the pinned source dependencies for every target. +(cd "$angle" && gclient sync --nohooks --force) +python3 - "$WORK_DIR/cross-sources.json" "$skia_sharp/externals/skia" "$angle" "$SOURCE_LOCK" <<'PY' +import json, sys +from pathlib import Path +Path(sys.argv[1]).write_text(json.dumps({'skia': sys.argv[2], 'angle': sys.argv[3], + 'angle_gn': sys.argv[3] + '/buildtools/linux64/gn', 'source_lock': sys.argv[4]}, indent=2) + '\n') +PY +echo "Prepared $WORK_DIR/cross-sources.json" diff --git a/scripts/resolve-release-lock.py b/scripts/resolve-release-lock.py new file mode 100644 index 0000000..613007e --- /dev/null +++ b/scripts/resolve-release-lock.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Resolve a published SkiaSharp tag and its Skia gitlink to immutable commits.""" +import argparse, json, re, subprocess, tempfile +from pathlib import Path +import xml.etree.ElementTree as ET + +def git(*args): + return subprocess.check_output(["git", *args], text=True).strip() + +p = argparse.ArgumentParser() +p.add_argument("--version", default="") +p.add_argument("--output", default="build/linux-toolchain/release.lock.json") +a = p.parse_args() +root = ET.parse("NuGet/StaticGraphics/StaticLink.Avalonia.csproj").getroot() +current = next(x.attrib["Version"] for x in root.findall(".//PackageReference") if x.attrib.get("Include") == "SkiaSharp") +version = a.version or current +if not re.fullmatch(r"\d+\.\d+\.\d+", version) or version.split(".")[0] != current.split(".")[0]: + raise SystemExit("Only stable versions from this branch's SkiaSharp major are allowed") +if current.startswith("2.") and version != "2.88.9": + raise SystemExit("SkiaSharp 2 is frozen at 2.88.9") +base, revision = root.findtext(".//Version").rsplit(".", 1) +angle = base.split("-", 1)[1] +# ANGLE remains fixed: changing SkiaSharp must not silently change its toolchain. +profile = json.loads(Path("build/linux-toolchain/sources.lock.json").read_text()) +if profile["angle_branch"] != angle: + raise SystemExit("ANGLE branch does not match the source lock") +remote = "https://github.com/mono/SkiaSharp.git" +refs = git("ls-remote", "--tags", remote, f"refs/tags/v{version}", f"refs/tags/v{version}^{{}}") +if not refs: + raise SystemExit(f"No immutable upstream release tag for {version}; refusing a moving branch") +lines = refs.splitlines() +commit = next((x.split()[0] for x in lines if x.endswith("^{}")), lines[0].split()[0]) +with tempfile.TemporaryDirectory() as tmp: + git("init", "-q", tmp) + git("-C", tmp, "fetch", "-q", "--depth=1", remote, commit) + tree = git("-C", tmp, "ls-tree", "FETCH_HEAD", "externals/skia") + mode, kind, skia, name = tree.split() + if mode != "160000" or kind != "commit": + raise SystemExit("Skia source is not a pinned git submodule") +profile.update(skiasharp_version=version, skiasharp_revision=commit, skia_revision=skia) +Path(a.output).write_text(json.dumps(profile, indent=2) + "\n") +package = f"{version}-{angle}.{revision}" +print(json.dumps({"skiasharp_version": version, "package_version": package, "lock": profile}))