Author SHA1 Message Date
geruipu eed8065164 Run two isolated persistent platform builds with 22 jobs each
ci/woodpecker/push/woodpecker Pipeline failed
2026-10-05 18:35:03 +08:00
geruipu 6e5cd77713 Validate all nine RIDs and NuGet with persistent Woodpecker caches
ci/woodpecker/push/woodpecker Pipeline failed
2026-10-05 17:52:09 +08:00
geruipu 1f23aff5e2 Complete Linux GL headers from the pinned container SDK
ci/woodpecker/push/woodpecker Pipeline was successful
2026-10-05 17:40:18 +08:00
geruipu bde7e2b1f7 Disable optional ANGLE PCI inventory outside locked Linux SDK
ci/woodpecker/push/woodpecker Pipeline failed
2026-10-05 17:35:13 +08:00
geruipu 0cccf559ea Expose pinned Expat headers to Zig Linux compiler adapters
ci/woodpecker/push/woodpecker Pipeline failed
2026-10-05 17:29:38 +08:00
geruipu 618427df2b Use all available container CPUs for Woodpecker compilation
ci/woodpecker/push/woodpecker Pipeline failed
2026-10-05 17:22:10 +08:00
geruipu 558e3b2454 Persist WSL source and build caches outside Woodpecker temporary home
ci/woodpecker/push/woodpecker Pipeline was canceled
2026-10-05 17:00:15 +08:00
geruipu f387e280a2 Retry source downloads and stop on exhausted fetch failures
ci/woodpecker/push/woodpecker Pipeline is running
2026-10-05 16:50:30 +08:00
geruipu 323444d998 Check Docker CLI availability on WSL agent
ci/woodpecker/push/woodpecker Pipeline failed
2026-10-05 16:44:12 +08:00
geruipu 5689530857 Use Woodpecker skip_clone workflow option
ci/woodpecker/push/woodpecker Pipeline failed
2026-10-05 16:15:26 +08:00
geruipu e5cc03dfe3 Add WSL Woodpecker cross-build test and prefer bundled headers 2026-10-05 16:14:08 +08:00
geruipu 2694645d0d Sync verified Native toolchain fixes for graphics Actions tests 2026-10-05 13:49:54 +08:00
geruipu 3f7082d37d Validate the complete container toolchain before release builds 2026-10-05 13:29:41 +08:00
geruipu 5b15aeb5e2 Align Debian base packages with the pinned snapshot 2026-10-05 13:11:35 +08:00
geruipu 9375222b47 Isolate manual artifact validation without package publication 2026-10-05 13:09:17 +08:00
greepar 98976747c3 Allow independent native release runs 2026-09-28 12:28:45 +08:00
greepar bf549214dd Track Avalonia 11 and 12 native releases 2026-09-28 12:25:50 +08:00
greepar 15f1f520de Fix Avalonia 12 macOS native header generation 2026-09-28 12:16:21 +08:00
greepar f91711fe9c Merge pull request #2 from greepar/fix/macos-metal-docs-ci
Clarify macOS Metal support and add CI coverage
2026-09-08 10:51:13 +08:00
geruipu 8371cd3a44 Document and test macOS Metal support 2026-09-08 10:46:32 +08:00
greepar 572d14040d Add SkiaSharp 4.150.1 static graphics release 2026-07-23 19:53:03 +08:00
91 changed files with 5121 additions and 828 deletions

No files matched your search

