11 changed files with 106 additions and 514 deletions

No files matched your search

@@ -1,93 +0,0 @@
name: daily-avalonia-native
on:
schedule:
- cron: '17 3 * * *'
workflow_dispatch:
inputs:
force_build:
description: Rebuild even if matching successful Actions artifacts exist
type: boolean
default: false
permissions:
contents: read
actions: write
concurrency:
group: daily-avalonia-native
cancel-in-progress: false
jobs:
check:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
major: [11, 12]
steps:
- name: Check latest stable Avalonia and dispatch Native build
env:
GH_TOKEN: ${{ github.token }}
AVALONIA_MAJOR: ${{ matrix.major }}
FORCE_BUILD: ${{ inputs.force_build }}
NATIVE_BRANCH: avalonia-native
PACKAGE_REVISION: '2'
run: |
python3 - <<'PY'
import json
import os
import re
import subprocess
repository = os.environ['GITHUB_REPOSITORY']
branch = os.environ['NATIVE_BRANCH']
major = int(os.environ['AVALONIA_MAJOR'])
def api(endpoint):
return json.loads(subprocess.check_output(['gh', 'api', endpoint], text=True))
def summary(message):
print(message, flush=True)
with open(os.environ['GITHUB_STEP_SUMMARY'], 'a', encoding='utf-8') as output:
output.write(message + '\n')
upstream = json.loads(subprocess.check_output([
'curl', '--fail', '--silent', '--show-error', '--location', '--retry', '3',
'https://api.nuget.org/v3-flatcontainer/avalonia/index.json',
], text=True))
candidates = [version for version in upstream['versions']
if re.fullmatch(r'\d+\.\d+\.\d+', version)
and int(version.split('.')[0]) == major]
if not candidates:
raise SystemExit(f'No stable Avalonia {major} version found')
version = max(candidates, key=lambda value: tuple(map(int, value.split('.'))))
package_version = version + '.' + os.environ['PACKAGE_REVISION']
artifact_name = 'staticlink-avalonia-native-' + package_version
head_sha = api(f'repos/{repository}/branches/{branch}')['commit']['sha']
if os.environ['FORCE_BUILD'] != 'true':
already_validated = False
runs = api(f'repos/{repository}/actions/workflows/nuget-avalonia-native.yml/runs'
f'?branch={branch}&status=success&per_page=100')['workflow_runs']
for run in runs:
if run['head_sha'] != head_sha or run['conclusion'] != 'success':
continue
artifacts = api(f"repos/{repository}/actions/runs/{run['id']}/artifacts?per_page=100")['artifacts']
if any(artifact['name'] == artifact_name and not artifact['expired']
for artifact in artifacts):
summary(f"Already validated {package_version}: {run['html_url']}")
already_validated = True
break
if already_validated:
raise SystemExit(0)
payload = json.dumps({'ref': branch, 'inputs': {
'avalonia_version': version, 'force_build': True, 'publish_to_nuget': False,
}})
subprocess.run([
'gh', 'api', '--method', 'POST',
f'repos/{repository}/actions/workflows/nuget-avalonia-native.yml/dispatches',
'--input', '-',
], input=payload, text=True, check=True)
summary(f'Dispatched {package_version} on {branch}; Actions artifacts only, no NuGet publication.')
PY
@@ -1,76 +0,0 @@
name: daily-native-release
on:
workflow_dispatch:
inputs:
force_build:
description: Build current stable versions even if already published
type: boolean
default: false
permissions:
contents: read
actions: write
concurrency:
group: daily-native-release-${{ github.ref }}
cancel-in-progress: false
jobs:
dispatch:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
branch: [skia3, skia4, avalonia-native]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ matrix.branch }}
- name: Check and dispatch this branch
env:
GH_TOKEN: ${{ github.token }}
TRACK_BRANCH: ${{ matrix.branch }}
FORCE_BUILD: ${{ inputs.force_build }}
run: |
python3 - <<'PY'
import json
import os
import subprocess
branch = os.environ["TRACK_BRANCH"]
force = os.environ["FORCE_BUILD"] == "true"
def dispatch(workflow, inputs):
payload = json.dumps({"ref": branch, "inputs": inputs})
subprocess.run(
["gh", "api", "--method", "POST",
f"repos/{os.environ['GITHUB_REPOSITORY']}/actions/workflows/{workflow}/dispatches",
"--input", "-"],
input=payload, text=True, check=True,
)
if branch == "avalonia-native":
dispatch("nuget-avalonia-native.yml", {
"force_build": force, "publish_to_nuget": False,
})
else:
command = ["python3", "scripts/check-upstream-releases.py"]
if force:
command.append("--force")
subprocess.run(command, check=True)
for release in json.load(open("release-plan.json")):
if release["ref"] != branch:
raise SystemExit("Release track does not match the checked-out branch")
dispatch("nuget-static-graphics.yml", {
"skiasharp_version": release["skiasharp_version"],
"publish_to_nuget": False, "publish_to_release": False,
})
print(f"Dispatched {release['package_version']} on {branch}")
PY
- uses: actions/upload-artifact@v4
if: ${{ always() && matrix.branch != 'avalonia-native' }}
with:
name: daily-release-plan-${{ matrix.branch }}
path: release-plan.json
if-no-files-found: warn
+44 -62
View File
@@ -1,10 +1,12 @@
name: staticlink-avalonia-native
on:
schedule:
- cron: "17 3 * * *"
workflow_dispatch:
inputs:
avalonia_version:
description: Avalonia version to build; empty checks the latest stable 11.x and 12.x versions
description: Avalonia version to build; empty uses the latest stable NuGet version
required: false
type: string
force_build:
@@ -15,16 +17,21 @@ on:
publish_to_nuget:
description: Publish with NuGet Trusted Publishing after smoke tests
required: true
default: false
default: true
type: boolean
concurrency:
group: staticlink-avalonia-native
cancel-in-progress: false
jobs:
detect:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
versions: ${{ steps.version.outputs.versions }}
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
@@ -36,55 +43,38 @@ jobs:
run: |
set -euo pipefail
if [ -n "$REQUESTED_VERSION" ]; then
candidate_versions=("$REQUESTED_VERSION")
else
nuget_versions="$(curl --fail --silent --show-error --location \
https://api.nuget.org/v3-flatcontainer/avalonia/index.json)"
candidate_versions=()
for major in 11 12; do
candidate_versions+=("$(jq -r --arg major "$major" \
'[.versions[] | select(startswith($major + ".") and (contains("-") | not))] | last' \
<<< "$nuget_versions")")
done
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
versions='[]'
for avalonia_version in "${candidate_versions[@]}"; do
if [ -z "$avalonia_version" ] || [ "$avalonia_version" = "null" ]; then
echo "Could not determine a stable Avalonia version." >&2
exit 1
fi
package_version="$avalonia_version.1"
package_url="https://api.nuget.org/v3-flatcontainer/staticlink.avalonia.native/$package_version/staticlink.avalonia.native.$package_version.nupkg"
if [ "$FORCE_BUILD" != "true" ] && curl --fail --silent --location --output /dev/null "$package_url"; then
echo "Already published: $package_version"
continue
fi
echo "Build required: $package_version"
versions="$(jq -cn --argjson versions "$versions" \
--arg avalonia_version "$avalonia_version" \
--arg package_version "$package_version" \
'$versions + [{avalonia_version: $avalonia_version, package_version: $package_version}]')"
done
echo "versions=$versions" >> "$GITHUB_OUTPUT"
if [ "$versions" = '[]' ]; then
echo 'should_build=false' >> "$GITHUB_OUTPUT"
else
echo 'should_build=true' >> "$GITHUB_OUTPUT"
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:
version: ${{ fromJSON(needs.detect.outputs.versions) }}
rid: [osx-x64, osx-arm64]
include:
- rid: osx-x64
runner: macos-14
@@ -95,7 +85,7 @@ jobs:
contents: read
env:
RID: ${{ matrix.rid }}
AVALONIA_VERSION: ${{ matrix.version.avalonia_version }}
AVALONIA_VERSION: ${{ needs.detect.outputs.avalonia_version }}
WORK_DIR: ${{ github.workspace }}/External/AvaloniaNative/.work
OUTPUT_DIR: ${{ github.workspace }}/External/AvaloniaNative/${{ matrix.rid }}/native
steps:
@@ -117,7 +107,7 @@ jobs:
- name: Upload static AvaloniaNative
uses: actions/upload-artifact@v4
with:
name: native-avalonia-${{ matrix.version.avalonia_version }}-${{ matrix.rid }}
name: native-avalonia-${{ matrix.rid }}
path: |
External/AvaloniaNative/${{ matrix.rid }}/native/libAvaloniaNative.a
External/AvaloniaNative/${{ matrix.rid }}/native/macos_font_preinit.o
@@ -128,9 +118,6 @@ jobs:
- detect
- build
if: ${{ needs.detect.outputs.should_build == 'true' }}
strategy:
matrix:
version: ${{ fromJSON(needs.detect.outputs.versions) }}
runs-on: ubuntu-latest
permissions:
contents: read
@@ -145,7 +132,7 @@ jobs:
- name: Download static AvaloniaNative archives
uses: actions/download-artifact@v4
with:
pattern: native-avalonia-${{ matrix.version.avalonia_version }}-*
pattern: native-avalonia-*
path: artifacts/native
merge-multiple: false
@@ -155,8 +142,8 @@ jobs:
set -euo pipefail
for rid in osx-x64 osx-arm64; do
mkdir -p "External/AvaloniaNative/$rid/native"
cp "artifacts/native/native-avalonia-${{ matrix.version.avalonia_version }}-$rid/libAvaloniaNative.a" "External/AvaloniaNative/$rid/native/"
cp "artifacts/native/native-avalonia-${{ matrix.version.avalonia_version }}-$rid/macos_font_preinit.o" "External/AvaloniaNative/$rid/native/"
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
@@ -164,13 +151,13 @@ jobs:
dotnet pack NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj \
-c Release \
-o artifacts/nuget \
/p:PackageVersion=${{ matrix.version.package_version }} \
/p:Version=${{ matrix.version.package_version }}
/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-${{ matrix.version.package_version }}
name: staticlink-avalonia-native-${{ needs.detect.outputs.package_version }}
path: artifacts/nuget/*.nupkg
if-no-files-found: error
@@ -182,8 +169,6 @@ jobs:
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.detect.outputs.versions) }}
rid: [osx-x64, osx-arm64]
include:
- rid: osx-x64
runner: macos-14
@@ -203,7 +188,7 @@ jobs:
- name: Download NuGet package
uses: actions/download-artifact@v4
with:
name: staticlink-avalonia-native-${{ matrix.version.package_version }}
name: staticlink-avalonia-native-${{ needs.detect.outputs.package_version }}
path: artifacts/nuget
- name: Publish Avalonia NativeAOT smoke app
@@ -213,8 +198,8 @@ jobs:
pwsh ./scripts/create-avalonia-smoke.ps1 `
-ProjectDir $projectDir `
-NuGetSource (Resolve-Path "artifacts/nuget") `
-AvaloniaVersion "${{ matrix.version.avalonia_version }}" `
-StaticLinkNativeVersion "${{ matrix.version.package_version }}"
-AvaloniaVersion "${{ needs.detect.outputs.avalonia_version }}" `
-StaticLinkNativeVersion "${{ needs.detect.outputs.package_version }}"
dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") `
-c Release `
-r "${{ matrix.rid }}" `
@@ -263,9 +248,6 @@ jobs:
- detect
- smoke
if: ${{ needs.detect.outputs.should_build == 'true' && (github.event_name == 'schedule' || inputs.publish_to_nuget == true) }}
strategy:
matrix:
version: ${{ fromJSON(needs.detect.outputs.versions) }}
runs-on: ubuntu-latest
permissions:
contents: read
@@ -274,7 +256,7 @@ jobs:
- name: Download NuGet package
uses: actions/download-artifact@v4
with:
name: staticlink-avalonia-native-${{ matrix.version.package_version }}
name: staticlink-avalonia-native-${{ needs.detect.outputs.package_version }}
path: artifacts/nuget
- name: Validate Trusted Publishing configuration
@@ -360,103 +360,11 @@ jobs:
path: artifacts/smoke-${{ matrix.rid }}/**
if-no-files-found: error
smoke-macos-metal:
runs-on: macos-15
needs: pack
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.302"
- name: Download NuGet package
uses: actions/download-artifact@v4
with:
name: staticlink-avalonia-nuget
path: artifacts/nuget
- name: Publish Avalonia 12 macOS arm64 smoke app
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$package = Get-ChildItem -Path "artifacts/nuget" -Filter "StaticLink.Avalonia.*.nupkg" | Where-Object { $_.Name -notlike "StaticLink.Avalonia.Native.*" } | Select-Object -First 1
if (-not $package) { throw "StaticLink NuGet package was not downloaded." }
if ($package.BaseName -notmatch '^StaticLink\.Avalonia\.(.+)$') { throw "Unexpected package name: $($package.Name)" }
$projectDir = Join-Path $PWD "artifacts/avalonia-metal-smoke-src"
pwsh ./scripts/create-avalonia-smoke.ps1 `
-ProjectDir $projectDir `
-NuGetSource (Resolve-Path "artifacts/nuget") `
-StaticLinkVersion $Matches[1] `
-AvaloniaVersion "12.1.0" `
-StaticLinkNativeVersion "12.1.0.1"
dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") `
-c Release `
-r osx-arm64 `
--configfile (Join-Path $projectDir "NuGet.config") `
/p:PublishAot=true `
/p:SelfContained=true `
/p:PublishSingleFile=true `
/p:StripSymbols=true `
/p:PublishDir="$PWD/artifacts/smoke-osx-arm64-metal/"
- name: Verify fully static third-party libraries
shell: bash
run: |
set -euo pipefail
publish_dir="artifacts/smoke-osx-arm64-metal"
test -x "$publish_dir/AvaloniaStaticLinkSmoke"
for library in libAvaloniaNative.dylib libSkiaSharp.dylib libHarfBuzzSharp.dylib; do
test ! -f "$publish_dir/$library"
done
- name: Run default and Metal-only smoke tests
shell: bash
run: |
set -euo pipefail
cd artifacts/smoke-osx-arm64-metal
python3 - <<'PY'
import os
import pathlib
import subprocess
for renderer in ("default", "metal"):
environment = os.environ.copy()
environment["STATICLINK_MAC_RENDERER"] = renderer
environment["STATICLINK_SMOKE_AUTOCLOSE"] = "1"
try:
result = subprocess.run(
["./AvaloniaStaticLinkSmoke"],
env=environment,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=15,
)
except subprocess.TimeoutExpired as exception:
output = exception.stdout or ""
if isinstance(output, bytes):
output = output.decode(errors="replace")
pathlib.Path(f"avalonia-{renderer}.log").write_text(output)
raise SystemExit(f"Avalonia {renderer} smoke app timed out.\n{output}")
output = result.stdout or ""
pathlib.Path(f"avalonia-{renderer}.log").write_text(output)
print(output, end="")
marker = f"STATICLINK_SMOKE_READY={renderer}"
if result.returncode != 0 or marker not in output.splitlines():
raise SystemExit(f"Avalonia {renderer} smoke app failed with exit code {result.returncode}.")
PY
publish-nuget:
runs-on: ubuntu-24.04
needs:
- pack
- smoke-avalonia
- smoke-macos-metal
steps:
- name: Setup .NET
uses: actions/setup-dotnet@v4
@@ -492,7 +400,6 @@ jobs:
- 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:
-45
View File
@@ -1,45 +0,0 @@
# Manual artifact validation
Native daily checks are enabled by daily-avalonia-native.yml on main at 03:17
UTC (11:17 Singapore time). Skia3/4 daily schedules remain disabled. No
NuGet.org publication or GitHub Release is requested by either dispatcher.
The Native checker handles the latest stable Avalonia 11 and 12 independently.
It skips versions with non-expired NuGet artifacts from a successful Native
run at the current avalonia-native branch commit. New versions, changed Native
code, expired artifacts, or failed builds trigger a new artifact-only build.
Set force_build=true on the daily checker to bypass this successful-run check.
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.
The manual graphics dispatcher still checks publication rather than successful
Actions runs. Add the same successful-run tracking before enabling artifact-only
daily graphics builds if avoiding repeated builds is required.
+14 -30
View File
@@ -4,48 +4,32 @@ Static native libraries for Avalonia single-file NativeAOT publishing.
## Install
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>
```bash
dotnet add package StaticLink.Avalonia
```
### macOS
Or add it manually:
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
<PackageReference Include="StaticLink.Avalonia" Version="4.150.1-7922.1" />
```
For macOS, also reference `StaticLink.Avalonia.Native`. This package is macOS-only and must match your Avalonia version because it contains `libAvaloniaNative.a`.
```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
dotnet publish -c Release -r win-x64 \
-p:PublishAot=true \
-p:SelfContained=true \
-p:PublishSingleFile=true \
-p:StripSymbols=true
```
Use the RID you need, such as `win-x86`, `linux-x64`, `linux-arm64`, `osx-arm64`, or `osx-x64`.
## 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.
For macOS, avoid the Metal renderer for fully static output. Use OpenGL or Software.
+38 -27
View File
@@ -4,48 +4,59 @@ Static native libraries for Avalonia single-file NativeAOT publishing.
## Install
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>
```bash
dotnet add package StaticLink.Avalonia
```
### macOS
Or add it manually:
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
<PackageReference Include="StaticLink.Avalonia" Version="4.150.1-7922.1" />
```
`4.150.1-7922.1` uses the latest stable SkiaSharp 4 release. 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`.
```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
dotnet publish -c Release -r win-x64 \
-p:PublishAot=true \
-p:SelfContained=true \
-p:PublishSingleFile=true \
-p:StripSymbols=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.
`.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.
## Static Graphics Automation
`.github/workflows/nuget-static-graphics.yml` runs a fast version, upstream-ref, patch, script, and smoke-project preflight before starting native builds. It then calls independent Windows, Linux, Linux musl, and macOS workflows, packs their artifacts, runs all NativeAOT smoke tests, and only then publishes NuGet and GitHub Release assets.
The platform workflows can also be dispatched independently when diagnosing a single platform:
- `static-graphics-windows.yml`
- `static-graphics-linux.yml`
- `static-graphics-musl.yml`
- `static-graphics-macos.yml`
The release workflow derives the SkiaSharp version and ANGLE branch from `NuGet/StaticGraphics/StaticLink.Avalonia.csproj`. Native source and build directories are cached by platform, RID, version, patch, and build script.
+1 -23
View File
@@ -1,7 +1,6 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
namespace AvaloniaStaticLinkSmoke;
@@ -16,28 +15,7 @@ public sealed partial class App : Application
{
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();
});
}
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
@@ -1,60 +1,11 @@
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()
{
}
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");
}
}
}
+7 -9
View File
@@ -39,16 +39,14 @@ internal static class Program
if (OperatingSystem.IsMacOS())
{
var renderer = Environment.GetEnvironmentVariable("STATICLINK_MAC_RENDERER");
if (!string.Equals(renderer, "default", StringComparison.OrdinalIgnoreCase))
builder = builder.With(new AvaloniaNativePlatformOptions
{
builder = builder.With(new AvaloniaNativePlatformOptions
{
RenderingMode = string.Equals(renderer, "metal", StringComparison.OrdinalIgnoreCase)
? [AvaloniaNativeRenderingMode.Metal]
: [AvaloniaNativeRenderingMode.OpenGl, AvaloniaNativeRenderingMode.Software]
});
}
RenderingMode =
[
AvaloniaNativeRenderingMode.OpenGl,
AvaloniaNativeRenderingMode.Software
]
});
}
return builder;
@@ -26,12 +26,7 @@ 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
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
bash "$src/native/Avalonia.Native/generate-headers.sh"
fi
project="$src/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj"