mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 15:44:25 +08:00
fix.安装器AOT优化
This commit is contained in:
40
.github/workflows/installer-build.yml
vendored
40
.github/workflows/installer-build.yml
vendored
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
45
LanDesktopPLONDS.installer/Compress-NativeLibrary.ps1
Normal file
45
LanDesktopPLONDS.installer/Compress-NativeLibrary.ps1
Normal 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
|
||||
@@ -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 "$(MSBuildThisFileDirectory)Compress-NativeLibrary.ps1" -SourcePath "%(InstallerNativeLibrary.FullPath)" -DestinationPath "$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\%(InstallerNativeLibrary.CompressedName)"" />
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
170
LanDesktopPLONDS.installer/NativeDependencyBootstrapper.cs
Normal file
170
LanDesktopPLONDS.installer/NativeDependencyBootstrapper.cs
Normal 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);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ public static class Program
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
NativeDependencyBootstrapper.Prepare();
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user