fix.安装器AOT优化

This commit is contained in:
lincube
2026-06-05 21:43:43 +08:00
parent f142307729
commit eae3e67238
8 changed files with 304 additions and 8 deletions

View File

@@ -57,15 +57,21 @@ jobs:
shell: pwsh
run: |
$publishDir = Join-Path $env:GITHUB_WORKSPACE '${{ env.INSTALLER_ARTIFACT_DIR }}'
$tempDir = Join-Path $env:GITHUB_WORKSPACE 'artifacts/installer-online/tmp'
if (Test-Path $publishDir) {
Remove-Item -LiteralPath $publishDir -Recurse -Force
}
New-Item -ItemType Directory -Path $publishDir -Force | Out-Null
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
$env:TEMP = $tempDir
$env:TMP = $tempDir
dotnet restore '${{ env.INSTALLER_PROJECT }}' -r '${{ env.INSTALLER_RUNTIME }}'
dotnet restore '${{ env.INSTALLER_PROJECT }}' `
-r '${{ env.INSTALLER_RUNTIME }}' `
-p:PublishAot=true
if ($LASTEXITCODE -ne 0) {
throw "Online installer runtime restore failed with exit code $LASTEXITCODE."
throw "Online installer NativeAOT restore failed with exit code $LASTEXITCODE."
}
dotnet publish '${{ env.INSTALLER_PROJECT }}' `
@@ -74,6 +80,9 @@ jobs:
-r '${{ env.INSTALLER_RUNTIME }}' `
-p:PublishAot=true `
-p:UseAppHost=true `
-p:DebugType=none `
-p:DebugSymbols=false `
-p:StripSymbols=true `
-o $publishDir `
-v minimal
@@ -90,7 +99,32 @@ jobs:
Write-Warning "dotnet publish exited with $LASTEXITCODE after producing the installer artifact."
}
Get-ChildItem -Path $publishDir -File -Filter 'LanDesktopPLONDS.installer*' |
Get-ChildItem -Path $publishDir -Recurse -Filter '*.pdb' |
Remove-Item -Force
$jitFiles = @(
'coreclr.dll',
'clrjit.dll',
'hostfxr.dll',
'hostpolicy.dll',
'LanDesktopPLONDS.installer.deps.json',
'LanDesktopPLONDS.installer.runtimeconfig.json'
)
foreach ($file in $jitFiles) {
if (Test-Path (Join-Path $publishDir $file)) {
throw "JIT runtime artifact found in NativeAOT output: $file"
}
}
$unexpectedFiles = Get-ChildItem -Path $publishDir -File |
Where-Object { $_.Name -ne 'LanDesktopPLONDS.installer.exe' }
if ($unexpectedFiles) {
$names = ($unexpectedFiles | Select-Object -ExpandProperty Name) -join ', '
throw "Unexpected files in single-exe NativeAOT installer artifact: $names"
}
Get-ChildItem -Path $publishDir -File |
Sort-Object Name |
Select-Object Name, Length
- name: Upload online installer artifact

View File

@@ -4,6 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="12.0.3" />
<PackageVersion Include="Avalonia.Angle.Windows.Natives" Version="2.1.25547.20250602" />
<PackageVersion Include="Avalonia.Controls.WebView" Version="12.0.1" />
<PackageVersion Include="Avalonia.Desktop" Version="12.0.3" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="12.0.3" />
@@ -16,6 +17,7 @@
<PackageVersion Include="Downloader" Version="5.4.0" />
<PackageVersion Include="FluentAvaloniaUI" Version="3.0.0-preview4" />
<PackageVersion Include="FluentIcons.Avalonia" Version="2.1.325" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Win32" Version="8.3.1.3" />
<PackageVersion Include="Lib.Harmony.Thin" Version="2.4.2" />
<PackageVersion Include="Material.Avalonia" Version="3.17.0" />
<PackageVersion Include="MaterialColorUtilities" Version="0.3.0" />
@@ -32,6 +34,7 @@
<PackageVersion Include="PortAudioSharp2" Version="1.0.6" />
<PackageVersion Include="PostHog" Version="2.7.1" />
<PackageVersion Include="Sentry" Version="6.5.0" />
<PackageVersion Include="SkiaSharp.NativeAssets.Win32" Version="3.119.4-preview.1.1" />
<PackageVersion Include="System.Drawing.Common" Version="11.0.0-preview.3.26207.106" />
<PackageVersion Include="System.Runtime.WindowsRuntime" Version="5.0.0-preview.5.20278.1" />
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.92.0" />
@@ -40,4 +43,4 @@
<PackageVersion Include="YamlDotNet" Version="17.1.0" />
<PackageVersion Include="log4net" Version="3.3.1" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,6 +1,6 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sty="using:FluentAvalonia.Styling"
xmlns:theme="using:Avalonia.Themes.Fluent"
xmlns:fi="using:FluentIcons.Avalonia"
x:Class="LanDesktopPLONDS.Installer.App"
RequestedThemeVariant="Default">
@@ -69,7 +69,7 @@
</Application.Resources>
<Application.Styles>
<sty:FluentAvaloniaTheme />
<theme:FluentTheme />
<Style Selector="Window">
<Setter Property="FontFamily" Value="{DynamicResource AppFontFamily}" />
</Style>

