Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/dotnet-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ UniGetUI Store.exe
UniGetUI.exe
installer.iss
output/
/artifacts/
*.old
vcredist.exe
unigetui_bin/
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
56 changes: 56 additions & 0 deletions scripts/publish-nativeaot.ps1
Original file line number Diff line number Diff line change
@@ -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-<platform>.

.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
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>x64</Platform>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishAot>true</PublishAot>
<PublishReadyToRun>false</PublishReadyToRun>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<PublishSingleFile>false</PublishSingleFile>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<UseSharedCompilation>false</UseSharedCompilation>
</PropertyGroup>
</Project>
16 changes: 16 additions & 0 deletions src/UniGetUI.Core.Settings/SettingsJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ private static JsonTypeInfo<T> GetRequiredTypeInfo<T>()
"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<T>(List<T> value)
{
return JsonSerializer.Serialize(value, Settings.SerializationOptions);
Expand All @@ -79,6 +83,10 @@ private static string SerializeListWithReflection<T>(List<T> 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<T>? DeserializeListWithReflection<T>(string json)
{
return JsonSerializer.Deserialize<List<T>>(json, Settings.SerializationOptions);
Expand All @@ -88,6 +96,10 @@ private static string SerializeListWithReflection<T>(List<T> 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<KeyT, ValueT>(
Dictionary<KeyT, ValueT> value
)
Expand All @@ -100,6 +112,10 @@ Dictionary<KeyT, ValueT> 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<KeyT, ValueT>? DeserializeDictionaryWithReflection<KeyT, ValueT>(
string json
)
Expand Down
2 changes: 1 addition & 1 deletion src/UniGetUI.Core.Tools/DWMThreadHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ private static IntPtr GetThreadStartAdress(int threadId)
hThread,
ThreadQuerySetWin32StartAddress,
ref adress,
Marshal.SizeOf(typeof(IntPtr)),
Marshal.SizeOf<IntPtr>(),
out _
);
if (status != 0)
Expand Down
27 changes: 22 additions & 5 deletions src/UniGetUI.Interface.IpcApi/IpcJson.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System.Text;

Check failure on line 1 in src/UniGetUI.Interface.IpcApi/IpcJson.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Fix imports ordering.
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.Http;
using System.Threading;

namespace UniGetUI.Interface;

Expand All @@ -24,6 +25,14 @@
return JsonSerializer.Deserialize(json, GetTypeInfo<T>());
}

internal static JsonTypeInfo<T> GetTypeInfo<T>()
{
return (JsonTypeInfo<T>?)IpcJsonContext.Default.GetTypeInfo(typeof(T))
?? throw new InvalidOperationException(
$"IPC JSON metadata for {typeof(T).FullName} was not generated."
);
}

public static HttpContent CreateContent<T>(T value)
{
return new StringContent(
Expand All @@ -38,13 +47,21 @@
response.ContentType = "application/json; charset=utf-8";
return response.WriteAsync(Serialize(value));
}
}

private static JsonTypeInfo<T> GetTypeInfo<T>()
internal static class IpcHttpResponseJsonExtensions
{
internal static Task WriteAsJsonAsync<TValue>(
this HttpResponse response,
TValue value,
JsonSerializerOptions? options,
CancellationToken cancellationToken = default)
{
return (JsonTypeInfo<T>?)IpcJsonContext.Default.GetTypeInfo(typeof(T))
?? throw new InvalidOperationException(
$"IPC JSON metadata for {typeof(T).FullName} was not generated."
);
ArgumentNullException.ThrowIfNull(response);
JsonTypeInfo<TValue>? typeInfo = options?.GetTypeInfo(typeof(TValue)) as JsonTypeInfo<TValue>;
string json = JsonSerializer.Serialize(value, typeInfo ?? IpcJson.GetTypeInfo<TValue>());
response.ContentType = "application/json; charset=utf-8";
return response.WriteAsync(json, cancellationToken);
}
}

Expand Down
Loading