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

@@ -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);
}