diff --git a/.github/workflows/nuget-avalonia-native.yml b/.github/workflows/nuget-avalonia-native.yml index 03a6173..cf12bf7 100644 --- a/.github/workflows/nuget-avalonia-native.yml +++ b/.github/workflows/nuget-avalonia-native.yml @@ -1,8 +1,17 @@ name: staticlink-avalonia-native on: - schedule: - - cron: "17 3 * * *" + workflow_call: + inputs: + avalonia_version: + type: string + default: '' + force_build: + type: boolean + default: false + publish_to_nuget: + type: boolean + default: false workflow_dispatch: inputs: avalonia_version: @@ -17,7 +26,7 @@ on: publish_to_nuget: description: Publish with NuGet Trusted Publishing after smoke tests required: true - default: true + default: false type: boolean jobs: @@ -58,9 +67,14 @@ jobs: exit 1 fi - package_version="$avalonia_version.1" + package_version="$avalonia_version.2" 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 + status=$(curl --retry 3 --silent --show-error --location --output /dev/null --write-out '%{http_code}' "$package_url") + if [ "$status" != 200 ] && [ "$status" != 404 ]; then + echo "NuGet availability check failed: HTTP $status" >&2 + exit 1 + fi + if [ "$FORCE_BUILD" != "true" ] && [ "$status" = 200 ]; then echo "Already published: $package_version" continue fi @@ -79,9 +93,17 @@ jobs: echo 'should_build=true' >> "$GITHUB_OUTPUT" fi - build: + toolchain: needs: detect if: ${{ needs.detect.outputs.should_build == 'true' }} + permissions: + contents: read + packages: write + uses: ./.github/workflows/zig-toolchain-image.yml + + build: + needs: [detect, toolchain] + if: ${{ needs.detect.outputs.should_build == 'true' }} strategy: fail-fast: false matrix: @@ -92,10 +114,12 @@ jobs: runner: macos-14 - rid: osx-arm64 runner: macos-15 - runs-on: ${{ matrix.runner }} + runs-on: ubuntu-24.04 permissions: contents: read + packages: read env: + TOOLCHAIN_IMAGE: ${{ needs.toolchain.outputs.image }} RID: ${{ matrix.rid }} AVALONIA_VERSION: ${{ matrix.version.avalonia_version }} WORK_DIR: ${{ github.workspace }}/External/AvaloniaNative/.work @@ -103,18 +127,32 @@ jobs: 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: 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: 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: Build static AvaloniaNative and font preinit with Zig + run: | + mkdir -p "$WORK_DIR" "$OUTPUT_DIR" + docker run --rm --user "$(id -u):$(id -g)" \ + -e HOME=/tmp/staticlink-home -e RID -e AVALONIA_VERSION \ + -v "$PWD:/src" -w /src --entrypoint bash "$TOOLCHAIN_IMAGE" -c ' + set -euo pipefail + mkdir -p "$HOME" + src="/src/External/AvaloniaNative/.work/Avalonia-$AVALONIA_VERSION" + git clone --depth 1 --branch "$AVALONIA_VERSION" https://github.com/AvaloniaUI/Avalonia.git "$src" + if git -C "$src" config -f .gitmodules --get-regexp path | awk '{print $2}' | grep -qx external/Numerge; then + git -C "$src" submodule update --init --depth 1 external/Numerge + fi + python3 build/linux-toolchain/build-avalonia-native-cross.py \ + --source "$src" --rid "$RID" --sdk /opt/macos-sdk/MacOSX15.5.sdk \ + --work "/src/External/AvaloniaNative/.work/zig-$AVALONIA_VERSION-$RID" \ + --output "/src/External/AvaloniaNative/$RID/native" + ' - name: Upload static AvaloniaNative uses: actions/upload-artifact@v4 @@ -123,6 +161,7 @@ jobs: path: | External/AvaloniaNative/${{ matrix.rid }}/native/libAvaloniaNative.a External/AvaloniaNative/${{ matrix.rid }}/native/macos_font_preinit.o + External/AvaloniaNative/${{ matrix.rid }}/native/avalonia-native-build.json if-no-files-found: error pack: @@ -260,11 +299,19 @@ jobs: sys.exit(proc.returncode if proc.returncode > 0 else 1) PY + - name: Upload AvaloniaNative smoke app + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: avalonia-native-smoke-${{ matrix.version.avalonia_version }}-${{ matrix.rid }} + path: artifacts/smoke-${{ matrix.rid }}/** + if-no-files-found: warn + publish: needs: - detect - smoke - if: ${{ needs.detect.outputs.should_build == 'true' && (github.event_name == 'schedule' || inputs.publish_to_nuget == true) }} + if: ${{ needs.detect.outputs.should_build == 'true' && inputs.publish_to_nuget == true }} strategy: matrix: version: ${{ fromJSON(needs.detect.outputs.versions) }} @@ -279,20 +326,32 @@ jobs: name: staticlink-avalonia-native-${{ matrix.version.package_version }} path: artifacts/nuget + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.302" + - name: Validate Trusted Publishing configuration env: NUGET_USER: ${{ vars.NUGET_USER }} - run: test -n "$NUGET_USER" + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: | + if [ -z "$NUGET_USER" ] && [ -z "$NUGET_API_KEY" ]; then + echo 'Configure NUGET_USER with Trusted Publishing or NUGET_API_KEY.' >&2 + exit 1 + fi - name: NuGet login + if: ${{ vars.NUGET_USER != '' }} id: login uses: NuGet/login@v1 with: user: ${{ vars.NUGET_USER }} - name: Publish to nuget.org + env: + NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY || secrets.NUGET_API_KEY }} run: | dotnet nuget push artifacts/nuget/*.nupkg \ --source https://www.nuget.org/api/v2/package \ - --api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \ + --api-key "$NUGET_API_KEY" \ --skip-duplicate diff --git a/.github/workflows/nuget-static-graphics-repack.yml b/.github/workflows/nuget-static-graphics-repack.yml deleted file mode 100644 index 52174e9..0000000 --- a/.github/workflows/nuget-static-graphics-repack.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: staticlink-avalonia-repack - -on: - workflow_dispatch: - inputs: - source_version: - description: Existing package version containing the static libraries - required: true - default: 4.150.1-7922.1 - type: string - publish_to_nuget: - description: Publish package to nuget.org - required: true - default: false - type: boolean - -jobs: - repack: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "10.0.x" - - - name: Extract static libraries from existing package - env: - SOURCE_VERSION: ${{ inputs.source_version }} - run: | - python3 - <<'PY' - import os - import pathlib - import urllib.request - import zipfile - - version = os.environ["SOURCE_VERSION"] - package = pathlib.Path("artifacts/source.nupkg") - package.parent.mkdir(parents=True, exist_ok=True) - urllib.request.urlretrieve( - f"https://api.nuget.org/v3-flatcontainer/staticlink.avalonia/{version}/staticlink.avalonia.{version}.nupkg", - package, - ) - - output = pathlib.Path("External/NativeStatic") - with zipfile.ZipFile(package) as archive: - for entry in archive.infolist(): - name = entry.filename.replace("\\", "/") - if not name.startswith("static/") or name.endswith("/"): - continue - destination = output / name.removeprefix("static/") - destination.parent.mkdir(parents=True, exist_ok=True) - destination.write_bytes(archive.read(entry)) - PY - - - name: Verify static archives - run: | - for rid in linux-x64 linux-arm64 linux-musl-x64 linux-musl-arm64 osx-x64 osx-arm64; do - test -f External/NativeStatic/$rid/native/libskia.a - test -f External/NativeStatic/$rid/native/libSkiaSharp.a - test -f External/NativeStatic/$rid/native/libHarfBuzzSharp.a - test -f External/NativeStatic/$rid/native/libANGLE_static.a - test -f External/NativeStatic/$rid/native/libGLESv2_static.a - done - - for rid in win-x64 win-x86 win-arm64; do - test -f External/NativeStatic/$rid/native/skia.lib - test -f External/NativeStatic/$rid/native/SkiaSharp.lib - test -f External/NativeStatic/$rid/native/libHarfBuzzSharp.lib - test -f External/NativeStatic/$rid/native/libANGLE_static.lib - test -f External/NativeStatic/$rid/native/libGLESv2_static.lib - done - 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 - - - name: Verify portable NuGet paths - run: | - package=$(find artifacts/nuget -name 'StaticLink.Avalonia.*.nupkg' ! -name 'StaticLink.Avalonia.Native.*.nupkg' -print -quit) - test -n "$package" - if unzip -Z1 "$package" | grep -F '\'; then - echo "NuGet package contains Windows-only backslash paths." - exit 1 - fi - unzip -Z1 "$package" | grep -Fx 'buildTransitive/StaticLink.Avalonia.props' - unzip -Z1 "$package" | grep -Fx 'buildTransitive/StaticLink.Avalonia.targets' - - - name: Upload NuGet artifact - uses: actions/upload-artifact@v4 - with: - name: staticlink-avalonia-repacked-nuget - path: artifacts/nuget/*.nupkg - if-no-files-found: error - - - name: Publish to nuget.org - if: ${{ inputs.publish_to_nuget == true }} - env: - NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} - run: | - test -n "$NUGET_API_KEY" - dotnet nuget push artifacts/nuget/*.nupkg \ - --source "https://api.nuget.org/v3/index.json" \ - --api-key "$NUGET_API_KEY" \ - --skip-duplicate diff --git a/.github/workflows/nuget-static-graphics.yml b/.github/workflows/nuget-static-graphics.yml deleted file mode 100644 index 980cb65..0000000 --- a/.github/workflows/nuget-static-graphics.yml +++ /dev/null @@ -1,547 +0,0 @@ -name: staticlink-avalonia - -on: - workflow_dispatch: - inputs: - publish_to_nuget: - description: Publish package to nuget.org - required: true - default: false - type: boolean - publish_to_release: - description: Publish NuGet package and smoke apps to GitHub Release - required: true - default: true - type: boolean - push: - tags: - - "staticlink-avalonia-v*" - -env: - DOTNET_ALPINE_IMAGE: mcr.microsoft.com/dotnet/sdk:10.0.302-alpine3.23 - -jobs: - preflight: - uses: ./.github/workflows/static-graphics-preflight.yml - - build-linux: - needs: preflight - uses: ./.github/workflows/static-graphics-linux.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 }} - - pack: - runs-on: ubuntu-24.04 - needs: - - build-linux - - build-musl - - build-windows - - build-macos - permissions: - contents: read - steps: - - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "10.0.302" - - - name: Download static archives - uses: actions/download-artifact@v4 - with: - pattern: native-static-* - path: External/NativeStatic - merge-multiple: false - - - name: Move static archives into RID directories - run: | - for rid in win-x64 win-x86 win-arm64 linux-x64 linux-arm64 linux-musl-x64 linux-musl-arm64 osx-x64 osx-arm64; do - mkdir -p "External/NativeStatic/$rid" - mv "External/NativeStatic/native-static-$rid"/* "External/NativeStatic/$rid/" - rmdir "External/NativeStatic/native-static-$rid" - if [ ! -d "External/NativeStatic/$rid/native" ]; then - mkdir -p "External/NativeStatic/$rid/native" - find "External/NativeStatic/$rid" -maxdepth 1 -type f -exec mv {} "External/NativeStatic/$rid/native/" \; - fi - done - - - name: Verify static archives - run: | - for rid in linux-x64 linux-arm64 linux-musl-x64 linux-musl-arm64 osx-x64 osx-arm64; do - test -f External/NativeStatic/$rid/native/libskia.a - test -f External/NativeStatic/$rid/native/libSkiaSharp.a - test -f External/NativeStatic/$rid/native/libHarfBuzzSharp.a - test -f External/NativeStatic/$rid/native/libANGLE_static.a - test -f External/NativeStatic/$rid/native/libGLESv2_static.a - done - - for rid in win-x64 win-x86 win-arm64; do - test -f External/NativeStatic/$rid/native/skia.lib - test -f External/NativeStatic/$rid/native/SkiaSharp.lib - test -f External/NativeStatic/$rid/native/libHarfBuzzSharp.lib - test -f External/NativeStatic/$rid/native/libANGLE_static.lib - test -f External/NativeStatic/$rid/native/libGLESv2_static.lib - done - 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 - - - name: Verify portable NuGet paths - shell: bash - run: | - package=$(find artifacts/nuget -name 'StaticLink.Avalonia.*.nupkg' ! -name 'StaticLink.Avalonia.Native.*.nupkg' -print -quit) - test -n "$package" - if unzip -Z1 "$package" | grep -F '\'; then - echo "NuGet package contains Windows-only backslash paths." - exit 1 - fi - unzip -Z1 "$package" | grep -Fx 'buildTransitive/StaticLink.Avalonia.props' - unzip -Z1 "$package" | grep -Fx 'buildTransitive/StaticLink.Avalonia.targets' - - - name: Upload NuGet artifact - uses: actions/upload-artifact@v4 - with: - name: staticlink-avalonia-nuget - path: | - artifacts/nuget/*.nupkg - artifacts/nuget/*.snupkg - if-no-files-found: error - - smoke-avalonia: - needs: pack - strategy: - fail-fast: false - matrix: - include: - - rid: win-x64 - runner: windows-2022 - msvc_arch: x64 - - rid: win-x86 - runner: windows-2022 - msvc_arch: x86 - - rid: win-arm64 - runner: windows-2022 - msvc_arch: amd64_arm64 - - rid: linux-x64 - runner: ubuntu-24.04 - - rid: linux-arm64 - runner: ubuntu-24.04-arm - - rid: linux-musl-x64 - runner: ubuntu-24.04 - - rid: linux-musl-arm64 - runner: ubuntu-24.04-arm - - rid: osx-x64 - runner: macos-15 - avalonia_version: 11.3.14 - - rid: osx-arm64 - runner: macos-15 - avalonia_version: 11.3.14 - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v4 - - - name: Setup MSVC environment - if: ${{ startsWith(matrix.rid, 'win-') }} - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: ${{ matrix.msvc_arch }} - - - 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 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "10.0.302" - - - name: Download NuGet package - uses: actions/download-artifact@v4 - with: - name: staticlink-avalonia-nuget - path: artifacts/nuget - - - name: Publish Avalonia single-file smoke app - if: ${{ !startsWith(matrix.rid, 'linux-musl-') }} - shell: pwsh - run: | - $ErrorActionPreference = "Stop" - $package = Get-ChildItem -Path "artifacts/nuget" -Filter "StaticLink.Avalonia.*.nupkg" | Where-Object { $_.Name -notlike "StaticLink.Avalonia.Native.*" } | Select-Object -First 1 - if (-not $package) { throw "StaticLink NuGet package was not downloaded." } - if ($package.BaseName -notmatch '^StaticLink\.Avalonia\.(.+)$') { throw "Unexpected package name: $($package.Name)" } - $version = $Matches[1] - $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 - dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") ` - -c Release ` - -r "${{ matrix.rid }}" ` - --configfile (Join-Path $projectDir "NuGet.config") ` - /p:PublishAot=true ` - /p:SelfContained=true ` - /p:PublishSingleFile=true ` - /p:StripSymbols=true ` - /p:PublishDir="$PWD/artifacts/smoke-${{ matrix.rid }}/" - - - name: Publish Avalonia musl smoke app - if: ${{ startsWith(matrix.rid, 'linux-musl-') }} - shell: pwsh - run: | - $ErrorActionPreference = "Stop" - $package = Get-ChildItem -Path "artifacts/nuget" -Filter "StaticLink.Avalonia.*.nupkg" | Where-Object { $_.Name -notlike "StaticLink.Avalonia.Native.*" } | Select-Object -First 1 - if (-not $package) { throw "StaticLink NuGet package was not downloaded." } - if ($package.BaseName -notmatch '^StaticLink\.Avalonia\.(.+)$') { throw "Unexpected package name: $($package.Name)" } - $version = $Matches[1] - $projectDir = Join-Path $PWD "artifacts/avalonia-smoke-src" - pwsh ./scripts/create-avalonia-smoke.ps1 -ProjectDir $projectDir -NuGetSource "/workspace/artifacts/nuget" -StaticLinkVersion $version -AvaloniaVersion "11.3.14" - docker run --rm ` - -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 }}/" - - - name: Verify no dynamic graphics libraries remain - shell: pwsh - run: | - $publishDir = "artifacts/smoke-${{ matrix.rid }}" - $blocked = Get-ChildItem -Path $publishDir -Recurse -File | Where-Object { - $_.Name -in @("libSkiaSharp.dll", "libHarfBuzzSharp.dll", "av_libglesv2.dll", "libSkiaSharp.so", "libHarfBuzzSharp.so", "libSkiaSharp.dylib", "libHarfBuzzSharp.dylib") - } - if ($blocked) { - $blocked | ForEach-Object { Write-Error "Unexpected dynamic graphics library: $($_.FullName)" } - exit 1 - } - Get-ChildItem -Path $publishDir -Recurse -File | ForEach-Object { $_.FullName } - - - name: Verify Windows executable architecture - if: ${{ startsWith(matrix.rid, 'win-') }} - shell: pwsh - run: | - $ErrorActionPreference = "Stop" - $app = Join-Path $PWD "artifacts/smoke-${{ matrix.rid }}/AvaloniaStaticLinkSmoke.exe" - $expectedMachine = @{ - "win-x64" = "8664 machine" - "win-x86" = "14C machine" - "win-arm64" = "AA64 machine" - }["${{ matrix.rid }}"] - $headers = & dumpbin /headers $app | Out-String - if ($LASTEXITCODE -ne 0 -or $headers -notmatch [regex]::Escape($expectedMachine)) { - throw "Expected $expectedMachine in PE headers for $app.`n$headers" - } - - - name: Run Windows Avalonia smoke app - if: ${{ matrix.rid == 'win-x64' || matrix.rid == 'win-x86' }} - shell: pwsh - run: | - $ErrorActionPreference = "Stop" - $publishDir = Join-Path $PWD "artifacts/smoke-${{ matrix.rid }}" - $app = Join-Path $publishDir "AvaloniaStaticLinkSmoke.exe" - $process = Start-Process -FilePath $app -WorkingDirectory $publishDir -PassThru - if ($process.WaitForExit(15000)) { - if ($process.ExitCode -eq 0) { - Write-Host "Avalonia Windows smoke app exited normally." - exit 0 - } - throw "Avalonia Windows smoke app failed with exit code $($process.ExitCode)." - } - - Stop-Process -Id $process.Id -Force - Write-Host "Avalonia Windows smoke app stayed alive for 15 seconds." - - - name: Run Linux Avalonia smoke app - if: ${{ startsWith(matrix.rid, 'linux-') && !startsWith(matrix.rid, 'linux-musl-') }} - shell: bash - run: | - set +e - cd "artifacts/smoke-${{ matrix.rid }}" - chmod +x ./AvaloniaStaticLinkSmoke - timeout 15s xvfb-run -a ./AvaloniaStaticLinkSmoke - code=$? - if [ "$code" = 124 ] || [ "$code" = 143 ]; then - echo "Avalonia Linux smoke app stayed alive for 15 seconds." - exit 0 - fi - exit "$code" - - - name: Verify musl ELF dependencies - if: ${{ startsWith(matrix.rid, 'linux-musl-') }} - shell: bash - run: | - set -euo pipefail - docker run --rm \ - -v "${{ github.workspace }}:/workspace" \ - -w /workspace \ - alpine:3.23 \ - sh -c 'apk add --no-cache binutils pax-utils && app="artifacts/smoke-${{ matrix.rid }}/AvaloniaStaticLinkSmoke" && readelf -l "$app" | grep "Requesting program interpreter: /lib/ld-musl-" && scanelf -n "$app" && ! scanelf -n "$app" | grep -E "ld-linux|ld64.so"' - - - name: Run Linux musl Avalonia smoke app - if: ${{ startsWith(matrix.rid, 'linux-musl-') }} - shell: bash - run: | - set -euo pipefail - docker run --rm \ - -v "${{ github.workspace }}:/workspace" \ - -w /workspace/artifacts/smoke-${{ matrix.rid }} \ - alpine:3.23 \ - sh -c 'apk add --no-cache xvfb-run libstdc++ libgcc fontconfig freetype ttf-dejavu libx11 libxcb libxext libxrender libxi libxrandr libxcursor libxfixes libxinerama libice libsm mesa-gl mesa-egl && fc-cache -f && fc-match sans && chmod +x ./AvaloniaStaticLinkSmoke && timeout 15s xvfb-run -a ./AvaloniaStaticLinkSmoke || code=$?; if [ "${code:-0}" = 124 ] || [ "${code:-0}" = 143 ]; then echo "Avalonia musl smoke app stayed alive for 15 seconds."; exit 0; fi; exit "${code:-0}"' - - - name: Run macOS Avalonia smoke app - if: ${{ startsWith(matrix.rid, 'osx-') }} - shell: bash - run: | - set -euo pipefail - cd "artifacts/smoke-${{ matrix.rid }}" - chmod +x ./AvaloniaStaticLinkSmoke - python3 - <<'PY' - import pathlib - import subprocess - import sys - - log = pathlib.Path("avalonia-smoke-run.log") - 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() - log.write_text(output or "") - print(output or "") - print("Avalonia smoke app stayed alive for 15 seconds.") - sys.exit(0) - - log.write_text(output or "") - print(output or "") - if proc.returncode == 0: - print("Avalonia smoke app exited normally.") - sys.exit(0) - - print(f"Avalonia smoke app failed with exit code {proc.returncode}.", file=sys.stderr) - sys.exit(proc.returncode if proc.returncode > 0 else 1) - PY - - - name: Upload Avalonia smoke app - uses: actions/upload-artifact@v4 - with: - name: avalonia-smoke-${{ matrix.rid }} - path: artifacts/smoke-${{ matrix.rid }}/** - if-no-files-found: error - - smoke-macos-metal: - runs-on: macos-15 - needs: pack - permissions: - contents: read - steps: - - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "10.0.302" - - - name: Download NuGet package - uses: actions/download-artifact@v4 - with: - name: staticlink-avalonia-nuget - path: artifacts/nuget - - - name: Publish Avalonia 12 macOS arm64 smoke app - shell: pwsh - run: | - $ErrorActionPreference = "Stop" - $package = Get-ChildItem -Path "artifacts/nuget" -Filter "StaticLink.Avalonia.*.nupkg" | Where-Object { $_.Name -notlike "StaticLink.Avalonia.Native.*" } | Select-Object -First 1 - if (-not $package) { throw "StaticLink NuGet package was not downloaded." } - if ($package.BaseName -notmatch '^StaticLink\.Avalonia\.(.+)$') { throw "Unexpected package name: $($package.Name)" } - $projectDir = Join-Path $PWD "artifacts/avalonia-metal-smoke-src" - pwsh ./scripts/create-avalonia-smoke.ps1 ` - -ProjectDir $projectDir ` - -NuGetSource (Resolve-Path "artifacts/nuget") ` - -StaticLinkVersion $Matches[1] ` - -AvaloniaVersion "12.1.0" ` - -StaticLinkNativeVersion "12.1.0.1" - dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") ` - -c Release ` - -r osx-arm64 ` - --configfile (Join-Path $projectDir "NuGet.config") ` - /p:PublishAot=true ` - /p:SelfContained=true ` - /p:PublishSingleFile=true ` - /p:StripSymbols=true ` - /p:PublishDir="$PWD/artifacts/smoke-osx-arm64-metal/" - - - name: Verify fully static third-party libraries - shell: bash - run: | - set -euo pipefail - publish_dir="artifacts/smoke-osx-arm64-metal" - test -x "$publish_dir/AvaloniaStaticLinkSmoke" - for library in libAvaloniaNative.dylib libSkiaSharp.dylib libHarfBuzzSharp.dylib; do - test ! -f "$publish_dir/$library" - done - - - name: Run default and Metal-only smoke tests - shell: bash - run: | - set -euo pipefail - cd artifacts/smoke-osx-arm64-metal - python3 - <<'PY' - import os - import pathlib - import subprocess - - for renderer in ("default", "metal"): - environment = os.environ.copy() - environment["STATICLINK_MAC_RENDERER"] = renderer - environment["STATICLINK_SMOKE_AUTOCLOSE"] = "1" - try: - result = subprocess.run( - ["./AvaloniaStaticLinkSmoke"], - env=environment, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - timeout=15, - ) - except subprocess.TimeoutExpired as exception: - output = exception.stdout or "" - if isinstance(output, bytes): - output = output.decode(errors="replace") - pathlib.Path(f"avalonia-{renderer}.log").write_text(output) - raise SystemExit(f"Avalonia {renderer} smoke app timed out.\n{output}") - - output = result.stdout or "" - pathlib.Path(f"avalonia-{renderer}.log").write_text(output) - print(output, end="") - marker = f"STATICLINK_SMOKE_READY={renderer}" - if result.returncode != 0 or marker not in output.splitlines(): - raise SystemExit(f"Avalonia {renderer} smoke app failed with exit code {result.returncode}.") - PY - - publish-nuget: - runs-on: ubuntu-24.04 - needs: - - pack - - smoke-avalonia - - smoke-macos-metal - steps: - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "10.0.302" - - - name: Download NuGet package - uses: actions/download-artifact@v4 - with: - name: staticlink-avalonia-nuget - path: artifacts/nuget - - - 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 }} - run: | - if [ -z "$NUGET_API_KEY" ]; then - echo "NUGET_API_KEY secret is not configured. Skipping publish." - exit 0 - fi - - for package in artifacts/nuget/*.nupkg; do - dotnet nuget push "$package" \ - --source "https://api.nuget.org/v3/index.json" \ - --api-key "$NUGET_API_KEY" \ - --skip-duplicate - done - - release: - runs-on: ubuntu-24.04 - needs: - - preflight - - pack - - smoke-avalonia - - smoke-macos-metal - - publish-nuget - if: ${{ startsWith(github.ref, 'refs/tags/staticlink-avalonia-v') || (github.event_name == 'workflow_dispatch' && inputs.publish_to_release == true) }} - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - - name: Download NuGet artifact - uses: actions/download-artifact@v4 - with: - name: staticlink-avalonia-nuget - path: release-artifacts/staticlink-avalonia-nuget - - - name: Download smoke artifacts - uses: actions/download-artifact@v4 - with: - pattern: avalonia-smoke-* - path: release-artifacts - merge-multiple: false - - - name: Prepare release files - run: | - mkdir -p 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 - name="$(basename "$dir")" - (cd "$dir" && zip -r "../../release-files/$name.zip" .) - done - ls -la release-files - - - name: Publish GitHub Release - env: - GH_TOKEN: ${{ github.token }} - PACKAGE_VERSION: ${{ needs.preflight.outputs.package_version }} - run: | - if [ "${GITHUB_REF_TYPE}" = "tag" ]; then - release_tag="${GITHUB_REF_NAME}" - title="${GITHUB_REF_NAME}" - else - release_tag="staticlink-avalonia-v${PACKAGE_VERSION}" - title="StaticLink.Avalonia ${PACKAGE_VERSION}" - fi - - if gh release view "$release_tag" >/dev/null 2>&1; then - gh release upload "$release_tag" release-files/* --clobber - else - gh release create "$release_tag" release-files/* \ - --target "$GITHUB_SHA" \ - --title "$title" \ - --notes "NuGet package plus Avalonia 11.3.14 NativeAOT single-file smoke apps for win-x64, win-x86, win-arm64, linux-x64, linux-arm64, linux-musl-x64, linux-musl-arm64, osx-x64, and osx-arm64." - fi diff --git a/.github/workflows/static-graphics-linux.yml b/.github/workflows/static-graphics-linux.yml deleted file mode 100644 index 7a72668..0000000 --- a/.github/workflows/static-graphics-linux.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: static-graphics-linux - -on: - workflow_call: - inputs: - skiasharp_version: - required: true - type: string - angle_branch: - required: true - type: string - workflow_dispatch: - inputs: - skiasharp_version: - required: true - default: "4.150.1" - type: string - angle_branch: - required: true - default: "7922" - type: string - -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 diff --git a/.github/workflows/static-graphics-macos.yml b/.github/workflows/static-graphics-macos.yml deleted file mode 100644 index 46d0986..0000000 --- a/.github/workflows/static-graphics-macos.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: static-graphics-macos - -on: - workflow_call: - inputs: - skiasharp_version: - required: true - type: string - angle_branch: - required: true - type: string - workflow_dispatch: - inputs: - skiasharp_version: - required: true - default: "4.150.1" - type: string - angle_branch: - required: true - default: "7922" - type: string - -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 diff --git a/.github/workflows/static-graphics-musl.yml b/.github/workflows/static-graphics-musl.yml deleted file mode 100644 index 728b27f..0000000 --- a/.github/workflows/static-graphics-musl.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: static-graphics-musl - -on: - workflow_call: - inputs: - skiasharp_version: - required: true - type: string - angle_branch: - required: true - type: string - dotnet_alpine_image: - required: true - type: string - 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 - -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 diff --git a/.github/workflows/static-graphics-preflight.yml b/.github/workflows/static-graphics-preflight.yml deleted file mode 100644 index 6519768..0000000 --- a/.github/workflows/static-graphics-preflight.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: static-graphics-preflight - -on: - workflow_call: - outputs: - package_version: - value: ${{ jobs.validate.outputs.package_version }} - skiasharp_version: - value: ${{ jobs.validate.outputs.skiasharp_version }} - angle_branch: - value: ${{ jobs.validate.outputs.angle_branch }} - workflow_dispatch: - -jobs: - validate: - runs-on: ubuntu-24.04 - outputs: - package_version: ${{ steps.versions.outputs.package_version }} - skiasharp_version: ${{ steps.versions.outputs.skiasharp_version }} - angle_branch: ${{ steps.versions.outputs.angle_branch }} - steps: - - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "10.0.302" - - - name: Read and validate versions - id: versions - shell: bash - run: | - python3 - <<'PY' - import os - import pathlib - import xml.etree.ElementTree as ET - - project = pathlib.Path("NuGet/StaticGraphics/StaticLink.Avalonia.csproj") - root = ET.parse(project).getroot() - package_version = root.findtext(".//Version") - skia_reference = next( - item.attrib["Version"] - for item in root.findall(".//PackageReference") - if item.attrib.get("Include") == "SkiaSharp" - ) - version_base, revision = package_version.rsplit(".", 1) - skiasharp_version, angle_branch = version_base.split("-", 1) - if not revision.isdigit() or skiasharp_version != skia_reference: - 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}" - if os.environ.get("GITHUB_REF_NAME") != expected_tag: - raise SystemExit(f"Release tag must be {expected_tag}") - - with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: - output.write(f"package_version={package_version}\n") - output.write(f"skiasharp_version={skiasharp_version}\n") - output.write(f"angle_branch={angle_branch}\n") - PY - - - name: Validate upstream refs and ANGLE patch - env: - SKIASHARP_VERSION: ${{ steps.versions.outputs.skiasharp_version }} - ANGLE_BRANCH: ${{ steps.versions.outputs.angle_branch }} - shell: bash - run: | - set -euo pipefail - git ls-remote --exit-code --heads https://github.com/mono/SkiaSharp.git "refs/heads/release/${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" - - - name: Validate scripts - shell: bash - run: | - set -euo pipefail - bash -n scripts/build-linux-static-graphics.sh - bash -n scripts/build-macos-static-graphics.sh - pwsh -NoProfile -Command '$files = @("scripts/build-windows-static-graphics.ps1", "scripts/create-avalonia-smoke.ps1"); foreach ($file in $files) { $errors = $null; [System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $file), [ref]$null, [ref]$errors) | Out-Null; if ($errors.Count) { $errors | Format-List | Out-String; exit 1 } }' - - - name: Compile smoke project - run: dotnet build Smoke/AvaloniaStaticLinkSmoke/AvaloniaStaticLinkSmoke.csproj -c Release -p:PublishAot=false diff --git a/.github/workflows/static-graphics-windows.yml b/.github/workflows/static-graphics-windows.yml deleted file mode 100644 index a6799e0..0000000 --- a/.github/workflows/static-graphics-windows.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: static-graphics-windows - -on: - workflow_call: - inputs: - skiasharp_version: - required: true - type: string - angle_branch: - required: true - type: string - workflow_dispatch: - inputs: - skiasharp_version: - required: true - default: "4.150.1" - type: string - angle_branch: - required: true - default: "7922" - type: string - -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 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/StaticLink.Avalonia.csproj b/NuGet/StaticGraphics/StaticLink.Avalonia.csproj index 69ca529..f7b3c24 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 + 4.150.1-7922.2 greepar greepar Static SkiaSharp, HarfBuzzSharp, and ANGLE libraries for Avalonia NativeAOT single-file publishing. @@ -24,6 +24,7 @@ + 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..b8f762a --- /dev/null +++ b/build/linux-toolchain/sources.lock.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/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..648f5a0 --- /dev/null +++ b/build/release-tracks.json @@ -0,0 +1,5 @@ +{ + "branches": [ + {"major": 4, "ref": "skia4", "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}))