View File

@@ -0,0 +1,45 @@
param(
[Parameter(Mandatory = $true)]
[string] $SourcePath,
[Parameter(Mandatory = $true)]
[string] $DestinationPath
)
$ErrorActionPreference = 'Stop'
$source = Get-Item -LiteralPath $SourcePath
$destinationDirectory = Split-Path -Parent $DestinationPath
New-Item -ItemType Directory -Path $destinationDirectory -Force | Out-Null
$existing = Get-Item -LiteralPath $DestinationPath -ErrorAction SilentlyContinue
if ($existing -and $existing.LastWriteTimeUtc -ge $source.LastWriteTimeUtc -and $existing.Length -gt 0) {
return
}
$temporaryPath = "$DestinationPath.$PID.tmp"
if (Test-Path -LiteralPath $temporaryPath) {
Remove-Item -LiteralPath $temporaryPath -Force
}
$inputStream = [System.IO.File]::OpenRead($source.FullName)
try {
$outputStream = [System.IO.File]::Create($temporaryPath)
try {
$gzipStream = New-Object System.IO.Compression.GZipStream($outputStream, [System.IO.Compression.CompressionMode]::Compress)
try {
$inputStream.CopyTo($gzipStream)
}
finally {
$gzipStream.Dispose()
}
}
finally {
$outputStream.Dispose()
}
}
finally {
$inputStream.Dispose()
}
Move-Item -LiteralPath $temporaryPath -Destination $DestinationPath -Force

View File

@@ -19,12 +19,52 @@
<ItemGroup Condition="'$(PublishAot)' == 'true'">
<TrimmerRootAssembly Include="Avalonia" />
<TrimmerRootAssembly Include="Avalonia.Desktop" />
<TrimmerRootAssembly Include="FluentAvalonia" />
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
<TrimmerRootAssembly Include="FluentIcons.Avalonia" />
<TrimmerRootAssembly Include="LanDesktopPLONDS.installer" />
<TrimmerRootAssembly Include="System.Text.Json" />
</ItemGroup>
<Target
Name="PrepareInstallerEmbeddedNativeLibraries"
BeforeTargets="AssignTargetPaths"
Condition="'$(PublishAot)' == 'true' and '$(RuntimeIdentifier)' == 'win-x64'">
<ItemGroup>
<InstallerNativeLibrary
Include="$(PkgAvalonia_Angle_Windows_Natives)\runtimes\win-x64\native\av_libglesv2.dll"
CompressedName="av_libglesv2.dll.gz"
Condition="Exists('$(PkgAvalonia_Angle_Windows_Natives)\runtimes\win-x64\native\av_libglesv2.dll')" />
<InstallerNativeLibrary
Include="$(PkgHarfBuzzSharp_NativeAssets_Win32)\runtimes\win-x64\native\libHarfBuzzSharp.dll"
CompressedName="libHarfBuzzSharp.dll.gz"
Condition="Exists('$(PkgHarfBuzzSharp_NativeAssets_Win32)\runtimes\win-x64\native\libHarfBuzzSharp.dll')" />
<InstallerNativeLibrary
Include="$(PkgSkiaSharp_NativeAssets_Win32)\runtimes\win-x64\native\libSkiaSharp.dll"
CompressedName="libSkiaSharp.dll.gz"
Condition="Exists('$(PkgSkiaSharp_NativeAssets_Win32)\runtimes\win-x64\native\libSkiaSharp.dll')" />
</ItemGroup>
<Error
Condition="'@(InstallerNativeLibrary)' == ''"
Text="NativeAOT installer native libraries were not found. Restore the installer with -p:PublishAot=true -r win-x64 before publishing." />
<MakeDir Directories="$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\" />
<Exec
Command="powershell -NoProfile -ExecutionPolicy Bypass -File &quot;$(MSBuildThisFileDirectory)Compress-NativeLibrary.ps1&quot; -SourcePath &quot;%(InstallerNativeLibrary.FullPath)&quot; -DestinationPath &quot;$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\%(InstallerNativeLibrary.CompressedName)&quot;" />
<ItemGroup>
<EmbeddedResource
Include="$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\av_libglesv2.dll.gz"
LogicalName="LanDesktopPLONDS.Installer.NativeLibraries.av_libglesv2.dll.gz" />
<EmbeddedResource
Include="$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\libHarfBuzzSharp.dll.gz"
LogicalName="LanDesktopPLONDS.Installer.NativeLibraries.libHarfBuzzSharp.dll.gz" />
<EmbeddedResource
Include="$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\libSkiaSharp.dll.gz"
LogicalName="LanDesktopPLONDS.Installer.NativeLibraries.libSkiaSharp.dll.gz" />
</ItemGroup>
</Target>
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
<TrimmerSingleWarn>false</TrimmerSingleWarn>

View File