-278
View File
@@ -1,278 +0,0 @@
name: staticlink-avalonia-native
on:
schedule:
- cron: "17 3 * * *"
workflow_dispatch:
inputs:
avalonia_version:
description: Avalonia version to build; empty uses the latest stable NuGet version
required: false
type: string
force_build:
description: Build even when this StaticLink.Avalonia.Native version already exists
required: true
default: false
type: boolean
publish_to_nuget:
description: Publish with NuGet Trusted Publishing after smoke tests
required: true
default: true
type: boolean
concurrency:
group: staticlink-avalonia-native
cancel-in-progress: false
jobs:
detect:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
avalonia_version: ${{ steps.version.outputs.avalonia_version }}
package_version: ${{ steps.version.outputs.package_version }}
should_build: ${{ steps.version.outputs.should_build }}
steps:
- name: Detect unpublished Avalonia version
id: version
shell: bash
env:
REQUESTED_VERSION: ${{ inputs.avalonia_version }}
FORCE_BUILD: ${{ inputs.force_build }}
run: |
set -euo pipefail
avalonia_version="$REQUESTED_VERSION"
if [ -z "$avalonia_version" ]; then
avalonia_version="$(curl --fail --silent --show-error --location \
https://api.nuget.org/v3-flatcontainer/avalonia/index.json \
| jq -r '[.versions[] | select(contains("-") | not)] | last')"
fi
if [ -z "$avalonia_version" ] || [ "$avalonia_version" = "null" ]; then
echo "Could not determine the latest stable Avalonia version." >&2
exit 1
fi
package_version="$avalonia_version.1"
package_url="https://api.nuget.org/v3-flatcontainer/staticlink.avalonia.native/$package_version/staticlink.avalonia.native.$package_version.nupkg"
should_build=true
if [ "$FORCE_BUILD" != "true" ] && curl --fail --silent --location --output /dev/null "$package_url"; then
should_build=false
fi
echo "Avalonia version: $avalonia_version"
echo "StaticLink.Avalonia.Native version: $package_version"
echo "Build required: $should_build"
echo "avalonia_version=$avalonia_version" >> "$GITHUB_OUTPUT"
echo "package_version=$package_version" >> "$GITHUB_OUTPUT"
echo "should_build=$should_build" >> "$GITHUB_OUTPUT"
build:
needs: detect
if: ${{ needs.detect.outputs.should_build == 'true' }}
strategy:
fail-fast: false
matrix:
include:
- rid: osx-x64
runner: macos-14
- rid: osx-arm64
runner: macos-15
runs-on: ${{ matrix.runner }}
permissions:
contents: read
env:
RID: ${{ matrix.rid }}
AVALONIA_VERSION: ${{ needs.detect.outputs.avalonia_version }}
WORK_DIR: ${{ github.workspace }}/External/AvaloniaNative/.work
OUTPUT_DIR: ${{ github.workspace }}/External/AvaloniaNative/${{ matrix.rid }}/native
steps:
- uses: actions/checkout@v4
- name: Setup .NET for Avalonia header generation
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
10.0.x
- name: Build static AvaloniaNative
run: bash scripts/build-macos-avalonia-native-static.sh
- name: Build macOS font preinit object
run: bash scripts/build-macos-font-preinit.sh
- name: Upload static AvaloniaNative
uses: actions/upload-artifact@v4
with:
name: native-avalonia-${{ matrix.rid }}
path: |
External/AvaloniaNative/${{ matrix.rid }}/native/libAvaloniaNative.a
External/AvaloniaNative/${{ matrix.rid }}/native/macos_font_preinit.o
if-no-files-found: error
pack:
needs:
- detect
- build
if: ${{ needs.detect.outputs.should_build == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- name: Download static AvaloniaNative archives
uses: actions/download-artifact@v4
with:
pattern: native-avalonia-*
path: artifacts/native
merge-multiple: false
- name: Prepare package inputs
shell: bash
run: |
set -euo pipefail
for rid in osx-x64 osx-arm64; do
mkdir -p "External/AvaloniaNative/$rid/native"
cp "artifacts/native/native-avalonia-$rid/libAvaloniaNative.a" "External/AvaloniaNative/$rid/native/"
cp "artifacts/native/native-avalonia-$rid/macos_font_preinit.o" "External/AvaloniaNative/$rid/native/"
done
- name: Pack StaticLink.Avalonia.Native
run: |
dotnet pack NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj \
-c Release \
-o artifacts/nuget \
/p:PackageVersion=${{ needs.detect.outputs.package_version }} \
/p:Version=${{ needs.detect.outputs.package_version }}
- name: Upload NuGet package
uses: actions/upload-artifact@v4
with:
name: staticlink-avalonia-native-${{ needs.detect.outputs.package_version }}
path: artifacts/nuget/*.nupkg
if-no-files-found: error
smoke:
needs:
- detect
- pack
if: ${{ needs.detect.outputs.should_build == 'true' }}
strategy:
fail-fast: false
matrix:
include:
- rid: osx-x64
runner: macos-14
- rid: osx-arm64
runner: macos-15
runs-on: ${{ matrix.runner }}
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- name: Download NuGet package
uses: actions/download-artifact@v4
with:
name: staticlink-avalonia-native-${{ needs.detect.outputs.package_version }}
path: artifacts/nuget
- name: Publish Avalonia NativeAOT smoke app
shell: pwsh
run: |
$projectDir = Join-Path $PWD "artifacts/avalonia-native-smoke-src"
pwsh ./scripts/create-avalonia-smoke.ps1 `
-ProjectDir $projectDir `
-NuGetSource (Resolve-Path "artifacts/nuget") `
-AvaloniaVersion "${{ needs.detect.outputs.avalonia_version }}" `
-StaticLinkNativeVersion "${{ needs.detect.outputs.package_version }}"
dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") `
-c Release `
-r "${{ matrix.rid }}" `
--configfile (Join-Path $projectDir "NuGet.config") `
/p:PublishDir="$PWD/artifacts/smoke-${{ matrix.rid }}/"
- name: Verify AvaloniaNative is statically linked
shell: bash
run: |
set -euo pipefail
test ! -f "artifacts/smoke-${{ matrix.rid }}/libAvaloniaNative.dylib"
test -x "artifacts/smoke-${{ matrix.rid }}/AvaloniaStaticLinkSmoke"
- name: Run Avalonia smoke app
shell: bash
run: |
set -euo pipefail
cd "artifacts/smoke-${{ matrix.rid }}"
python3 - <<'PY'
import subprocess
import sys
proc = subprocess.Popen(
["./AvaloniaStaticLinkSmoke"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
try:
output, _ = proc.communicate(timeout=15)
except subprocess.TimeoutExpired:
proc.kill()
output, _ = proc.communicate()
print(output or "")
print("Avalonia smoke app stayed alive for 15 seconds.")
sys.exit(0)
print(output or "")
if proc.returncode == 0:
sys.exit(0)
sys.exit(proc.returncode if proc.returncode > 0 else 1)
PY
publish:
needs:
- detect
- smoke
if: ${{ needs.detect.outputs.should_build == 'true' && (github.event_name == 'schedule' || inputs.publish_to_nuget == true) }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Download NuGet package
uses: actions/download-artifact@v4
with:
name: staticlink-avalonia-native-${{ needs.detect.outputs.package_version }}
path: artifacts/nuget
- name: Validate Trusted Publishing configuration
env:
NUGET_USER: ${{ vars.NUGET_USER }}
run: test -n "$NUGET_USER"
- name: NuGet login
id: login
uses: NuGet/login@v1
with:
user: ${{ vars.NUGET_USER }}
- name: Publish to nuget.org
run: |
dotnet nuget push artifacts/nuget/*.nupkg \
--source https://www.nuget.org/api/v2/package \
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
--skip-duplicate
@@ -6,7 +6,7 @@ on:
source_version:
description: Existing package version containing the static libraries
required: true
default: 3.119.2-7151.11
default: 3.119.4-7922.1
type: string
publish_to_nuget:
description: Publish package to nuget.org
+294 -218
View File
@@ -3,6 +3,18 @@ name: staticlink-avalonia
on:
workflow_dispatch:
inputs:
environment_only:
description: Validate the complete toolchain container without building release libraries
type: boolean
default: false
skiasharp_version:
description: Stable SkiaSharp version from this branch major; empty uses the branch version
type: string
default: ''
native_package_revision:
description: Published AvaloniaNative revision for independent graphics smoke tests
type: string
default: '1'
publish_to_nuget:
description: Publish package to nuget.org
required: true
@@ -11,202 +23,48 @@ on:
publish_to_release:
description: Publish NuGet package and smoke apps to GitHub Release
required: true
default: true
default: false
type: boolean
push:
tags:
- "staticlink-avalonia-v*"
env:
SKIASHARP_VERSION: "3.119.2"
ANGLE_BRANCH: "7151"
DOTNET_ALPINE_IMAGE: mcr.microsoft.com/dotnet/sdk:10.0-alpine
DOTNET_ALPINE_IMAGE: mcr.microsoft.com/dotnet/sdk:10.0.302-alpine3.23
concurrency:
group: graphics-${{ github.ref }}-${{ inputs.skiasharp_version }}
cancel-in-progress: false
jobs:
build-linux:
strategy:
fail-fast: false
matrix:
include:
- rid: linux-x64
target_cpu: x64
runner: ubuntu-latest
- rid: linux-arm64
target_cpu: arm64
runner: ubuntu-24.04-arm
- rid: linux-musl-x64
target_cpu: x64
runner: ubuntu-latest
- rid: linux-musl-arm64
target_cpu: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
environment:
permissions:
contents: read
packages: write
uses: ./.github/workflows/zig-toolchain-image.yml
- name: Install native build dependencies
if: ${{ !startsWith(matrix.rid, 'linux-musl-') }}
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
preflight:
if: ${{ inputs.environment_only != true }}
uses: ./.github/workflows/static-graphics-preflight.yml
with:
skiasharp_version: ${{ inputs.skiasharp_version }}
- name: Build Linux static graphics libraries
if: ${{ !startsWith(matrix.rid, 'linux-musl-') }}
env:
RID: ${{ matrix.rid }}
TARGET_CPU: ${{ matrix.target_cpu }}
LLVM_AR: llvm-ar
run: scripts/build-linux-static-graphics.sh all
- name: Build Linux musl static graphics libraries
if: ${{ startsWith(matrix.rid, 'linux-musl-') }}
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
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: 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
build-windows:
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
steps:
- uses: actions/checkout@v4
- 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
build-macos:
strategy:
fail-fast: false
matrix:
include:
- rid: osx-x64
target_cpu: x64
runner: macos-14
- rid: osx-arm64
target_cpu: arm64
runner: macos-15
runs-on: ${{ matrix.runner }}
env:
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: 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 <metal_stdlib>
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
build-zig:
permissions:
contents: read
packages: write
needs: [preflight, environment]
uses: ./.github/workflows/static-graphics-zig-cross.yml
with:
skiasharp_version: ${{ needs.preflight.outputs.skiasharp_version }}
source_lock: ${{ needs.preflight.outputs.source_lock }}
toolchain_image: ${{ needs.environment.outputs.image }}
pack:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- build-linux
- build-windows
- build-macos
- preflight
- build-zig
permissions:
contents: read
steps:
@@ -215,7 +73,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
dotnet-version: "10.0.302"
- name: Download static archives
uses: actions/download-artifact@v4
@@ -256,7 +114,13 @@ jobs:
test -f External/NativeStatic/win-x86/native/skia_x86_stdcall_thunks.lib
- name: Pack NuGet package
run: dotnet pack NuGet/StaticGraphics/StaticLink.Avalonia.csproj -c Release -o artifacts/nuget
env:
SOURCE_LOCK_JSON: ${{ needs.preflight.outputs.source_lock }}
PACKAGE_VERSION: ${{ needs.preflight.outputs.package_version }}
SKIASHARP_VERSION: ${{ needs.preflight.outputs.skiasharp_version }}
run: |
python3 scripts/apply-release-version.py
dotnet pack NuGet/StaticGraphics/StaticLink.Avalonia.csproj -c Release -o artifacts/nuget
- name: Verify portable NuGet paths
shell: bash
@@ -270,6 +134,13 @@ jobs:
unzip -Z1 "$package" | grep -Fx 'buildTransitive/StaticLink.Avalonia.props'
unzip -Z1 "$package" | grep -Fx 'buildTransitive/StaticLink.Avalonia.targets'
- name: Upload release source lock
uses: actions/upload-artifact@v4
with:
name: graphics-release-source-lock
path: build/linux-toolchain/release.lock.json
if-no-files-found: error
- name: Upload NuGet artifact
uses: actions/upload-artifact@v4
with:
@@ -279,23 +150,6 @@ jobs:
artifacts/nuget/*.snupkg
if-no-files-found: error
- 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
smoke-avalonia:
needs: pack
strategy:
@@ -312,15 +166,15 @@ jobs:
runner: windows-2022
msvc_arch: amd64_arm64
- rid: linux-x64
runner: ubuntu-latest
runner: ubuntu-24.04
- rid: linux-arm64
runner: ubuntu-24.04-arm
- rid: linux-musl-x64
runner: ubuntu-latest
runner: ubuntu-24.04
- rid: linux-musl-arm64
runner: ubuntu-24.04-arm
- rid: osx-x64
runner: macos-14
runner: macos-15
avalonia_version: 11.3.14
- rid: osx-arm64
runner: macos-15
@@ -335,16 +189,21 @@ jobs:
with:
arch: ${{ matrix.msvc_arch }}
- name: Install LLVM linker for Zig Windows archives
if: ${{ startsWith(matrix.rid, 'win-') }}
shell: pwsh
run: choco install llvm -y
- name: Install Linux publish dependencies
if: ${{ startsWith(matrix.rid, 'linux-') && !startsWith(matrix.rid, 'linux-musl-') }}
run: |
sudo apt-get update
sudo apt-get install -y clang lld zlib1g-dev libfontconfig1-dev
sudo apt-get install -y clang lld xvfb zlib1g-dev libfontconfig1-dev libffi-dev libwayland-dev libx11-dev libxext-dev libxcb1-dev libfreetype6 libgl1 libice6 libsm6 libx11-6 libxcb1 libxcursor1 libxext6 libxfixes3 libxinerama1 libxrandr2 libxrender1 libxi6
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
dotnet-version: "10.0.302"
- name: Download NuGet package
uses: actions/download-artifact@v4
@@ -364,7 +223,9 @@ jobs:
$avaloniaVersion = "${{ matrix.avalonia_version }}"
if (-not $avaloniaVersion) { $avaloniaVersion = "11.3.14" }
$projectDir = Join-Path $PWD "artifacts/avalonia-smoke-src"
pwsh ./scripts/create-avalonia-smoke.ps1 -ProjectDir $projectDir -NuGetSource (Resolve-Path "artifacts/nuget") -StaticLinkVersion $version -AvaloniaVersion $avaloniaVersion
$nativeVersion = ""
if ("${{ matrix.rid }}".StartsWith("osx-")) { $nativeVersion = "$avaloniaVersion.${{ inputs.native_package_revision || '1' }}" }
pwsh ./scripts/create-avalonia-smoke.ps1 -ProjectDir $projectDir -NuGetSource (Resolve-Path "artifacts/nuget") -StaticLinkVersion $version -AvaloniaVersion $avaloniaVersion -StaticLinkNativeVersion $nativeVersion
dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") `
-c Release `
-r "${{ matrix.rid }}" `
@@ -390,7 +251,7 @@ jobs:
-v "${{ github.workspace }}:/workspace" `
-w /workspace `
"${{ env.DOTNET_ALPINE_IMAGE }}" `
sh -c "apk add --no-cache clang lld build-base zlib-dev fontconfig-dev && dotnet publish /workspace/artifacts/avalonia-smoke-src/AvaloniaStaticLinkSmoke.csproj -c Release -r ${{ matrix.rid }} --configfile /workspace/artifacts/avalonia-smoke-src/NuGet.config /p:PublishAot=true /p:SelfContained=true /p:PublishSingleFile=true /p:StripSymbols=true /p:PublishDir=/workspace/artifacts/smoke-${{ matrix.rid }}/"
sh -c "apk add --no-cache clang lld build-base zlib-dev fontconfig-dev libffi-dev wayland-dev libx11-dev libxext-dev libxcb-dev && dotnet publish /workspace/artifacts/avalonia-smoke-src/AvaloniaStaticLinkSmoke.csproj -c Release -r ${{ matrix.rid }} --configfile /workspace/artifacts/avalonia-smoke-src/NuGet.config /p:PublishAot=true /p:SelfContained=true /p:PublishSingleFile=true /p:StripSymbols=true /p:PublishDir=/workspace/artifacts/smoke-${{ matrix.rid }}/"
- name: Verify no dynamic graphics libraries remain
shell: pwsh
@@ -405,6 +266,56 @@ jobs:
}
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
@@ -413,7 +324,7 @@ jobs:
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-w /workspace \
alpine:latest \
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
@@ -424,7 +335,7 @@ jobs:
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-w /workspace/artifacts/smoke-${{ matrix.rid }} \
alpine:latest \
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
@@ -468,31 +379,194 @@ jobs:
PY
- name: Upload Avalonia smoke app
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: avalonia-smoke-${{ matrix.rid }}
path: artifacts/smoke-${{ matrix.rid }}/**
if-no-files-found: error
if-no-files-found: warn
release:
runs-on: ubuntu-latest
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.${{ inputs.native_package_revision || '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
- name: Upload macOS Metal smoke app
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: avalonia-smoke-osx-arm64-metal
path: artifacts/smoke-osx-arm64-metal/**
if-no-files-found: warn
publish-nuget:
permissions:
contents: read
id-token: write
runs-on: ubuntu-24.04
needs:
- pack
- 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: NuGet Trusted Publishing login
id: login
if: ${{ vars.NUGET_USER != '' && (startsWith(github.ref, 'refs/tags/staticlink-avalonia-v') || inputs.publish_to_nuget == true) }}
uses: NuGet/login@v1
with:
user: ${{ vars.NUGET_USER }}
- name: Publish to nuget.org
if: ${{ startsWith(github.ref, 'refs/tags/staticlink-avalonia-v') || (github.event_name == 'workflow_dispatch' && inputs.publish_to_nuget == true) }}
env:
NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY || secrets.NUGET_API_KEY }}
run: |
if [ -z "$NUGET_API_KEY" ]; then
echo "NUGET_API_KEY secret is not configured." >&2
exit 1
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 release artifacts
- name: Download NuGet artifact
uses: actions/download-artifact@v4
with:
name: staticlink-avalonia-nuget
path: release-artifacts/staticlink-avalonia-nuget
- name: Download release source lock
uses: actions/download-artifact@v4
with:
name: graphics-release-source-lock
path: release-artifacts/source-lock
- name: Download smoke artifacts
uses: actions/download-artifact@v4
with:
pattern: avalonia-smoke-*
path: release-artifacts
merge-multiple: false
- name: Prepare release files
run: |
mkdir -p release-files
cp release-artifacts/source-lock/release.lock.json release-files/
find release-artifacts/staticlink-avalonia-nuget -type f \( -name '*.nupkg' -o -name '*.snupkg' \) -exec cp {} release-files/ \;
for dir in release-artifacts/avalonia-smoke-*; do
[ -d "$dir" ] || continue
@@ -504,19 +578,21 @@ jobs:
- 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-smoke-${GITHUB_RUN_NUMBER}"
title="StaticLink.Avalonia smoke ${GITHUB_RUN_NUMBER}"
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-arm64, linux-x64, linux-arm64, osx-x64, and osx-arm64."
--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
@@ -0,0 +1,28 @@
name: static-graphics-linux-debian-zig
on:
workflow_call:
inputs:
skiasharp_version:
required: true
type: string
source_lock:
type: string
default: ''
workflow_dispatch:
inputs:
skiasharp_version:
type: string
default: '3.119.4'
permissions:
contents: read
packages: write
jobs:
build:
uses: ./.github/workflows/static-graphics-zig-cross.yml
with:
skiasharp_version: ${{ inputs.skiasharp_version }}
source_lock: ${{ inputs.source_lock || '' }}
rids: '["linux-x64","linux-arm64","linux-musl-x64","linux-musl-arm64"]'
@@ -0,0 +1,28 @@
name: static-graphics-linux-zig
on:
workflow_call:
inputs:
skiasharp_version:
required: true
type: string
source_lock:
type: string
default: ''
workflow_dispatch:
inputs:
skiasharp_version:
type: string
default: '3.119.4'
permissions:
contents: read
packages: write
jobs:
build:
uses: ./.github/workflows/static-graphics-zig-cross.yml
with:
skiasharp_version: ${{ inputs.skiasharp_version }}
source_lock: ${{ inputs.source_lock || '' }}
rids: '["linux-x64","linux-arm64"]'
@@ -0,0 +1,28 @@
name: static-graphics-macos-zig
on:
workflow_call:
inputs:
skiasharp_version:
required: true
type: string
source_lock:
type: string
default: ''
workflow_dispatch:
inputs:
skiasharp_version:
type: string
default: '3.119.4'
permissions:
contents: read
packages: write
jobs:
build:
uses: ./.github/workflows/static-graphics-zig-cross.yml
with:
skiasharp_version: ${{ inputs.skiasharp_version }}
source_lock: ${{ inputs.source_lock || '' }}
rids: '["osx-x64","osx-arm64"]'
@@ -0,0 +1,28 @@
name: static-graphics-musl-zig
on:
workflow_call:
inputs:
skiasharp_version:
required: true
type: string
source_lock:
type: string
default: ''
workflow_dispatch:
inputs:
skiasharp_version:
type: string
default: '3.119.4'
permissions:
contents: read
packages: write
jobs:
build:
uses: ./.github/workflows/static-graphics-zig-cross.yml
with:
skiasharp_version: ${{ inputs.skiasharp_version }}
source_lock: ${{ inputs.source_lock || '' }}
rids: '["linux-musl-x64","linux-musl-arm64"]'
@@ -0,0 +1,99 @@
name: static-graphics-preflight
on:
workflow_call:
inputs:
skiasharp_version:
type: string
default: ''
outputs:
source_lock:
value: ${{ jobs.validate.outputs.source_lock }}
package_version:
value: ${{ jobs.validate.outputs.package_version }}
skiasharp_version:
value: ${{ jobs.validate.outputs.skiasharp_version }}
angle_branch:
value: ${{ jobs.validate.outputs.angle_branch }}
workflow_dispatch:
jobs:
validate:
runs-on: ubuntu-24.04
outputs:
source_lock: ${{ steps.versions.outputs.source_lock }}
package_version: ${{ steps.versions.outputs.package_version }}
skiasharp_version: ${{ steps.versions.outputs.skiasharp_version }}
angle_branch: ${{ steps.versions.outputs.angle_branch }}
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
env:
REQUESTED_VERSION: ${{ inputs.skiasharp_version }}
run: |
if [ "${GITHUB_REF_TYPE:-}" = tag ] && [ -z "$REQUESTED_VERSION" ]; then
tag_version="${GITHUB_REF_NAME#staticlink-avalonia-v}"
REQUESTED_VERSION="${tag_version%%-*}"
fi
python3 scripts/resolve-release-lock.py --version "$REQUESTED_VERSION"
python3 - <<'PY'
import os
import pathlib
import xml.etree.ElementTree as ET
project = pathlib.Path("NuGet/StaticGraphics/StaticLink.Avalonia.csproj")
root = ET.parse(project).getroot()
import json
lock = json.loads(pathlib.Path("build/linux-toolchain/release.lock.json").read_text())
package_version = root.findtext(".//Version")
package_version = lock["skiasharp_version"] + "-" + package_version.split("-", 1)[1]
skia_reference = next(
item.attrib["Version"]
for item in root.findall(".//PackageReference")
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 != lock["skiasharp_version"]:
raise SystemExit(f"Inconsistent package version: {package_version}, SkiaSharp: {skia_reference}")
if os.environ.get("GITHUB_REF_TYPE") == "tag":
expected_tag = f"staticlink-avalonia-v{package_version}"
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("source_lock=" + json.dumps(lock, separators=(",", ":")) + "\n")
output.write(f"package_version={package_version}\n")
output.write(f"skiasharp_version={skiasharp_version}\n")
output.write(f"angle_branch={angle_branch}\n")
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 --tags https://github.com/mono/SkiaSharp.git "refs/tags/v${SKIASHARP_VERSION}"
git clone --depth 1 --branch "chromium/${ANGLE_BRANCH}" https://github.com/google/angle.git /tmp/staticlink-angle
git -C /tmp/staticlink-angle apply --check "$GITHUB_WORKSPACE/External/NativeStatic/patches/angle-chromium-${ANGLE_BRANCH}.patch"
- 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
@@ -0,0 +1,28 @@
name: static-graphics-windows-zig
on:
workflow_call:
inputs:
skiasharp_version:
required: true
type: string
source_lock:
type: string
default: ''
workflow_dispatch:
inputs:
skiasharp_version:
type: string
default: '3.119.4'
permissions:
contents: read
packages: write
jobs:
build:
uses: ./.github/workflows/static-graphics-zig-cross.yml
with:
skiasharp_version: ${{ inputs.skiasharp_version }}
source_lock: ${{ inputs.source_lock || '' }}
rids: '["win-x64","win-x86","win-arm64"]'
@@ -0,0 +1,111 @@
name: static-graphics-zig-cross-debian12
# All native archives are compiled with Zig on Linux; platform runners validate consumer apps.
on:
workflow_call:
inputs:
skiasharp_version:
type: string
required: true
toolchain_image:
type: string
default: ''
rids:
type: string
default: '["linux-x64","linux-arm64","linux-musl-x64","linux-musl-arm64","win-x64","win-x86","win-arm64","osx-x64","osx-arm64"]'
artifact_prefix:
type: string
default: native-static
source_lock:
type: string
required: true
workflow_dispatch:
inputs:
skiasharp_version:
description: Locked SkiaSharp release profile
type: string
default: '3.119.4'
permissions:
contents: read
packages: write
jobs:
image:
if: ${{ inputs.toolchain_image == '' }}
uses: ./.github/workflows/zig-toolchain-image.yml
build:
needs: image
if: ${{ always() && (needs.image.result == 'success' || inputs.toolchain_image != '') }}
permissions:
contents: read
packages: read
env:
TOOLCHAIN_IMAGE: ${{ inputs.toolchain_image || needs.image.outputs.image }}
timeout-minutes: 180
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
rid: ${{ fromJSON(inputs.rids || '["linux-x64","linux-arm64","linux-musl-x64","linux-musl-arm64","win-x64","win-x86","win-arm64","osx-x64","osx-arm64"]') }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Resolve immutable build inputs
env:
SOURCE_LOCK_JSON: ${{ inputs.source_lock }}
REQUESTED_VERSION: ${{ inputs.skiasharp_version }}
run: |
if [ -n "$SOURCE_LOCK_JSON" ]; then
python3 - <<'PY'
import json, os, pathlib
lock = json.loads(os.environ['SOURCE_LOCK_JSON'])
if lock['skiasharp_version'] != os.environ['REQUESTED_VERSION']:
raise SystemExit('Source lock does not match requested version')
pathlib.Path('build/linux-toolchain/release.lock.json').write_text(json.dumps(lock, indent=2) + '\n')
PY
else
python3 scripts/resolve-release-lock.py --version "$REQUESTED_VERSION"
fi
- name: Restore exact target cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: External/NativeStatic/.work
key: zig-debian12-v3-${{ inputs.skiasharp_version }}-${{ matrix.rid }}-${{ hashFiles('build/linux-toolchain/**', 'scripts/prepare-cross-sources.sh', 'scripts/build-linux-static-graphics.sh', 'External/NativeStatic/patches/*.patch') }}
- name: Pull immutable toolchain image
env:
GH_TOKEN: ${{ github.token }}
ACTOR: ${{ github.actor }}
run: |
printf '%s' "$GH_TOKEN" | docker login ghcr.io -u "$ACTOR" --password-stdin
docker pull "$TOOLCHAIN_IMAGE"
- name: Prepare locked sources and build native libraries
env:
CROSS_RID: ${{ matrix.rid }}
SKIASHARP_PROFILE: ${{ inputs.skiasharp_version }}
run: |
mkdir -p External/NativeStatic/.work/home
docker run --rm --user "$(id -u):$(id -g)" \
-e HOME=/src/External/NativeStatic/.work/home -e CROSS_RID -e SKIASHARP_PROFILE \
-e CCACHE_DIR=/src/External/NativeStatic/.work/ccache -e CCACHE_MAXSIZE=1G \
-e ZIG_GLOBAL_CACHE_DIR=/src/External/NativeStatic/.work/zig-global \
-v "$PWD:/src" -w /src --entrypoint bash "$TOOLCHAIN_IMAGE" -c '
set -euo pipefail
export SOURCE_LOCK="/src/build/linux-toolchain/release.lock.json"
source scripts/prepare-cross-sources.sh
python3 build/linux-toolchain/build-cross.py \
--target "$CROSS_RID" \
--sources-manifest "$WORK_DIR/cross-sources.json" \
--work /src/External/NativeStatic/.work/toolchains \
--output "/src/External/NativeStatic/zig-cross/$SKIASHARP_PROFILE/$CROSS_RID" --jobs 4
python3 build/linux-toolchain/package-zig.py \
--rid "$CROSS_RID" \
--input "/src/External/NativeStatic/zig-cross/$SKIASHARP_PROFILE/$CROSS_RID" \
--output "/src/External/NativeStatic/$CROSS_RID/native"
'
- name: Upload archives, link tests and input manifest
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ inputs.artifact_prefix || 'native-static' }}-${{ matrix.rid }}
path: External/NativeStatic/${{ matrix.rid }}/**
if-no-files-found: error
@@ -0,0 +1,55 @@
name: zig-recent-release-regression
on:
workflow_dispatch:
inputs:
versions:
description: JSON array of stable SkiaSharp versions from this branch major
type: string
default: '["3.119.2","3.119.4"]'
permissions:
contents: read
packages: write
jobs:
resolve:
runs-on: ubuntu-24.04
outputs:
releases: ${{ steps.locks.outputs.releases }}
steps:
- uses: actions/checkout@v4
- id: locks
env:
VERSIONS: ${{ inputs.versions }}
run: |
python3 - <<'PY'
import json, os, pathlib, subprocess
versions = json.loads(os.environ['VERSIONS'])
if not isinstance(versions, list) or not 1 <= len(versions) <= 5:
raise SystemExit('Expected one to five stable versions')
releases = []
for version in versions:
subprocess.run(['python3', 'scripts/resolve-release-lock.py', '--version', version], check=True)
lock = json.loads(pathlib.Path('build/linux-toolchain/release.lock.json').read_text())
releases.append({'version': version, 'lock': json.dumps(lock)})
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
output.write('releases=' + json.dumps(releases, separators=(',', ':')) + '\n')
PY
toolchain:
needs: resolve
uses: ./.github/workflows/zig-toolchain-image.yml
build:
needs: [resolve, toolchain]
strategy:
fail-fast: false
matrix:
release: ${{ fromJSON(needs.resolve.outputs.releases) }}
uses: ./.github/workflows/static-graphics-zig-cross.yml
with:
skiasharp_version: ${{ matrix.release.version }}
source_lock: ${{ matrix.release.lock }}
toolchain_image: ${{ needs.toolchain.outputs.image }}
artifact_prefix: regression-${{ matrix.release.version }}
+78
View File
@@ -0,0 +1,78 @@
name: pinned-zig-toolchain-image
on:
workflow_call:
inputs:
validation_rids:
type: string
default: '[]'
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 }}
VALIDATION_RIDS: ${{ inputs.validation_rids || '[]' }}
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"
built_image=false
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
built_image=true
fi
mkdir -p artifacts/toolchain-environment
docker run --rm --user "$(id -u):$(id -g)" \
-e HOME=/tmp -e DOTNET_CLI_HOME=/tmp -e ZIG_GLOBAL_CACHE_DIR=/tmp/zig-cache \
-v "$PWD/artifacts/toolchain-environment:/validation" \
--entrypoint python3 "$image" /opt/staticlink/validate-toolchain.py --output /validation --rids "$VALIDATION_RIDS"
if [ "$built_image" = true ]; then
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"
- name: Upload environment validation report
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: toolchain-environment-validation
path: artifacts/toolchain-environment/
if-no-files-found: warn
+22
View File
@@ -0,0 +1,22 @@
when:
- event: [push, manual]
branch: skia3
labels:
platform: linux/amd64
backend: local
skip_clone: true
steps:
- name: checkout
image: bash
commands:
- git init source
- git -C source remote add origin https://git.oeo.one/greepar/StaticLink.Avalonia.git
- git -C source fetch --depth=1 origin "$CI_COMMIT_SHA"
- git -C source checkout --detach FETCH_HEAD
- name: nine-platform-build-link-and-pack
image: bash
commands:
- bash source/scripts/woodpecker-cross-test.sh
+38
View File
@@ -0,0 +1,38 @@
# Manual artifact validation
Daily schedules are disabled during validation. No NuGet.org publication or
GitHub Release is requested by the manual dispatcher on main.
Before release builds, run nuget-static-graphics.yml on skia4 with
environment_only=true. This checks the actual container's Python/depot_tools,
.NET 8/10, SDKs, sysroots, and C/C++ compilation plus C executable linking for
all nine RIDs. Download toolchain-environment-validation to inspect the report.
The same environment gate runs before every graphics and native build and
must pass before the toolchain image is published for release compilation.
- 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-<RID> from each graphics run.
Download staticlink-avalonia-native-<version> and
avalonia-native-smoke-<AvaloniaVersion>-<RID> 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.
+9
View File
@@ -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.
-64
View File
@@ -1,64 +0,0 @@
diff --git a/DEPS b/DEPS
index 9085ba513e..565840a832 100644
--- a/DEPS
+++ b/DEPS
@@ -696,11 +696,6 @@ deps = {
'dep_type': 'cipd',
},
- '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': {
'url': Var('android_git') + '/platform/external/cherry@4f8fb08d33ca5ff05a1c638f04c85bbb8d8b52cc',
@@ -722,12 +717,6 @@ deps = {
'condition': 'checkout_android and not build_with_chromium',
},
-
- 'third_party/dawn': {
- 'url': Var('dawn_git') + '/dawn.git' + '@' + Var('dawn_revision'),
- 'condition': 'not build_with_chromium'
- },
-
'third_party/depot_tools': {
'url': Var('chromium_git') + '/chromium/tools/depot_tools.git@1fcc527019d786502b02f71b8b764ee674a40953',
'condition': 'not build_with_chromium',
@@ -809,11 +798,6 @@ deps = {
'condition': 'not build_with_chromium',
},
- 'third_party/llvm/src': {
- 'url': Var('chromium_git') + '/external/github.com/llvm/llvm-project@1fd8d3fea53e6e4573cdce55bd38ef0a7813a442',
- 'condition': 'not build_with_chromium',
- },
-
'third_party/jdk': {
'packages': [
{
@@ -1000,11 +984,6 @@ deps = {
'condition': 'checkout_android and not build_with_chromium',
},
- 'third_party/SwiftShader': {
- 'url': Var('swiftshader_git') + '/SwiftShader@7905fa19e456df5aa8e2233a7ec5832c9c6c287b',
- 'condition': 'not build_with_chromium',
- },
-
'third_party/turbine/cipd': {
'packages': [
{
@@ -1016,10 +995,6 @@ deps = {
'dep_type': 'cipd',
},
- '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@b9aab6c35f37e6532011ddab976c3ca71819c074',
'condition': 'not build_with_chromium',
+64
View File
@@ -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<size_t> checkedArraySize = elementSize;
checkedArraySize *= getOutermostArraySize();
- constArray = new TConstantUnion[checkedArraySize.ValueOrDie()];
+ constArray = new TConstantUnion[static_cast<size_t>(checkedArraySize.ValueOrDie())];
size_t elementOffset = 0u;
@@ -19,6 +19,8 @@
</PropertyGroup>
<ItemGroup>
<None Include="../../External/AvaloniaNative/osx-x64/native/avalonia-native-build.json" Pack="true" PackagePath="buildMetadata/osx-x64.json" />
<None Include="../../External/AvaloniaNative/osx-arm64/native/avalonia-native-build.json" Pack="true" PackagePath="buildMetadata/osx-arm64.json" />
<None Include="README.md" Pack="true" PackagePath="README.md" />
<None Include="lib/net8.0/_._" Pack="true" PackagePath="lib/net8.0/_._" />
<None Include="buildTransitive/StaticLink.Avalonia.Native.props" Pack="true" PackagePath="buildTransitive/$(PackageId).props" />
+30 -14
View File
@@ -4,32 +4,48 @@ Static native libraries for Avalonia single-file NativeAOT publishing.
## Install
```bash
dotnet add package StaticLink.Avalonia
Choose the static graphics package that matches the Avalonia and SkiaSharp major versions used by your application.
| Avalonia version | SkiaSharp version | `StaticLink.Avalonia` version |
| --- | --- | --- |
| 11 | 2.88.9 | `2.88.9-7151.10` |
| 11 | 3.119.4 | `3.119.4-7922.1` |
| 12 | 3.119.4 | `3.119.4-7922.1` |
| 12 | 3.119.4 | `3.119.4-7922.1` |
Example:
```xml
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.1.0" />
<PackageReference Include="StaticLink.Avalonia" Version="3.119.4-7922.1" />
</ItemGroup>
```
Or add it manually:
### macOS
```xml
<PackageReference Include="StaticLink.Avalonia" Version="3.119.2-7151.12" />
```
For macOS, also reference `StaticLink.Avalonia.Native`. This package is macOS-only and must match your Avalonia version because it contains `libAvaloniaNative.a`.
For macOS, also reference `StaticLink.Avalonia.Native`. This package contains `libAvaloniaNative.a`, so its version must match the Avalonia version used by the application.
```xml
<!-- Avalonia 11.3.14 -->
<PackageReference Include="StaticLink.Avalonia.Native" Version="11.3.14.1" />
<!-- Avalonia 12.1.0 -->
<PackageReference Include="StaticLink.Avalonia.Native" Version="12.1.0.1" />
```
Add only the `StaticLink.Avalonia.Native` reference matching your Avalonia version.
On macOS, only Avalonia 12 with skia3/4 supports Metal. Avalonia 11 fully static apps must use OpenGL or Software because its Metal path dynamically loads `libSkiaSharp`.
## Publish
```bash
dotnet publish -c Release -r win-x64 \
-p:PublishAot=true \
-p:SelfContained=true \
-p:PublishSingleFile=true \
-p:StripSymbols=true
dotnet publish -c Release -r win-x64 -p:PublishAot=true
```
Use the RID you need, such as `win-x86`, `linux-x64`, `linux-arm64`, `osx-arm64`, or `osx-x64`.
For macOS, avoid the Metal renderer for fully static output. Use OpenGL or Software.
## Native Package Automation
`.github/workflows/nuget-avalonia-native.yml` runs daily and checks the latest stable `Avalonia` version on NuGet.org. If `StaticLink.Avalonia.Native.<AvaloniaVersion>.1` does not exist, it builds both macOS architectures from the matching Avalonia source tag, runs NativeAOT smoke tests, and publishes the package with NuGet Trusted Publishing.
@@ -6,7 +6,7 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
<SuppressDependenciesWhenPacking>false</SuppressDependenciesWhenPacking>
<PackageId>StaticLink.Avalonia</PackageId>
<Version>3.119.2-7151.12</Version>
<Version>3.119.4-7922.2</Version>
<Authors>greepar</Authors>
<Company>greepar</Company>
<Description>Static SkiaSharp, HarfBuzzSharp, and ANGLE libraries for Avalonia NativeAOT single-file publishing.</Description>
@@ -19,11 +19,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="3.119.2" />
<PackageReference Include="SkiaSharp" Version="3.119.4" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
<None Include="../../build/linux-toolchain/release.lock.json" Condition="Exists('../../build/linux-toolchain/release.lock.json')" Pack="true" PackagePath="buildMetadata/sources.lock.json" />
<None Include="lib/net8.0/_._" Pack="true" PackagePath="lib/net8.0/_._" />
<None Include="buildTransitive/StaticLink.Avalonia.props" Pack="true" PackagePath="buildTransitive/$(PackageId).props" />
<None Include="buildTransitive/StaticLink.Avalonia.targets" Pack="true" PackagePath="buildTransitive/$(PackageId).targets" />
@@ -163,4 +163,19 @@
</ItemGroup>
<Delete Files="@(_StaticLinkAvaloniaFilesToDelete)" TreatErrorsAsWarnings="true" />
</Target>
<Target Name="StaticLinkAvaloniaAddZigRuntime"
BeforeTargets="LinkNative"
Condition="'$(PublishAot)' == 'true' and Exists('$(_StaticLinkAvaloniaNativeRoot)/zig-build.json')">
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith('win-'))">
<CppLinker>lld-link</CppLinker>
</PropertyGroup>
<ItemGroup>
<NativeLibrary Include="$(_StaticLinkAvaloniaNativeRoot)/zig-runtime/*.lib" Condition="$(RuntimeIdentifier.StartsWith('win-'))" />
<NativeLibrary Include="$(_StaticLinkAvaloniaNativeRoot)/zig-runtime/*.a" Condition="!$(RuntimeIdentifier.StartsWith('win-'))" />
<NativeLibrary Include="$(_StaticLinkAvaloniaNativeRoot)/libEGL_static.lib" Condition="Exists('$(_StaticLinkAvaloniaNativeRoot)/libEGL_static.lib')" />
<NativeLibrary Include="$(_StaticLinkAvaloniaNativeRoot)/libEGL_static.a" Condition="Exists('$(_StaticLinkAvaloniaNativeRoot)/libEGL_static.a')" />
<NativeSystemLibrary Include="X11;Xext;xcb;wayland-client;wayland-egl;ffi" Condition="$(RuntimeIdentifier.StartsWith('linux-'))" />
<LinkerArg Include="/lldmingw" Condition="$(RuntimeIdentifier.StartsWith('win-'))" />
</ItemGroup>
</Target>
</Project>
+29 -25
View File
@@ -1,49 +1,53 @@
> 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.
## Install
```bash
dotnet add package StaticLink.Avalonia
Choose the static graphics package that matches the Avalonia and SkiaSharp major versions used by your application.
| Avalonia version | SkiaSharp version | `StaticLink.Avalonia` version |
| --- | --- | --- |
| 11 | 2.88.9 | `2.88.9-7151.10` |
| 11 | 3.119.4 | `3.119.4-7922.1` |
| 12 | 3.119.4 | `3.119.4-7922.1` |
| 12 | 4.150.1 | `4.150.1-7922.1` |
Example:
```xml
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.1.0" />
<PackageReference Include="StaticLink.Avalonia" Version="4.150.1-7922.1" />
</ItemGroup>
```
Or add it manually:
### macOS
```xml
<PackageReference Include="StaticLink.Avalonia" Version="3.119.2-7151.12" />
```
`3.119.2-7151.12` supports Avalonia 11. If you need the SkiaSharp 2 line for an Avalonia 11 project, use this optional package variant:
```xml
<PackageReference Include="StaticLink.Avalonia" Version="2.88.9-7151.10" />
```
`2.88.9-7151.10` is only for Avalonia 11 projects that specifically want SkiaSharp 2.
For macOS, also reference `StaticLink.Avalonia.Native`. This package is macOS-only and must match your Avalonia version because it contains `libAvaloniaNative.a`.
For macOS, also reference `StaticLink.Avalonia.Native`. This package contains `libAvaloniaNative.a`, so its version must match the Avalonia version used by the application.
```xml
<!-- Avalonia 11.3.14 -->
<PackageReference Include="StaticLink.Avalonia.Native" Version="11.3.14.1" />
<!-- Avalonia 12.1.0 -->
<PackageReference Include="StaticLink.Avalonia.Native" Version="12.1.0.1" />
```
Add only the `StaticLink.Avalonia.Native` reference matching your Avalonia version.
On macOS, only Avalonia 12 with skia3/4 supports Metal. Avalonia 11 fully static apps must use OpenGL or Software because its Metal path dynamically loads `libSkiaSharp`.
## Publish
```bash
dotnet publish -c Release -r win-x64 \
-p:PublishAot=true \
-p:SelfContained=true \
-p:PublishSingleFile=true \
-p:StripSymbols=true
dotnet publish -c Release -r win-x64 -p:PublishAot=true
```
Use the RID you need, such as `win-x86`, `linux-x64`, `linux-arm64`, `osx-arm64`, or `osx-x64`.
For macOS, avoid the Metal renderer for fully static output. Use OpenGL or Software.
## Native Package Automation
`.github/workflows/nuget-avalonia-native.yml` runs daily and checks the latest stable `Avalonia` version on NuGet.org. If `StaticLink.Avalonia.Native.<AvaloniaVersion>.1` does not exist, it builds both macOS architectures from the matching Avalonia source tag, runs NativeAOT smoke tests, and publishes the package with NuGet Trusted Publishing.
Configure NuGet.org Trusted Publishing for this repository and the `nuget-avalonia-native.yml` workflow, then add the NuGet.org account name as the repository variable `NUGET_USER`. No long-lived NuGet API key is used by this workflow.
`.github/workflows/nuget-avalonia-native.yml` runs daily and checks the latest stable `Avalonia` version on NuGet.org. If `StaticLink.Avalonia.Native.<AvaloniaVersion>.1` does not exist, it builds both macOS architectures from the matching Avalonia source tag, runs NativeAOT smoke tests, and publishes the package with NuGet Trusted Publishing.
+7
View File
@@ -0,0 +1,7 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvaloniaStaticLinkSmoke.App">
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
@@ -0,0 +1,45 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
namespace AvaloniaStaticLinkSmoke;
public sealed partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var renderer = Environment.GetEnvironmentVariable("STATICLINK_MAC_RENDERER") ?? "opengl";
var autoClose = Environment.GetEnvironmentVariable("STATICLINK_SMOKE_AUTOCLOSE") == "1";
desktop.MainWindow = new MainWindow(autoClose ? CompleteSmoke : null);
void CompleteSmoke(string backend)
{
Dispatcher.UIThread.Post(() =>
{
if (!renderer.Equals("metal", StringComparison.OrdinalIgnoreCase) ||
backend.Equals("Metal", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"STATICLINK_SMOKE_READY={renderer}");
}
else
{
Console.Error.WriteLine($"Expected the Metal renderer, but the active Skia backend was {backend}.");
Environment.ExitCode = 1;
}
desktop.MainWindow?.Close();
});
}
}
base.OnFrameworkInitializationCompleted();
}
}
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="SmokeVersions.props" Condition="Exists('SmokeVersions.props')" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<PublishAot>true</PublishAot>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>false</PublishReadyToRun>
<InvariantGlobalization>true</InvariantGlobalization>
<AvaloniaVersion Condition="'$(AvaloniaVersion)' == ''">11.3.14</AvaloniaVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
<PackageReference Include="StaticLink.Avalonia" Version="$(StaticLinkVersion)" Condition="'$(StaticLinkVersion)' != ''" />
<PackageReference Include="StaticLink.Avalonia.Native" Version="$(StaticLinkNativeVersion)" Condition="'$(StaticLinkNativeVersion)' != ''" />
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvaloniaStaticLinkSmoke.MainWindow"
Width="420"
Height="220"
Title="Avalonia StaticLink Smoke">
<Border Padding="24">
<StackPanel Spacing="8" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Avalonia static link smoke" FontSize="24" FontWeight="SemiBold" HorizontalAlignment="Center" />
<TextBlock Text="NativeAOT single-file publish" HorizontalAlignment="Center" />
</StackPanel>
</Border>
</Window>
@@ -0,0 +1,60 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Skia;
namespace AvaloniaStaticLinkSmoke;
public sealed partial class MainWindow : Window
{
private readonly Action<string>? _renderResult;
private int _renderResultReported;
public MainWindow() : this(null)
{
}
public MainWindow(Action<string>? renderResult)
{
_renderResult = renderResult;
InitializeComponent();
}
public override void Render(DrawingContext context)
{
base.Render(context);
if (_renderResult is not null)
{
context.Custom(new MetalBackendProbe(new Rect(Bounds.Size), ReportMetalResult));
}
}
private void ReportMetalResult(string backend)
{
if (Interlocked.Exchange(ref _renderResultReported, 1) == 0)
{
_renderResult?.Invoke(backend);
}
}
private sealed class MetalBackendProbe(Rect bounds, Action<string> report) : ICustomDrawOperation
{
public Rect Bounds { get; } = bounds;
public void Dispose()
{
}
public bool Equals(ICustomDrawOperation? other) => false;
public bool HitTest(Point point) => false;
public void Render(ImmediateDrawingContext context)
{
var feature = context.TryGetFeature(typeof(ISkiaSharpApiLeaseFeature)) as ISkiaSharpApiLeaseFeature;
using var lease = feature?.Lease();
report(lease?.GrContext?.Backend.ToString() ?? "Software");
}
}
}
+56
View File
@@ -0,0 +1,56 @@
using Avalonia;
namespace AvaloniaStaticLinkSmoke;
internal static class Program
{
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
{
var builder = AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
if (OperatingSystem.IsWindows())
{
builder = builder.With(new Win32PlatformOptions
{
RenderingMode =
[
Win32RenderingMode.AngleEgl,
Win32RenderingMode.Software
]
});
}
if (OperatingSystem.IsLinux())
{
builder = builder.With(new X11PlatformOptions
{
RenderingMode =
[
X11RenderingMode.Glx,
X11RenderingMode.Software
]
});
}
if (OperatingSystem.IsMacOS())
{
var renderer = Environment.GetEnvironmentVariable("STATICLINK_MAC_RENDERER");
if (!string.Equals(renderer, "default", StringComparison.OrdinalIgnoreCase))
{
builder = builder.With(new AvaloniaNativePlatformOptions
{
RenderingMode = string.Equals(renderer, "metal", StringComparison.OrdinalIgnoreCase)
? [AvaloniaNativeRenderingMode.Metal]
: [AvaloniaNativeRenderingMode.OpenGl, AvaloniaNativeRenderingMode.Software]
});
}
}
return builder;
}
}
+72
View File
@@ -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"
]
}
+60
View File
@@ -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.
+38
View File
@@ -0,0 +1,38 @@
# 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 \
&& printf 'Package: *\nPin: origin "snapshot.debian.org"\nPin-Priority: 1001\n' > /etc/apt/preferences.d/staticlink-snapshot \
&& apt-get dist-upgrade -y --allow-downgrades \
&& 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 lld ld.lld lld-link ld64.lld; 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"]
@@ -0,0 +1,61 @@
# 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 python:3.12.12-slim-bookworm@sha256:593bd06efe90efa80dc4eee3948be7c0fde4134606dd40d8dd8dbcade98e669c AS python312
FROM ${TOOLCHAIN_IMAGE}
COPY --from=python312 /usr/local/ /usr/local/
ENV PYTHONPATH=/usr/lib/python3/dist-packages
RUN ldconfig && python3 -c 'import inspect, tarfile, six, requests, httplib2; assert "filter" in inspect.signature(tarfile.TarFile.extractall).parameters'
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
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
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/
+20
View File
@@ -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.
+24
View File
@@ -0,0 +1,24 @@
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
use_libpci = false
+21
View File
@@ -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"
+25
View File
@@ -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
@@ -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')
+242
View File
@@ -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()
@@ -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"
}
@@ -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)
+100
View File
@@ -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()
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -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"
}
}
}
}
@@ -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."
}
+27
View File
@@ -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)
@@ -0,0 +1,2 @@
# Unified diffs contain space-prefixed empty context lines.
*.patch -whitespace
@@ -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" ]
@@ -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(
@@ -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<size_t> checkedArraySize = elementSize;
checkedArraySize *= getOutermostArraySize();
- constArray = new TConstantUnion[checkedArraySize.ValueOrDie()];
+ constArray = new TConstantUnion[static_cast<size_t>(checkedArraySize.ValueOrDie())];
size_t elementOffset = 0u;
@@ -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" ]
}
@@ -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
@@ -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"
@@ -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
@@ -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;
+81
View File
@@ -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 <string>\nextern "C" int answer(void) { std::string s="test"; return int(s.size()); }\n',
"windows": '''#include <windows.h>
#include <dwrite_3.h>
#include <xpsobjectmodel.h>
// 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 <ApplicationServices/ApplicationServices.h>
#import <Metal/Metal.h>
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())
@@ -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."
}
+30
View File
@@ -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
+27
View File
@@ -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
+28
View File
@@ -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
+123
View File
@@ -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())
+3
View File
@@ -0,0 +1,3 @@
#include <EGL/egl.h>
#include <GLES2/gl2.h>
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; }
+31
View File
@@ -0,0 +1,31 @@
#include <stdio.h>
#include <stdint.h>
#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;
}
+7
View File
@@ -0,0 +1,7 @@
{
"skiasharp_version": "3.119.4",
"skiasharp_revision": "f568ac94dd768ef9a2f593537cfde2dd0d348ef5",
"angle_branch": "7922",
"angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66",
"skia_revision": "7dbfc07dd33181f84e0958afb7ee805c6c769f0b"
}
+89
View File
@@ -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()
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""Validate the actual release container before compiling native libraries."""
import argparse
import inspect
import json
from pathlib import Path
import runpy
import shutil
import subprocess
import sys
import tarfile
import tempfile
def run(command):
print("+", " ".join(map(str, command)), flush=True)
return subprocess.check_output(list(map(str, command)), text=True, stderr=subprocess.STDOUT)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--rids", default="[]")
args = parser.parse_args()
args.output.mkdir(parents=True, exist_ok=True)
report = {"python": sys.version, "targets": {}, "passed": False}
try:
if "filter" not in inspect.signature(tarfile.TarFile.extractall).parameters:
raise RuntimeError("depot_tools requires tarfile extraction filters")
for module in ("six", "requests", "httplib2"):
__import__(module)
run([sys.executable, "/opt/depot_tools/gclient.py", "help"])
for tool in ("git", "ninja", "clang", "llvm-ar", "lld", "pkg-config", "cmake", "patch", "file", "dotnet", "zig"):
if not shutil.which(tool):
raise RuntimeError("Missing tool: " + tool)
report["zig"] = run(["zig", "version"]).strip()
if report["zig"] != "0.14.1":
raise RuntimeError("Unexpected Zig version")
report["dotnet_sdks"] = run(["dotnet", "--list-sdks"]).splitlines()
for major in ("8.", "10."):
if not any(version.startswith(major) for version in report["dotnet_sdks"]):
raise RuntimeError("Missing .NET SDK " + major)
adapter = Path(__file__).with_name("zig-cross.py")
targets = runpy.run_path(str(adapter))["TARGETS"]
requested = json.loads(args.rids)
if not isinstance(requested, list) or any(rid not in targets for rid in requested):
raise ValueError("Expected a JSON array of supported RIDs")
if requested:
targets = {rid: targets[rid] for rid in requested}
sdk = Path("/opt/macos-sdk/MacOSX15.5.sdk")
if json.loads((sdk / "SDKSettings.json").read_text())["Version"] != "15.5":
raise RuntimeError("Unexpected macOS SDK version")
with tempfile.TemporaryDirectory(prefix="staticlink-environment-") as temporary:
work = Path(temporary)
plain = work / "main.c"
plain.write_text("int main(void) { return 0; }\n")
cpp = work / "probe.cpp"
for rid in targets:
target_work = work / rid
command = [sys.executable, adapter, "--target", rid, "--output", target_work,
"--sdk", sdk, "--mingw-headers", "/opt/mingw/usr/share/mingw-w64/include"]
source = "#include <string>\nint value() { return int(std::string(\\\"ok\\\").size()); }\n".replace('\\\"', '"')
if rid.startswith("linux"):
sysroot = Path("/opt/sysroots") / rid
if not (sysroot / "usr/include").is_dir():
raise RuntimeError("Missing sysroot: " + rid)
if not (sysroot / "usr/include/wayland-client.h").is_file():
raise RuntimeError("Missing Wayland headers: " + rid)
command += ["--linux-sysroot", sysroot, "--linux-headers", "/opt/staticlink/linux-headers"]
source += "#include <fontconfig/fontconfig.h>\n#include <ffi.h>\n#include <ft2build.h>\n#include FT_FREETYPE_H\n"
elif rid.startswith("win"):
source += "#include <windows.h>\n#include <dwrite_3.h>\n#include <XpsObjectModel.h>\n#include <windows.ui.composition.h>\n"
cpp.write_text(source)
run(command)
run([target_work / "cxx", "-std=c++17", "-c", cpp, "-o", target_work / "cpp.o"])
run([target_work / "cc", "-c", plain, "-o", target_work / "main.o"])
run([target_work / "ar", "rcs", target_work / "probe.a", target_work / "main.o"])
executable = target_work / ("probe.exe" if rid.startswith("win") else "probe")
run([target_work / "cc", plain, "-o", executable])
if rid.startswith("osx"):
objc = work / "probe.mm"
objc.write_text("#import <Cocoa/Cocoa.h>\n#import <Metal/Metal.h>\nint value() { return sizeof(CFIndex); }\n")
run([target_work / "cxx", "-fobjc-arc", "-fblocks", "-c", objc, "-o", target_work / "objc.o"])
report["targets"][rid] = {
"passed": True,
"object": run(["file", target_work / "main.o"]).strip(),
"executable": run(["file", executable]).strip(),
}
print(rid + ": C/C++ compile, archive and executable link PASS", flush=True)
report["passed"] = True
except Exception as error:
report["error"] = str(error)
if isinstance(error, subprocess.CalledProcessError):
report["error_output"] = error.output
print(error.output, flush=True)
raise
finally:
(args.output / "environment-validation.json").write_text(json.dumps(report, indent=2) + "\n")
if __name__ == "__main__":
main()
@@ -0,0 +1,7 @@
{
"skiasharp_version": "2.88.9",
"skiasharp_revision": "9699cdf046a989423a5e5eca68e7fd813486d81c",
"angle_branch": "7922",
"angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66",
"skia_revision": "4bed689c9c9eb77a120c6a9d54af6a572c85d1c2"
}
@@ -0,0 +1,7 @@
{
"skiasharp_version": "3.119.1",
"skiasharp_revision": "cc78b5933d23e6383db5d246e70db915770d55d6",
"angle_branch": "7922",
"angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66",
"skia_revision": "40f75dc0051d141913c07c20d4c19590c7da0cb7"
}
@@ -0,0 +1,7 @@
{
"skiasharp_version": "3.119.2",
"skiasharp_revision": "2888c737ad016d584c74525e2d35db5097ea8576",
"angle_branch": "7922",
"angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66",
"skia_revision": "927041a58f130e0dd0562ba86cb4170989ad39e9"
}
@@ -0,0 +1,7 @@
{
"skiasharp_version": "3.119.4",
"skiasharp_revision": "f568ac94dd768ef9a2f593537cfde2dd0d348ef5",
"angle_branch": "7922",
"angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66",
"skia_revision": "7dbfc07dd33181f84e0958afb7ee805c6c769f0b"
}
@@ -0,0 +1,7 @@
{
"skiasharp_version": "4.150.1",
"skiasharp_revision": "c3e4f4c20e1f23ab74d31a8838a5bd6dc55365f2",
"angle_branch": "7922",
"angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66",
"skia_revision": "0aa2d542e833ffd1d4d1b68a5152375e7f65ce11"
}
@@ -0,0 +1,7 @@
{
"skiasharp_version": "4.150.2",
"skiasharp_revision": "fd1fdd5239f826514651a4ada5d7bb3dcec2fb43",
"angle_branch": "7922",
"angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66",
"skia_revision": "b70519562e2d26d3cf56286d3a13ca25152cfae4"
}
@@ -0,0 +1,7 @@
{
"skiasharp_version": "4.150.3",
"skiasharp_revision": "f8a404ff3375872d042404a29189ceb8e3001a41",
"angle_branch": "7922",
"angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66",
"skia_revision": "eb86e4b3ee034085b62d3980b3d94629862441e6"
}
@@ -0,0 +1,7 @@
{
"skiasharp_version": "4.150.5",
"skiasharp_revision": "16f82682f756217654eb1a152cdb639e358b8976",
"angle_branch": "7922",
"angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66",
"skia_revision": "579fa81eeed6906732e27b7d95149538584dd20b"
}
@@ -0,0 +1,7 @@
{
"skiasharp_version": "4.153.1",
"skiasharp_revision": "4783f51448f9b070dda4f87b83e941c9599e466e",
"angle_branch": "7922",
"angle_revision": "7e08726e32026a0bbabd6ec8a1a90f5ab533ed66",
"skia_revision": "45afab4f1f0921f3feb97f58cf89b136fffa85e6"
}
+69
View File
@@ -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')
+305
View File
@@ -0,0 +1,305 @@
#!/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 += ['-idirafter', config['linux_abi_headers']]
if config.get('linux_headers'):
args += ['-idirafter', config['linux_headers'], '-idirafter', 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")
libtool = shutil.which('llvm-libtool-darwin') or shutil.which('llvm-libtool-darwin-16')
if not libtool:
raise SystemExit('The pinned image must provide llvm-libtool-darwin to flatten archive dependencies')
command = [libtool, '-static', '-o', 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', 'expat.h', 'expat_external.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)
gl_headers = abi / 'GL'
gl_headers.mkdir(exist_ok=True)
for prefix in (root / 'usr/include/GL', Path('/usr/include/GL')):
for header in prefix.glob('*.h'):
destination = gl_headers / header.name
if not destination.exists():
destination.write_bytes(header.read_bytes())
if not (gl_headers / 'glx.h').is_file():
raise SystemExit('The pinned Linux SDK/image lacks GL/glx.h')
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())
+5
View File
@@ -0,0 +1,5 @@
{
"branches": [
{"major": 3, "ref": "skia3", "angle_branch": "7922", "revision": "2"}
]
}
+14
View File
@@ -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")
+46
View File
@@ -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}"
+135 -84
View File
@@ -6,12 +6,13 @@ WORK_DIR="${WORK_DIR:-$ROOT_DIR/External/NativeStatic/.work}"
TARGET_CPU="${TARGET_CPU:-x64}"
RID="${RID:-linux-$TARGET_CPU}"
OUTPUT_DIR="${OUTPUT_DIR:-$ROOT_DIR/External/NativeStatic/$RID}"
SKIASHARP_VERSION="${SKIASHARP_VERSION:-3.119.2}"
ANGLE_BRANCH="${ANGLE_BRANCH:-7151}"
SKIASHARP_VERSION="${SKIASHARP_VERSION:-4.150.1}"
ANGLE_BRANCH="${ANGLE_BRANCH:-7922}"
BUILD_JOBS="${BUILD_JOBS:-$(nproc)}"
ANGLE_PATCH_DIR="${ANGLE_PATCH_DIR:-$ROOT_DIR/External/NativeStatic/patches}"
LLVM_AR="${LLVM_AR:-llvm-ar-19}"
SKIA_DEPS_RETRIES="${SKIA_DEPS_RETRIES:-3}"
SOURCE_DOWNLOAD_ATTEMPTS="${SOURCE_DOWNLOAD_ATTEMPTS:-5}"
usage() {
cat <<'USAGE'
@@ -20,13 +21,14 @@ Usage: scripts/build-linux-static-graphics.sh [skia|angle|all]
Environment:
WORK_DIR Source/build cache directory. Default: External/NativeStatic/.work
OUTPUT_DIR Final static library directory. Default: External/NativeStatic/linux-$TARGET_CPU
SKIASHARP_VERSION SkiaSharp release branch version. Default: 3.119.2
ANGLE_BRANCH ANGLE chromium branch. Default: 7151
SKIASHARP_VERSION SkiaSharp release branch version. Default: 4.150.1
ANGLE_BRANCH ANGLE chromium branch. Default: 7922
TARGET_CPU GN target_cpu. Default: x64. Supported: x64, arm64
RID Output RID. Default: linux-$TARGET_CPU
BUILD_JOBS Ninja parallelism. Default: nproc
ANGLE_PATCH_DIR ANGLE patch directory. Default: External/NativeStatic/patches
LLVM_AR llvm-ar command used to expand thin archives. Default: llvm-ar-19
SOURCE_DOWNLOAD_ATTEMPTS Maximum attempts per source download. Default: 5
USAGE
}
@@ -53,10 +55,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
@@ -134,26 +146,54 @@ PY
export PYTHONPATH="$module_dir:${PYTHONPATH:-}"
}
retry_source_download() {
local attempt status
if [[ ! "$SOURCE_DOWNLOAD_ATTEMPTS" =~ ^[1-9][0-9]*$ ]]; then
echo "SOURCE_DOWNLOAD_ATTEMPTS must be a positive integer" >&2
return 1
fi
for ((attempt = 1; attempt <= SOURCE_DOWNLOAD_ATTEMPTS; attempt++)); do
if "$@"; then
return 0
else
status=$?
fi
if ((attempt == SOURCE_DOWNLOAD_ATTEMPTS)); then
echo "Source download failed after $attempt attempts; stopping" >&2
return "$status"
fi
echo "Source download failed ($attempt/$SOURCE_DOWNLOAD_ATTEMPTS); retrying in $((attempt * 10)) seconds" >&2
sleep "$((attempt * 10))"
done
}
sync_skiasharp() {
local src="$WORK_DIR/SkiaSharp-$SKIASHARP_VERSION"
local revision="${SKIASHARP_REVISION:-release/$SKIASHARP_VERSION}"
if [[ ! -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"
git -C "$src" checkout -q FETCH_HEAD
git init "$src" >&2 || return $?
git -C "$src" remote add origin https://github.com/mono/SkiaSharp.git || return $?
fi
git -C "$src" submodule update --init --depth 1 externals/skia >&2
retry_source_download git -C "$src" fetch --depth 1 origin "$revision" >&2 || return $?
git -C "$src" checkout --detach FETCH_HEAD >&2 || return $?
retry_source_download git -C "$src" submodule update --init --depth 1 externals/skia >&2 || return $?
echo "$src"
}
prepare_skia_git_sync_deps() {
local sync_deps="$1/tools/git-sync-deps"
python3 - "$sync_deps" <<'PY'
import re
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
text = path.read_text()
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)
deps_path.write_text(deps)
old = " multithread(git_checkout_to_directory, list_of_arg_lists)"
new = " for args in list_of_arg_lists:\n git_checkout_to_directory(*args)"
if old in text:
@@ -163,21 +203,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() {
@@ -227,7 +267,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" \
@@ -243,12 +283,13 @@ EOF_ARGS
sync_angle() {
local src="$WORK_DIR/ANGLE-$ANGLE_BRANCH"
local revision="${ANGLE_REVISION:-chromium/$ANGLE_BRANCH}"
if [[ ! -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"
git -C "$src" checkout -q FETCH_HEAD
git init "$src" >&2 || return $?
git -C "$src" remote add origin https://github.com/google/angle.git || return $?
fi
retry_source_download git -C "$src" fetch --depth 1 origin "$revision" >&2 || return $?
git -C "$src" checkout --detach FETCH_HEAD >&2 || return $?
echo "$src"
}
@@ -267,15 +308,15 @@ build_angle() {
patch_depot_tools_python_deps "$src/third_party/depot_tools"
prepend_python_module_path six
prepare_angle_gcs_artifacts "$src"
prepare_musl_clang_toolchain
fi
prepare_musl_clang_runtime
prepare_musl_libstdcxx_headers
gclient sync -f -D -R
local out_dir="$src/out/linux-static-$TARGET_CPU"
local angle_is_clang="false"
if is_musl_rid; then
angle_is_clang="true"
local angle_is_clang="true"
if [[ "$TARGET_CPU" == "arm64" ]] && ! is_musl_rid; then
angle_is_clang="false"
fi
mkdir -p "$out_dir"
cat >"$out_dir/args.gn" <<EOF_ARGS
@@ -296,8 +337,9 @@ angle_enable_swiftshader = false
angle_enable_vulkan = false
angle_enable_wgpu = false
EOF_ARGS
append_angle_musl_gn_args "$out_dir/args.gn"
if is_musl_rid; then
printf 'clang_base_path = "%s/clang"\nclang_use_chrome_plugins = false\nclang_version = "%s"\n' "$WORK_DIR" "$(clang -dumpversion | cut -d. -f1)" >>"$out_dir/args.gn"
fi
gn gen "$out_dir"
ninja -C "$out_dir" -j "$BUILD_JOBS" libANGLE_static libGLESv2_static
@@ -380,53 +422,6 @@ download_angle_gcs_artifact() {
fi
}
append_angle_musl_gn_args() {
local args_file="$1"
if is_musl_rid; then
cat >>"$args_file" <<'EOF_ARGS'
clang_base_path = "/usr"
clang_use_chrome_plugins = false
EOF_ARGS
fi
}
prepare_musl_clang_runtime() {
if ! is_musl_rid; then
return 0
fi
local arch
local gnu_triple
case "$TARGET_CPU" in
x64)
arch="x86_64"
gnu_triple="x86_64-unknown-linux-gnu"
;;
arm64)
arch="aarch64"
gnu_triple="aarch64-unknown-linux-gnu"
;;
*)
echo "Unsupported musl ANGLE target_cpu for clang runtime: $TARGET_CPU" >&2
return 1
;;
esac
local src
src="$(find /usr/lib/llvm* /usr/lib/clang -path "*/${arch}-alpine-linux-musl/libclang_rt.builtins-${arch}.a" -print -quit 2>/dev/null || true)"
if [[ -z "$src" ]]; then
echo "Could not find Alpine compiler-rt builtins for $arch" >&2
return 1
fi
local clang_version
clang_version="$(clang -print-resource-dir | awk -F/ '{print $NF}')"
local dest_dir="/usr/lib/clang/$clang_version/lib/$gnu_triple"
mkdir -p "$dest_dir"
ln -sf "$src" "$dest_dir/libclang_rt.builtins.a"
}
prepare_musl_libstdcxx_headers() {
if ! is_musl_rid; then
return 0
@@ -466,6 +461,60 @@ prepare_musl_libstdcxx_headers() {
ln -sfn "$src_dir" "$dest_dir"
}
prepare_musl_clang_toolchain() {
local arch
local gnu_triple
case "$TARGET_CPU" in
x64)
arch="x86_64"
gnu_triple="x86_64-unknown-linux-gnu"
;;
arm64)
arch="aarch64"
gnu_triple="aarch64-unknown-linux-gnu"
;;
*)
echo "Unsupported musl ANGLE target_cpu: $TARGET_CPU" >&2
return 1
;;
esac
local clang_version
clang_version="$(clang -dumpversion | cut -d. -f1)"
local toolchain_dir="$WORK_DIR/clang"
local runtime
runtime="$(find /usr/lib/llvm* /usr/lib/clang -path "*${arch}*" -name 'libclang_rt.builtins*.a' -print -quit 2>/dev/null || true)"
if [[ -z "$runtime" ]]; then
echo "Could not find Alpine compiler-rt builtins for $arch" >&2
return 1
fi
mkdir -p "$toolchain_dir/bin" "$toolchain_dir/lib/clang/$clang_version/lib/$gnu_triple"
ln -sf "$runtime" "$toolchain_dir/lib/clang/$clang_version/lib/$gnu_triple/libclang_rt.builtins.a"
cat >"$toolchain_dir/bin/clang-wrapper.py" <<'PY'
#!/usr/bin/env python3
import os
import sys
blocked = (
"-fdiagnostics-show-inlining-chain",
"-fno-lifetime-dse",
"-fsanitize-ignore-for-ubsan-feature=",
)
args = [arg for arg in sys.argv[1:] if not arg.startswith(blocked)]
compiler = "/usr/bin/clang++" if os.path.basename(sys.argv[0]) == "clang++" else "/usr/bin/clang"
os.execv(compiler, [compiler, *args])
PY
chmod +x "$toolchain_dir/bin/clang-wrapper.py"
ln -sf clang-wrapper.py "$toolchain_dir/bin/clang"
ln -sf clang-wrapper.py "$toolchain_dir/bin/clang++"
for tool in llvm-ar llvm-ranlib llvm-nm; do
if command -v "$tool" >/dev/null 2>&1; then
ln -sf "$(command -v "$tool")" "$toolchain_dir/bin/$tool"
fi
done
}
copy_first_existing() {
local dest="$1"
shift
@@ -530,4 +579,6 @@ main() {
esac
}
main "$@"
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
main "$@"
fi
@@ -26,7 +26,12 @@ if git -C "$src" config -f .gitmodules --get-regexp path | grep -q 'external/Num
fi
if [[ ! -f "$src/native/Avalonia.Native/inc/avalonia-native.h" ]]; then
bash "$src/native/Avalonia.Native/generate-headers.sh"
header_project="$src/native/Avalonia.Native/Avalonia.Native.macOS.proj"
if [[ -f "$header_project" ]]; then
dotnet build "$header_project" -t:GenerateMicroComItems
else
bash "$src/native/Avalonia.Native/generate-headers.sh"
fi
fi
project="$src/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj"
+3 -1
View File
@@ -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" \
+25 -6
View File
@@ -6,8 +6,8 @@ WORK_DIR="${WORK_DIR:-$ROOT_DIR/External/NativeStatic/.work}"
TARGET_CPU="${TARGET_CPU:-arm64}"
RID="${RID:-osx-$TARGET_CPU}"
OUTPUT_DIR="${OUTPUT_DIR:-$ROOT_DIR/External/NativeStatic/$RID}"
SKIASHARP_VERSION="${SKIASHARP_VERSION:-3.119.2}"
ANGLE_BRANCH="${ANGLE_BRANCH:-7151}"
SKIASHARP_VERSION="${SKIASHARP_VERSION:-4.150.1}"
ANGLE_BRANCH="${ANGLE_BRANCH:-7922}"
BUILD_JOBS="${BUILD_JOBS:-$(sysctl -n hw.ncpu)}"
ANGLE_PATCH_DIR="${ANGLE_PATCH_DIR:-$ROOT_DIR/External/NativeStatic/patches}"
SKIA_DEPS_RETRIES="${SKIA_DEPS_RETRIES:-3}"
@@ -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"
}
@@ -68,11 +75,17 @@ sync_skiasharp() {
prepare_skia_git_sync_deps() {
local sync_deps="$1/tools/git-sync-deps"
python3 - "$sync_deps" <<'PY'
import re
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
text = path.read_text()
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)
deps_path.write_text(deps)
old = " multithread(git_checkout_to_directory, list_of_arg_lists)"
new = " for args in list_of_arg_lists:\n git_checkout_to_directory(*args)"
if old in text:
@@ -112,10 +125,12 @@ build_skia() {
cat >"$out_dir/args.gn" <<EOF_ARGS
target_os = "mac"
target_cpu = "$TARGET_CPU"
min_macos_version = "10.13"
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
@@ -139,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"
@@ -154,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"
}
+51 -6
View File
@@ -7,8 +7,8 @@ $ErrorActionPreference = "Stop"
$RootDir = Split-Path -Parent $PSScriptRoot
$WorkDir = if ($env:WORK_DIR) { $env:WORK_DIR } else { Join-Path $RootDir "External\NativeStatic\.work" }
$SkiaSharpVersion = if ($env:SKIASHARP_VERSION) { $env:SKIASHARP_VERSION } else { "3.119.2" }
$AngleBranch = if ($env:ANGLE_BRANCH) { $env:ANGLE_BRANCH } else { "7151" }
$SkiaSharpVersion = if ($env:SKIASHARP_VERSION) { $env:SKIASHARP_VERSION } else { "4.150.1" }
$AngleBranch = if ($env:ANGLE_BRANCH) { $env:ANGLE_BRANCH } else { "7922" }
$TargetCpu = if ($env:TARGET_CPU) { $env:TARGET_CPU } else { "x64" }
$Rid = if ($env:RID) { $env:RID } else { "win-$TargetCpu" }
$OutputDir = if ($env:OUTPUT_DIR) { $env:OUTPUT_DIR } else { Join-Path $RootDir "External\NativeStatic\$Rid" }
@@ -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,18 +349,50 @@ 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
}
function Patch-WinX86SkiaLinker($SkiaDir) {
if ($TargetCpu -ne "x86") {
return
}
$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) {
if ($text -match '(?m)^ int r = 0;\r?$') {
return
}
throw "Unable to patch win-x86 sk_linker JSON keep-alive references."
}
Set-Content -Path $linkerPath -Value $patched -NoNewline -Encoding UTF8
}
function Prepare-SkiaGitSyncDeps($SkiaDir) {
$syncDeps = Join-Path $SkiaDir "tools\git-sync-deps"
$text = Get-Content -Path $syncDeps -Raw
$depsPath = Join-Path $SkiaDir "DEPS"
if (Test-Path $depsPath) {
$depsText = Get-Content -Path $depsPath -Raw
$depsText = [regex]::Replace($depsText, '(?m)^\s*"third_party/externals/dng_sdk"\s*:\s*"[^"]+",\s*\r?\n', '')
Set-Content -Path $depsPath -Value $depsText -NoNewline -Encoding UTF8
}
$old = " multithread(git_checkout_to_directory, list_of_arg_lists)"
$new = " for args in list_of_arg_lists:`n git_checkout_to_directory(*args)"
if ($text.Contains($old)) {
@@ -384,6 +421,7 @@ function Build-Skia {
Ensure-DepotTools
$src = Sync-SkiaSharp
$skiaDir = Join-Path $src "externals\skia"
Patch-WinX86SkiaLinker $skiaDir
if (-not (Test-Path (Join-Path $skiaDir "bin\gn.exe"))) {
Invoke-SkiaGitSyncDeps $skiaDir
}
@@ -422,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
@@ -455,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
}
+47
View File
@@ -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))
+20 -128
View File
@@ -14,144 +14,36 @@ param(
$ErrorActionPreference = "Stop"
$templateDir = Join-Path (Split-Path -Parent $PSScriptRoot) "Smoke/AvaloniaStaticLinkSmoke"
if (-not (Test-Path (Join-Path $templateDir "AvaloniaStaticLinkSmoke.csproj"))) {
throw "Smoke project template was not found: $templateDir"
}
New-Item -ItemType Directory -Path $ProjectDir -Force | Out-Null
Copy-Item (Join-Path $templateDir "*") -Destination $ProjectDir -Recurse -Force
$nativeReference = ""
if ($StaticLinkNativeVersion) {
$nativeReference = "`n <PackageReference Include=`"StaticLink.Avalonia.Native`" Version=`"$StaticLinkNativeVersion`" />"
}
$escapedNuGetSource = [System.Security.SecurityElement]::Escape($NuGetSource)
$escapedAvaloniaVersion = [System.Security.SecurityElement]::Escape($AvaloniaVersion)
$escapedStaticLinkVersion = [System.Security.SecurityElement]::Escape($StaticLinkVersion)
$escapedStaticLinkNativeVersion = [System.Security.SecurityElement]::Escape($StaticLinkNativeVersion)
$staticLinkReference = ""
if ($StaticLinkVersion) {
$staticLinkReference = "`n <PackageReference Include=`"StaticLink.Avalonia`" Version=`"$StaticLinkVersion`" />"
}
@"
<Project>
<PropertyGroup>
<AvaloniaVersion>$escapedAvaloniaVersion</AvaloniaVersion>
<StaticLinkVersion>$escapedStaticLinkVersion</StaticLinkVersion>
<StaticLinkNativeVersion>$escapedStaticLinkNativeVersion</StaticLinkNativeVersion>
</PropertyGroup>
</Project>
"@ | Set-Content -Path (Join-Path $ProjectDir "SmokeVersions.props") -Encoding UTF8
@"
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="local-staticlink" value="$NuGetSource" />
<add key="local-staticlink" value="$escapedNuGetSource" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
"@ | Set-Content -Path (Join-Path $ProjectDir "NuGet.config") -Encoding UTF8
@"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<PublishAot>true</PublishAot>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>false</PublishReadyToRun>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="$AvaloniaVersion" />
<PackageReference Include="Avalonia.Desktop" Version="$AvaloniaVersion" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="$AvaloniaVersion" />$staticLinkReference$nativeReference
</ItemGroup>
</Project>
"@ | Set-Content -Path (Join-Path $ProjectDir "AvaloniaStaticLinkSmoke.csproj") -Encoding UTF8
@"
using Avalonia;
namespace AvaloniaStaticLinkSmoke;
internal static class Program
{
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
{
var builder = AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
if (OperatingSystem.IsMacOS())
{
builder = builder.With(new AvaloniaNativePlatformOptions
{
RenderingMode = [AvaloniaNativeRenderingMode.Software]
});
}
return builder;
}
}
"@ | Set-Content -Path (Join-Path $ProjectDir "Program.cs") -Encoding UTF8
@"
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace AvaloniaStaticLinkSmoke;
public sealed partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}
"@ | Set-Content -Path (Join-Path $ProjectDir "App.axaml.cs") -Encoding UTF8
@"
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvaloniaStaticLinkSmoke.App">
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
"@ | Set-Content -Path (Join-Path $ProjectDir "App.axaml") -Encoding UTF8
@"
using Avalonia.Controls;
namespace AvaloniaStaticLinkSmoke;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
"@ | Set-Content -Path (Join-Path $ProjectDir "MainWindow.axaml.cs") -Encoding UTF8
@"
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvaloniaStaticLinkSmoke.MainWindow"
Width="420"
Height="220"
Title="Avalonia StaticLink Smoke">
<Border Padding="24">
<StackPanel Spacing="8" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Avalonia $AvaloniaVersion" FontSize="24" FontWeight="SemiBold" HorizontalAlignment="Center" />
<TextBlock Text="StaticLink NativeAOT smoke publish" HorizontalAlignment="Center" />
</StackPanel>
</Border>
</Window>
"@ | Set-Content -Path (Join-Path $ProjectDir "MainWindow.axaml") -Encoding UTF8
+60
View File
@@ -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)" || exit $?
retry_source_download 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)" || exit $?
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" && retry_source_download 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"
+43
View File
@@ -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}))
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TOOLCHAIN_IMAGE="ghcr.io/greepar/staticlink.avalonia-zig@sha256:a88b5ea9f76f659978cda36dda06233d7fdc9121046eedf945044fc908d24b85"
export CROSS_RID=linux-x64
AGENT_HOME="$(getent passwd "$(id -u)" | cut -d: -f6)"
if [ -z "$AGENT_HOME" ] || [ "$AGENT_HOME" = / ]; then
echo "Cannot find a persistent home directory for the agent user" >&2
exit 1
fi
CACHE_KEY="$(printf '%s\n' "${CI_REPO_OWNER:?}/${CI_REPO_NAME:?}" "${CI_COMMIT_BRANCH:?}" "$TOOLCHAIN_IMAGE" "$CROSS_RID" | sha256sum | cut -c1-24)"
CACHE_ROOT="$AGENT_HOME/.cache/staticlink-woodpecker/$CACHE_KEY"
RESULTS="$AGENT_HOME/staticlink-ci-artifacts/${CI_REPO_OWNER}/${CI_REPO_NAME}/${CI_COMMIT_BRANCH}/${CI_PIPELINE_NUMBER:?}"
mkdir -p "$CACHE_ROOT"
exec 9>"$CACHE_ROOT/build.lock"
echo "Waiting for exclusive access to persistent cache: $CACHE_ROOT"
flock 9
if [ ! -d "$CACHE_ROOT/source/.git" ]; then
git clone --no-hardlinks "$SCRIPT_ROOT" "$CACHE_ROOT/source"
fi
git -C "$CACHE_ROOT/source" fetch --depth=1 "$SCRIPT_ROOT" HEAD
git -C "$CACHE_ROOT/source" checkout --force --detach FETCH_HEAD
cd "$CACHE_ROOT/source"
cp build/linux-toolchain/sources.lock.json build/linux-toolchain/release.lock.json
mkdir -p "$RESULTS/environment"
df -h .
free -h
command -v docker
docker version
if [ "$(docker info --format '{{.OSType}}/{{.Architecture}}')" != "linux/x86_64" ]; then
echo "Requires a Linux x86_64 Docker daemon on the WSL agent" >&2
exit 1
fi
AVAILABLE_KB="$(df -Pk . | awk 'NR == 2 {print $4}')"
if [ "$AVAILABLE_KB" -lt 41943040 ]; then
echo "Need at least 40 GiB free before pulling the toolchain and compiling; more is recommended" >&2
exit 1
fi
echo "Persistent test results: $RESULTS"
exec > >(tee "$RESULTS/build.log") 2>&1
trap 'result=$?; trap - EXIT; printf "%s\n" "$result" > "$RESULTS/exit-code"; echo "Results retained at $RESULTS"; exit "$result"' EXIT
git rev-parse HEAD > "$RESULTS/commit"
printf '%s\n' "$TOOLCHAIN_IMAGE" > "$RESULTS/toolchain-image"
docker pull "$TOOLCHAIN_IMAGE"
docker run --rm --user "$(id -u):$(id -g)" \
-e HOME=/tmp -e DOTNET_CLI_HOME=/tmp -e ZIG_GLOBAL_CACHE_DIR=/tmp/zig-cache \
-v "$RESULTS/environment:/validation" --entrypoint python3 \
"$TOOLCHAIN_IMAGE" /opt/staticlink/validate-toolchain.py \
--output /validation --rids '[]'
mkdir -p External/NativeStatic/.work/home
docker run --rm --user "$(id -u):$(id -g)" \
-e HOME=/src/External/NativeStatic/.work/home -e CROSS_RID \
-e CCACHE_DIR=/src/External/NativeStatic/.work/ccache -e CCACHE_MAXSIZE=1G \
-e ZIG_GLOBAL_CACHE_DIR=/src/External/NativeStatic/.work/zig-global \
-v "$PWD:/src" -v "$RESULTS:/results" -w /src --entrypoint bash "$TOOLCHAIN_IMAGE" -c '
set -euo pipefail
export SOURCE_LOCK=/src/build/linux-toolchain/release.lock.json
source scripts/prepare-cross-sources.sh
python3 scripts/woodpecker-nine-platforms.py \
--sources-manifest "$WORK_DIR/cross-sources.json" --results /results
'
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
import argparse
from concurrent.futures import ThreadPoolExecutor, as_completed
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import time
import zipfile
RIDS = (
'osx-arm64', 'osx-x64', 'linux-x64', 'linux-arm64',
'linux-musl-x64', 'linux-musl-arm64', 'win-x64', 'win-x86', 'win-arm64',
)
def run(command, logfile, rid=None, env=None):
prefix = f'[{rid}] ' if rid else ''
print(prefix + '+ ' + ' '.join(map(str, command)), flush=True)
with logfile.open('a') as output:
process = subprocess.Popen(list(map(str, command)), stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True, env=env,
errors='replace')
try:
for line in process.stdout:
print(prefix + line, end='', flush=True)
output.write(line)
except BaseException:
process.terminate()
process.wait()
raise
return process.wait()
def isolate_sources(manifest, rid):
sources = json.loads(manifest.read_text())
source_lock = Path(sources['source_lock'])
version = hashlib.sha256(source_lock.read_bytes()).hexdigest()[:16]
workspace = Path('External/NativeStatic/.work/parallel-sources').resolve() / version / rid
workspace.mkdir(parents=True, exist_ok=True)
isolated_manifest = workspace / 'cross-sources.json'
if isolated_manifest.is_file():
return isolated_manifest, workspace
for incomplete in (workspace / 'SkiaSharp', workspace / 'ANGLE'):
if incomplete.exists():
shutil.rmtree(incomplete)
skia = Path(sources['skia'])
angle = Path(sources['angle'])
parent = workspace / 'SkiaSharp'
isolated_skia = parent / 'externals/skia'
isolated_angle = workspace / 'ANGLE'
for original, destination in ((skia.parent.parent, parent),
(skia, isolated_skia), (angle, isolated_angle)):
if not (destination / '.git').is_dir():
destination.parent.mkdir(parents=True, exist_ok=True)
subprocess.run(['git', 'clone', '--shared', '--no-checkout', str(original),
str(destination)], check=True)
ignored = shutil.ignore_patterns('.git', 'out', 'staticlink_zig_*')
for original, destination in ((skia, isolated_skia), (angle, isolated_angle)):
shutil.copytree(original, destination, dirs_exist_ok=True, symlinks=True,
ignore=ignored)
sources.update(skia=str(isolated_skia), angle=str(isolated_angle),
angle_gn=str(isolated_angle / Path(sources['angle_gn']).relative_to(angle)))
isolated_manifest.write_text(json.dumps(sources, indent=2) + '\n')
return isolated_manifest, workspace
def build_target(rid, args, lock, jobs):
print(f'=== {rid}: compile and link with {jobs} jobs ===', flush=True)
started = time.monotonic()
logfile = args.results / (rid + '.log')
status = 1
failure = None
try:
manifest, workspace = isolate_sources(args.sources_manifest, rid)
env = os.environ.copy()
env['ZIG_GLOBAL_CACHE_DIR'] = str(workspace / 'zig-global')
libraries = Path('External/NativeStatic/zig-cross') / lock['skiasharp_version'] / rid
native = Path('External/NativeStatic') / rid / 'native'
command = [sys.executable, 'build/linux-toolchain/build-cross.py',
'--target', rid, '--sources-manifest', manifest,
'--work', workspace / 'toolchains',
'--output', libraries, '--jobs', str(jobs)]
status = run(command, logfile, rid, env)
if status == 0:
status = run([sys.executable, 'build/linux-toolchain/package-zig.py',
'--rid', rid, '--input', libraries, '--output', native],
logfile, rid, env)
if status == 0:
shutil.copytree(native, args.results / rid / 'native', dirs_exist_ok=True)
shutil.copytree(libraries / 'link-tests', args.results / rid / 'link-tests',
dirs_exist_ok=True)
except Exception as error:
status = 1
failure = f'{type(error).__name__}: {error}'
print(f'[{rid}] {failure}', flush=True)
print(f'=== {rid}: {"PASS" if status == 0 else "FAIL"} ===', flush=True)
return {'exit_code': status, 'passed': status == 0, 'error': failure,
'elapsed_seconds': round(time.monotonic() - started, 1)}
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--sources-manifest', type=Path, required=True)
parser.add_argument('--results', type=Path, required=True)
args = parser.parse_args()
args.results.mkdir(parents=True, exist_ok=True)
lock = json.loads(Path('build/linux-toolchain/release.lock.json').read_text())
processors = int(subprocess.check_output(['nproc'], text=True).strip())
workers = min(2, processors)
jobs = min(22, max(1, processors // workers))
report = {'sources': lock, 'jobs': jobs, 'parallel_platforms': workers,
'targets': {}, 'package': 'not-built',
'runtime_tests': 'not performed; cross compilation and executable linking only'}
descriptor = args.results / 'nine-platform-results.json'
def save():
descriptor.write_text(json.dumps(report, indent=2) + '\n')
save()
print(f'Parallel platforms: {workers}; compile jobs per platform: {jobs}', flush=True)
with ThreadPoolExecutor(max_workers=workers) as executor:
pending = {executor.submit(build_target, rid, args, lock, jobs): rid for rid in RIDS}
for future in as_completed(pending):
report['targets'][pending[future]] = future.result()
save()
failed = [rid for rid in RIDS if not report['targets'][rid]['passed']]
if failed:
print('Failed platforms: ' + ', '.join(failed), flush=True)
return 1
os.environ['SOURCE_LOCK_JSON'] = json.dumps(lock)
os.environ['PACKAGE_VERSION'] = lock['skiasharp_version'] + '-' + lock['angle_branch'] + '.2'
logfile = args.results / 'pack.log'
status = run([sys.executable, 'scripts/apply-release-version.py'], logfile)
if status == 0:
status = run(['dotnet', 'pack', 'NuGet/StaticGraphics/StaticLink.Avalonia.csproj',
'-c', 'Release', '-o', args.results / 'nuget'], logfile)
if status == 0:
package = next((args.results / 'nuget').glob('*.nupkg'))
with zipfile.ZipFile(package) as archive:
names = set(archive.namelist())
required = {'buildTransitive/StaticLink.Avalonia.props',
'buildTransitive/StaticLink.Avalonia.targets'}
required.update('static/' + rid + '/native/' +
('skia.lib' if rid.startswith('win-') else 'libskia.a') for rid in RIDS)
if not required.issubset(names) or any('\\' in name for name in names):
print('NuGet package is missing target archives or has non-portable paths', flush=True)
status = 1
report['package'] = 'passed' if status == 0 else 'failed'
save()
print('Nine-platform build/link/package: ' + report['package'], flush=True)
return status
if __name__ == '__main__':
raise SystemExit(main())