From bdf631d01daad9ccd382cce232ee33601c0f7192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Tue, 7 Jul 2026 16:46:48 -0400 Subject: [PATCH 1/2] Add opt-in NativeAOT publish path Add a Windows x64 NativeAOT publish profile and helper script while keeping the existing release publish flow unchanged. Wire CI to report NativeAOT warnings and clean up first-party AOT-sensitive serialization and marshalling paths. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/dotnet-test.yml | 19 +++++++ .gitignore | 1 + README.md | 9 +++ scripts/publish-nativeaot.ps1 | 56 +++++++++++++++++++ .../PublishProfiles/Win-x64-NativeAot.pubxml | 16 ++++++ src/UniGetUI.Core.Settings/SettingsJson.cs | 16 ++++++ src/UniGetUI.Core.Tools/DWMThreadHelper.cs | 2 +- src/UniGetUI.Interface.IpcApi/IpcJson.cs | 27 +++++++-- 8 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 scripts/publish-nativeaot.ps1 create mode 100644 src/UniGetUI.Avalonia/Properties/PublishProfiles/Win-x64-NativeAot.pubxml diff --git a/.github/workflows/dotnet-test.yml b/.github/workflows/dotnet-test.yml index fc428541be..743f6cb28f 100644 --- a/.github/workflows/dotnet-test.yml +++ b/.github/workflows/dotnet-test.yml @@ -95,3 +95,22 @@ jobs: /p:TrimmerSingleWarn=false ` /p:UseSharedCompilation=false ` --verbosity minimal + + - name: Report NativeAOT publish warnings + working-directory: src + shell: pwsh + continue-on-error: true + run: | + dotnet build-server shutdown + dotnet publish UniGetUI.Avalonia/UniGetUI.Avalonia.csproj ` + --no-restore ` + --configuration Release ` + --runtime win-x64 ` + --self-contained true ` + --output "${{ runner.temp }}\unigetui-nativeaot-publish" ` + /m:1 ` + /p:Platform=x64 ` + /p:PublishProfile=Win-x64-NativeAot ` + /p:TrimmerSingleWarn=false ` + /p:UseSharedCompilation=false ` + --verbosity minimal diff --git a/.gitignore b/.gitignore index 711cadf3d3..ee7a94371c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ UniGetUI Store.exe UniGetUI.exe installer.iss output/ +/artifacts/ *.old vcredist.exe unigetui_bin/ diff --git a/README.md b/README.md index 99e56f78ff..ba75df507c 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,15 @@ scoop install extras/unigetui choco install unigetui ``` +### Experimental NativeAOT build (Windows) +For contributors and advanced validation, the repository includes an opt-in NativeAOT publish profile and a helper script: + +```powershell +pwsh ./scripts/publish-nativeaot.ps1 +``` + +This publishes `src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj` for `win-x64` as a self-contained NativeAOT build into `artifacts/nativeaot/win-x64/`. It does not replace the existing Release or installer flows. + ### macOS macOS builds are available from GitHub Releases. Use the `.dmg` for the standard installer experience, or the `.tar.gz` archive for a portable app bundle. diff --git a/scripts/publish-nativeaot.ps1 b/scripts/publish-nativeaot.ps1 new file mode 100644 index 0000000000..4c563355c5 --- /dev/null +++ b/scripts/publish-nativeaot.ps1 @@ -0,0 +1,56 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Publishes UniGetUI as a NativeAOT self-contained Windows build. + +.PARAMETER Configuration + Build configuration. Default: Release. + +.PARAMETER Platform + Target platform. Default: x64. + +.PARAMETER OutputPath + Directory for the published output. Default: ./artifacts/nativeaot/win-. + +.PARAMETER PublishProfileName + NativeAOT publish profile name. Default: Win-x64-NativeAot. +#> +[CmdletBinding()] +param( + [string] $Configuration = "Release", + [string] $Platform = "x64", + [string] $OutputPath = (Join-Path (Join-Path $PSScriptRoot "..") "artifacts/nativeaot/win-$Platform"), + [string] $PublishProfileName = "Win-x64-NativeAot" +) + +$ErrorActionPreference = 'Stop' + +$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +$ProjectPath = Join-Path $RepoRoot "src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj" + +if (Test-Path $OutputPath) { + Remove-Item $OutputPath -Recurse -Force +} + +New-Item $OutputPath -ItemType Directory -Force | Out-Null + +Write-Host "Publishing NativeAOT build for win-$Platform to $OutputPath" -ForegroundColor Cyan + +dotnet publish $ProjectPath ` + --configuration $Configuration ` + --runtime "win-$Platform" ` + --self-contained true ` + --output $OutputPath ` + /m:1 ` + /p:Platform=$Platform ` + /p:PublishProfile=$PublishProfileName ` + /p:TrimmerSingleWarn=false ` + /p:UseSharedCompilation=false ` + --nologo ` + --verbosity minimal + +if ($LASTEXITCODE -ne 0) { + throw "dotnet publish failed with exit code $LASTEXITCODE" +} + +Write-Host "NativeAOT publish complete." -ForegroundColor Green diff --git a/src/UniGetUI.Avalonia/Properties/PublishProfiles/Win-x64-NativeAot.pubxml b/src/UniGetUI.Avalonia/Properties/PublishProfiles/Win-x64-NativeAot.pubxml new file mode 100644 index 0000000000..21532d6318 --- /dev/null +++ b/src/UniGetUI.Avalonia/Properties/PublishProfiles/Win-x64-NativeAot.pubxml @@ -0,0 +1,16 @@ + + + + Release + x64 + win-x64 + true + true + false + true + full + false + false + false + + diff --git a/src/UniGetUI.Core.Settings/SettingsJson.cs b/src/UniGetUI.Core.Settings/SettingsJson.cs index cc785fc856..42094f2949 100644 --- a/src/UniGetUI.Core.Settings/SettingsJson.cs +++ b/src/UniGetUI.Core.Settings/SettingsJson.cs @@ -70,6 +70,10 @@ private static JsonTypeInfo GetRequiredTypeInfo() "Trimming", "IL2026", Justification = "Runtime settings use generated metadata for known app types; this fallback preserves generic settings tests and extension scenarios outside trimmed app paths.")] + [UnconditionalSuppressMessage( + "AotCompatibility", + "IL3050", + Justification = "This reflection fallback is only used when generated metadata is unavailable; NativeAOT app paths rely on source-generated metadata for the known settings types.")] private static string SerializeListWithReflection(List value) { return JsonSerializer.Serialize(value, Settings.SerializationOptions); @@ -79,6 +83,10 @@ private static string SerializeListWithReflection(List value) "Trimming", "IL2026", Justification = "Runtime settings use generated metadata for known app types; this fallback preserves generic settings tests and extension scenarios outside trimmed app paths.")] + [UnconditionalSuppressMessage( + "AotCompatibility", + "IL3050", + Justification = "This reflection fallback is only used when generated metadata is unavailable; NativeAOT app paths rely on source-generated metadata for the known settings types.")] private static List? DeserializeListWithReflection(string json) { return JsonSerializer.Deserialize>(json, Settings.SerializationOptions); @@ -88,6 +96,10 @@ private static string SerializeListWithReflection(List value) "Trimming", "IL2026", Justification = "Runtime settings use generated metadata for known app types; this fallback preserves generic settings tests and extension scenarios outside trimmed app paths.")] + [UnconditionalSuppressMessage( + "AotCompatibility", + "IL3050", + Justification = "This reflection fallback is only used when generated metadata is unavailable; NativeAOT app paths rely on source-generated metadata for the known settings types.")] private static string SerializeDictionaryWithReflection( Dictionary value ) @@ -100,6 +112,10 @@ Dictionary value "Trimming", "IL2026", Justification = "Runtime settings use generated metadata for known app types; this fallback preserves generic settings tests and extension scenarios outside trimmed app paths.")] + [UnconditionalSuppressMessage( + "AotCompatibility", + "IL3050", + Justification = "This reflection fallback is only used when generated metadata is unavailable; NativeAOT app paths rely on source-generated metadata for the known settings types.")] private static Dictionary? DeserializeDictionaryWithReflection( string json ) diff --git a/src/UniGetUI.Core.Tools/DWMThreadHelper.cs b/src/UniGetUI.Core.Tools/DWMThreadHelper.cs index cfd8867a0f..a50922625d 100644 --- a/src/UniGetUI.Core.Tools/DWMThreadHelper.cs +++ b/src/UniGetUI.Core.Tools/DWMThreadHelper.cs @@ -123,7 +123,7 @@ private static IntPtr GetThreadStartAdress(int threadId) hThread, ThreadQuerySetWin32StartAddress, ref adress, - Marshal.SizeOf(typeof(IntPtr)), + Marshal.SizeOf(), out _ ); if (status != 0) diff --git a/src/UniGetUI.Interface.IpcApi/IpcJson.cs b/src/UniGetUI.Interface.IpcApi/IpcJson.cs index b77eec37d6..2baa0aec0e 100644 --- a/src/UniGetUI.Interface.IpcApi/IpcJson.cs +++ b/src/UniGetUI.Interface.IpcApi/IpcJson.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Http; +using System.Threading; namespace UniGetUI.Interface; @@ -24,6 +25,14 @@ public static string Serialize(T value) return JsonSerializer.Deserialize(json, GetTypeInfo()); } + internal static JsonTypeInfo GetTypeInfo() + { + return (JsonTypeInfo?)IpcJsonContext.Default.GetTypeInfo(typeof(T)) + ?? throw new InvalidOperationException( + $"IPC JSON metadata for {typeof(T).FullName} was not generated." + ); + } + public static HttpContent CreateContent(T value) { return new StringContent( @@ -38,13 +47,21 @@ public static Task WriteAsync(HttpResponse response, T value) response.ContentType = "application/json; charset=utf-8"; return response.WriteAsync(Serialize(value)); } +} - private static JsonTypeInfo GetTypeInfo() +internal static class IpcHttpResponseJsonExtensions +{ + internal static Task WriteAsJsonAsync( + this HttpResponse response, + TValue value, + JsonSerializerOptions? options, + CancellationToken cancellationToken = default) { - return (JsonTypeInfo?)IpcJsonContext.Default.GetTypeInfo(typeof(T)) - ?? throw new InvalidOperationException( - $"IPC JSON metadata for {typeof(T).FullName} was not generated." - ); + ArgumentNullException.ThrowIfNull(response); + JsonTypeInfo? typeInfo = options?.GetTypeInfo(typeof(TValue)) as JsonTypeInfo; + string json = JsonSerializer.Serialize(value, typeInfo ?? IpcJson.GetTypeInfo()); + response.ContentType = "application/json; charset=utf-8"; + return response.WriteAsync(json, cancellationToken); } } From c96c889b64242843869bcfa35f2850b39256148b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Wed, 8 Jul 2026 09:16:53 -0400 Subject: [PATCH 2/2] Fix IPC JSON import ordering Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/UniGetUI.Interface.IpcApi/IpcJson.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UniGetUI.Interface.IpcApi/IpcJson.cs b/src/UniGetUI.Interface.IpcApi/IpcJson.cs index 2baa0aec0e..1dbe68c02e 100644 --- a/src/UniGetUI.Interface.IpcApi/IpcJson.cs +++ b/src/UniGetUI.Interface.IpcApi/IpcJson.cs @@ -2,8 +2,8 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; -using Microsoft.AspNetCore.Http; using System.Threading; +using Microsoft.AspNetCore.Http; namespace UniGetUI.Interface;