@@ -20,10 +20,13 @@
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="Avalonia.Fonts.Inter" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="FluentIcons.Avalonia" />
<PackageReference Include="HarfBuzzSharp.NativeAssets.Win32" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="SkiaSharp.NativeAssets.Win32" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="CommunityToolkit.Mvvm" />
</ItemGroup>

View File

@@ -0,0 +1,170 @@
using System.ComponentModel;
using System.Diagnostics;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.InteropServices;
namespace LanDesktopPLONDS.Installer;
internal static class NativeDependencyBootstrapper
{
private const string CacheRootEnvironmentVariable = "LANDESKTOPPLONDS_INSTALLER_NATIVE_CACHE";
private const string ResourcePrefix = "LanDesktopPLONDS.Installer.NativeLibraries.";
private static readonly string[] NativeLibraryNames =
[
"av_libglesv2.dll",
"libHarfBuzzSharp.dll",
"libSkiaSharp.dll"
];
public static void Prepare()
{
if (!OperatingSystem.IsWindows())
{
return;
}
var nativeDirectory = GetNativeDirectory();
Directory.CreateDirectory(nativeDirectory);
var extractedLibraries = new List<string>(NativeLibraryNames.Length);
foreach (var libraryName in NativeLibraryNames)
{
extractedLibraries.Add(ExtractLibrary(nativeDirectory, libraryName));
}
AddToProcessDllSearchPath(nativeDirectory);
foreach (var libraryPath in extractedLibraries)
{
NativeLibrary.Load(libraryPath);
}
}
private static string GetNativeDirectory()
{
var configuredCacheRoot = Environment.GetEnvironmentVariable(CacheRootEnvironmentVariable);
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var cacheRoot = !string.IsNullOrWhiteSpace(configuredCacheRoot)
? configuredCacheRoot
: string.IsNullOrWhiteSpace(localAppData)
? Path.GetTempPath()
: localAppData;
string? versionStamp = null;
if (!string.IsNullOrWhiteSpace(Environment.ProcessPath))
{
versionStamp = FileVersionInfo.GetVersionInfo(Environment.ProcessPath).ProductVersion;
}
if (string.IsNullOrWhiteSpace(versionStamp))
{
versionStamp = "dev";
}
return Path.Combine(
cacheRoot,
"LanDesktopPLONDS",
"Installer",
"native",
RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(),
SanitizePathSegment(versionStamp));
}
private static string ExtractLibrary(string nativeDirectory, string libraryName)
{
var resourceName = ResourcePrefix + libraryName + ".gz";
var assembly = Assembly.GetExecutingAssembly();
using var resource = assembly.GetManifestResourceStream(resourceName);
if (resource is null)
{
var availableResources = string.Join(", ", assembly.GetManifestResourceNames());
throw new FileNotFoundException(
$"Missing embedded native installer library resource '{resourceName}'. Available resources: {availableResources}");
}
var destinationPath = Path.Combine(nativeDirectory, libraryName);
var temporaryPath = destinationPath + "." + Guid.NewGuid().ToString("N") + ".tmp";
using (var gzip = new GZipStream(resource, CompressionMode.Decompress))
using (var output = File.Create(temporaryPath))
{
gzip.CopyTo(output);
}
if (File.Exists(destinationPath) && FilesEqual(destinationPath, temporaryPath))
{
File.Delete(temporaryPath);
return destinationPath;
}
File.Move(temporaryPath, destinationPath, overwrite: true);
return destinationPath;
}
private static void AddToProcessDllSearchPath(string nativeDirectory)
{
var currentPath = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
if (!currentPath.Contains(nativeDirectory, StringComparison.OrdinalIgnoreCase))
{
Environment.SetEnvironmentVariable("PATH", nativeDirectory + Path.PathSeparator + currentPath);
}
if (!SetDllDirectory(nativeDirectory))
{
throw new Win32Exception(Marshal.GetLastPInvokeError(), "Failed to update the process native DLL search path.");
}
}
private static string SanitizePathSegment(string value)
{
foreach (var invalidChar in Path.GetInvalidFileNameChars())
{
value = value.Replace(invalidChar, '_');
}
return value;
}
private static bool FilesEqual(string leftPath, string rightPath)
{
var left = new FileInfo(leftPath);
var right = new FileInfo(rightPath);
if (left.Length != right.Length)
{
return false;
}
using var leftStream = File.OpenRead(leftPath);
using var rightStream = File.OpenRead(rightPath);
var leftBuffer = new byte[81920];
var rightBuffer = new byte[81920];
while (true)
{
var leftRead = leftStream.Read(leftBuffer, 0, leftBuffer.Length);
var rightRead = rightStream.Read(rightBuffer, 0, rightBuffer.Length);
if (leftRead != rightRead)
{
return false;
}
if (leftRead == 0)
{
return true;
}
for (var i = 0; i < leftRead; i++)
{
if (leftBuffer[i] != rightBuffer[i])
{
return false;
}
}
}
}
[DllImport("kernel32", EntryPoint = "SetDllDirectoryW", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetDllDirectory(string pathName);
}

View File

@@ -7,6 +7,7 @@ public static class Program
[STAThread]
public static void Main(string[] args)
{
NativeDependencyBootstrapper.Prepare();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}