Document and test macOS Metal support
This commit is contained in:
1 parent
572d14040d
commit
8371cd3a44
6 files changed
+214
-86
No files matched your search
@@ -360,11 +360,103 @@ 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
|
||||
@@ -400,6 +492,7 @@ 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:
|
||||
|
||||
@@ -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 | 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="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`.
|
||||
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.
|
||||
@@ -8,51 +8,15 @@ Choose the static graphics package that matches the Avalonia and SkiaSharp major
|
||||
|
||||
| Avalonia version | SkiaSharp version | `StaticLink.Avalonia` version |
|
||||
| --- | --- | --- |
|
||||
| 11.3.14 | 2.88.9 | `2.88.9-7151.10` |
|
||||
| 11.3.14 | 3.119.4 | `3.119.4-7922.1` |
|
||||
| 12.1.0 | 3.119.4 | `3.119.4-7922.1` |
|
||||
| 12.1.0 | 4.150.1 | `4.150.1-7922.1` |
|
||||
|
||||
### Avalonia 11 with SkiaSharp 2
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.3.14" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.14" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.14" />
|
||||
<PackageReference Include="StaticLink.Avalonia" Version="2.88.9-7151.10" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
### Avalonia 11 with SkiaSharp 3
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.3.14" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.14" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.14" />
|
||||
<PackageReference Include="StaticLink.Avalonia" Version="3.119.4-7922.1" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
### Avalonia 12 with SkiaSharp 3
|
||||
| 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="Avalonia.Desktop" Version="12.1.0" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.0" />
|
||||
<PackageReference Include="StaticLink.Avalonia" Version="3.119.4-7922.1" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
### Avalonia 12 with SkiaSharp 4
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="12.1.0" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="12.1.0" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.0" />
|
||||
<PackageReference Include="StaticLink.Avalonia" Version="4.150.1-7922.1" />
|
||||
</ItemGroup>
|
||||
```
|
||||
@@ -71,35 +35,17 @@ For macOS, also reference `StaticLink.Avalonia.Native`. This package contains `l
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
`.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.
|
||||
@@ -1,6 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace AvaloniaStaticLinkSmoke;
|
||||
|
||||
@@ -15,7 +16,28 @@ public sealed partial class App : Application
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow();
|
||||
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();
|
||||
|
||||
@@ -1,11 +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
|
||||
{
|
||||
public MainWindow()
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,14 +39,16 @@ internal static class Program
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
builder = builder.With(new AvaloniaNativePlatformOptions
|
||||
var renderer = Environment.GetEnvironmentVariable("STATICLINK_MAC_RENDERER");
|
||||
if (!string.Equals(renderer, "default", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RenderingMode =
|
||||
[
|
||||
AvaloniaNativeRenderingMode.OpenGl,
|
||||
AvaloniaNativeRenderingMode.Software
|
||||
]
|
||||
});
|
||||
builder = builder.With(new AvaloniaNativePlatformOptions
|
||||
{
|
||||
RenderingMode = string.Equals(renderer, "metal", StringComparison.OrdinalIgnoreCase)
|
||||
? [AvaloniaNativeRenderingMode.Metal]
|
||||
: [AvaloniaNativeRenderingMode.OpenGl, AvaloniaNativeRenderingMode.Software]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
|
||||
Reference in new issue
Block a user