diff --git a/.github/workflows/nuget-avalonia-native.yml b/.github/workflows/nuget-avalonia-native.yml
new file mode 100644
index 0000000..12d71d5
--- /dev/null
+++ b/.github/workflows/nuget-avalonia-native.yml
@@ -0,0 +1,278 @@
+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
diff --git a/.github/workflows/nuget-static-graphics.yml b/.github/workflows/nuget-static-graphics.yml
index 1609538..663265b 100644
--- a/.github/workflows/nuget-static-graphics.yml
+++ b/.github/workflows/nuget-static-graphics.yml
@@ -194,9 +194,6 @@ jobs:
- name: Build macOS static graphics libraries
run: bash ./scripts/build-macos-static-graphics.sh all
- - name: Build macOS font preinit object
- run: bash scripts/build-macos-font-preinit.sh
-
- name: Upload macOS static archives
uses: actions/upload-artifact@v4
with:
@@ -204,61 +201,12 @@ jobs:
path: External/NativeStatic/${{ matrix.rid }}/*
if-no-files-found: error
- build-macos-avalonia-native:
- strategy:
- fail-fast: false
- matrix:
- include:
- - rid: osx-x64
- runner: macos-14
- avalonia_version: 11.3.14
- - rid: osx-arm64
- runner: macos-15
- avalonia_version: 11.3.14
- - rid: osx-x64
- runner: macos-14
- avalonia_version: 12.0.3
- - rid: osx-arm64
- runner: macos-15
- avalonia_version: 12.0.3
- runs-on: ${{ matrix.runner }}
- env:
- RID: ${{ matrix.rid }}
- WORK_DIR: ${{ github.workspace }}/External/NativeStatic/.work
- OUTPUT_DIR: ${{ github.workspace }}/External/NativeStatic/${{ matrix.rid }}/native
- AVALONIA_VERSION: ${{ matrix.avalonia_version }}
- 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.avalonia_version }}-${{ matrix.rid }}
- path: |
- External/NativeStatic/${{ matrix.rid }}/native/libAvaloniaNative.a
- External/NativeStatic/${{ matrix.rid }}/native/macos_font_preinit.o
- if-no-files-found: error
-
pack:
runs-on: ubuntu-latest
needs:
- build-linux
- build-windows
- build-macos
- - build-macos-avalonia-native
permissions:
contents: read
steps:
@@ -276,13 +224,6 @@ jobs:
path: External/NativeStatic
merge-multiple: false
- - name: Download static AvaloniaNative archives
- uses: actions/download-artifact@v4
- with:
- pattern: native-avalonia-*
- path: External/AvaloniaNative
- merge-multiple: false
-
- name: Move static archives into RID directories
run: |
for rid in win-x64 win-x86 win-arm64 linux-x64 linux-arm64 linux-musl-x64 linux-musl-arm64 osx-x64 osx-arm64; do
@@ -315,18 +256,7 @@ 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
- for version in 11.3.14 12.0.3; do
- mkdir -p External/NativeStatic/osx-x64/native External/NativeStatic/osx-arm64/native
- cp External/AvaloniaNative/native-avalonia-$version-osx-x64/* External/NativeStatic/osx-x64/native/
- cp External/AvaloniaNative/native-avalonia-$version-osx-arm64/* External/NativeStatic/osx-arm64/native/
- dotnet pack NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj \
- -c Release \
- -o artifacts/nuget \
- /p:PackageVersion=$version.1 \
- /p:Version=$version.1
- done
+ run: dotnet pack NuGet/StaticGraphics/StaticLink.Avalonia.csproj -c Release -o artifacts/nuget
- name: Upload NuGet artifact
uses: actions/upload-artifact@v4
@@ -380,19 +310,9 @@ jobs:
- rid: osx-x64
runner: macos-14
avalonia_version: 11.3.14
- native_version: 11.3.14.1
- rid: osx-arm64
runner: macos-15
avalonia_version: 11.3.14
- native_version: 11.3.14.1
- - rid: osx-x64
- runner: macos-14
- avalonia_version: 12.0.3
- native_version: 12.0.3.1
- - rid: osx-arm64
- runner: macos-15
- avalonia_version: 12.0.3
- native_version: 12.0.3.1
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
@@ -429,15 +349,10 @@ jobs:
if (-not $package) { throw "StaticLink NuGet package was not downloaded." }
if ($package.BaseName -notmatch '^StaticLink\.Avalonia\.(.+)$') { throw "Unexpected package name: $($package.Name)" }
$version = $Matches[1]
- $nativeVersion = "${{ matrix.native_version }}"
$avaloniaVersion = "${{ matrix.avalonia_version }}"
if (-not $avaloniaVersion) { $avaloniaVersion = "11.3.14" }
- if ($nativeVersion) {
- $nativePackage = Get-ChildItem -Path "artifacts/nuget" -Filter "StaticLink.Avalonia.Native.$nativeVersion.nupkg" | Select-Object -First 1
- if (-not $nativePackage) { throw "StaticLink AvaloniaNative NuGet package $nativeVersion was not downloaded." }
- }
$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 -StaticLinkNativeVersion $nativeVersion
+ pwsh ./scripts/create-avalonia-smoke.ps1 -ProjectDir $projectDir -NuGetSource (Resolve-Path "artifacts/nuget") -StaticLinkVersion $version -AvaloniaVersion $avaloniaVersion
dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") `
-c Release `
-r "${{ matrix.rid }}" `
@@ -470,7 +385,7 @@ jobs:
run: |
$publishDir = "artifacts/smoke-${{ matrix.rid }}"
$blocked = Get-ChildItem -Path $publishDir -Recurse -File | Where-Object {
- $_.Name -in @("libSkiaSharp.dll", "libHarfBuzzSharp.dll", "av_libglesv2.dll", "libSkiaSharp.so", "libHarfBuzzSharp.so", "libAvaloniaNative.dylib", "libSkiaSharp.dylib", "libHarfBuzzSharp.dylib")
+ $_.Name -in @("libSkiaSharp.dll", "libHarfBuzzSharp.dll", "av_libglesv2.dll", "libSkiaSharp.so", "libHarfBuzzSharp.so", "libSkiaSharp.dylib", "libHarfBuzzSharp.dylib")
}
if ($blocked) {
$blocked | ForEach-Object { Write-Error "Unexpected dynamic graphics library: $($_.FullName)" }
diff --git a/.gitignore b/.gitignore
index b6f0af6..bae7f23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ artifacts/
*.nupkg
*.snupkg
External/NativeStatic/.work/
+External/AvaloniaNative/.work/
diff --git a/NuGet/AvaloniaNative/README.md b/NuGet/AvaloniaNative/README.md
new file mode 100644
index 0000000..aacdb38
--- /dev/null
+++ b/NuGet/AvaloniaNative/README.md
@@ -0,0 +1,12 @@
+# StaticLink.Avalonia.Native
+
+Static `AvaloniaNative` libraries for macOS Avalonia NativeAOT publishing.
+
+The package version matches the Avalonia version with a `.1` suffix. For example, use `StaticLink.Avalonia.Native` version `12.1.0.1` with Avalonia `12.1.0`.
+
+```xml
+
+
+```
+
+The package supports `osx-x64` and `osx-arm64` NativeAOT publishing.
diff --git a/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj b/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj
index 21eb745..639f7f2 100644
--- a/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj
+++ b/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj
@@ -6,7 +6,7 @@
false
false
StaticLink.Avalonia.Native
- 11.3.14.1
+ 0.0.0
greepar
greepar
Static AvaloniaNative library for macOS Avalonia NativeAOT publishing.
@@ -19,14 +19,14 @@
-
+
-
-
-
-
+
+
+
+
diff --git a/README.md b/README.md
index 58930ba..b8cb49f 100644
--- a/README.md
+++ b/README.md
@@ -41,3 +41,9 @@ dotnet publish -c Release -r win-x64 \
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..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.
diff --git a/scripts/build-macos-avalonia-native-static.sh b/scripts/build-macos-avalonia-native-static.sh
index d32833d..288d1c0 100644
--- a/scripts/build-macos-avalonia-native-static.sh
+++ b/scripts/build-macos-avalonia-native-static.sh
@@ -3,8 +3,8 @@ set -euo pipefail
RID="${RID:-osx-arm64}"
AVALONIA_VERSION="${AVALONIA_VERSION:-11.3.14}"
-WORK_DIR="${WORK_DIR:-$PWD/External/NativeStatic/.work}"
-OUTPUT_DIR="${OUTPUT_DIR:-$PWD/External/NativeStatic/$RID/native}"
+WORK_DIR="${WORK_DIR:-$PWD/External/AvaloniaNative/.work}"
+OUTPUT_DIR="${OUTPUT_DIR:-$PWD/External/AvaloniaNative/$RID/native}"
BUILD_JOBS="${BUILD_JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || echo 4)}"
case "$RID" in
diff --git a/scripts/create-avalonia-smoke.ps1 b/scripts/create-avalonia-smoke.ps1
index e9eb5f8..cfc75c1 100644
--- a/scripts/create-avalonia-smoke.ps1
+++ b/scripts/create-avalonia-smoke.ps1
@@ -5,8 +5,7 @@ param(
[Parameter(Mandatory = $true)]
[string]$NuGetSource,
- [Parameter(Mandatory = $true)]
- [string]$StaticLinkVersion,
+ [string]$StaticLinkVersion = "",
[string]$AvaloniaVersion = "11.3.14",
@@ -22,6 +21,11 @@ if ($StaticLinkNativeVersion) {
$nativeReference = "`n "
}
+$staticLinkReference = ""
+if ($StaticLinkVersion) {
+ $staticLinkReference = "`n "
+}
+
@"
@@ -52,8 +56,7 @@ if ($StaticLinkNativeVersion) {
-
- $nativeReference
+ $staticLinkReference$nativeReference
"@ | Set-Content -Path (Join-Path $ProjectDir "AvaloniaStaticLinkSmoke.csproj") -Encoding UTF8