fix.继续修复 .NET运行时问题

This commit is contained in:
lincube
2026-05-25 01:24:18 +08:00
parent fd3a193e68
commit 12f0caafc7
3 changed files with 181 additions and 16 deletions

View File

@@ -26,6 +26,8 @@ internal sealed record DotNetRuntimeProbeOptions
public string? ProgramFilesX86Path { get; init; } public string? ProgramFilesX86Path { get; init; }
public string? LocalAppDataPath { get; init; }
public IReadOnlyList<string>? DotNetHostCandidates { get; init; } public IReadOnlyList<string>? DotNetHostCandidates { get; init; }
public bool IncludeRegistry { get; init; } = true; public bool IncludeRegistry { get; init; } = true;
@@ -63,6 +65,13 @@ internal sealed record DotNetRuntimeProbeResult(
internal static class DotNetRuntimeProbe internal static class DotNetRuntimeProbe
{ {
public const string RequiredSharedFrameworkName = "Microsoft.NETCore.App"; public const string RequiredSharedFrameworkName = "Microsoft.NETCore.App";
public const string WindowsDesktopSharedFrameworkName = "Microsoft.WindowsDesktop.App";
private static readonly string[] RequiredSharedFrameworkNames =
[
RequiredSharedFrameworkName,
WindowsDesktopSharedFrameworkName
];
public static DotNetRuntimeProbeResult Probe(DotNetRuntimeProbeOptions? options = null) public static DotNetRuntimeProbeResult Probe(DotNetRuntimeProbeOptions? options = null)
{ {
@@ -71,10 +80,25 @@ internal static class DotNetRuntimeProbe
var searchedPaths = new List<string>(); var searchedPaths = new List<string>();
var detected = new List<DotNetRuntimeInfo>(); var detected = new List<DotNetRuntimeInfo>();
var requiredMajor = options.RequiredMajorVersion; var requiredMajor = options.RequiredMajorVersion;
var sharedFrameworkDirectory = GetSharedFrameworkDirectory(options, RequiredSharedFrameworkName);
searchedPaths.Add(sharedFrameworkDirectory);
AddDirectoryRuntimes(sharedFrameworkDirectory, RequiredSharedFrameworkName, "shared-framework-directory", detected); var localAppDataRoot = GetLocalAppDataPath(options);
var perUserDotnetRoot = !string.IsNullOrWhiteSpace(localAppDataRoot)
? Path.Combine(localAppDataRoot, "dotnet")
: null;
foreach (var frameworkName in RequiredSharedFrameworkNames)
{
foreach (var basePath in EnumerateDotNetInstallRoots(options))
{
var sharedFrameworkDirectory = Path.Combine(basePath, "shared", frameworkName);
searchedPaths.Add(sharedFrameworkDirectory);
var isPerUser = perUserDotnetRoot is not null &&
string.Equals(basePath, perUserDotnetRoot, StringComparison.OrdinalIgnoreCase);
AddDirectoryRuntimes(sharedFrameworkDirectory, frameworkName,
isPerUser ? "shared-framework-directory-per-user" : "shared-framework-directory",
detected);
}
}
string? dotNetHostPath = null; string? dotNetHostPath = null;
foreach (var candidate in EnumerateDotNetHostCandidates(options)) foreach (var candidate in EnumerateDotNetHostCandidates(options))
@@ -88,12 +112,15 @@ internal static class DotNetRuntimeProbe
if (OperatingSystem.IsWindows() && options.IncludeRegistry) if (OperatingSystem.IsWindows() && options.IncludeRegistry)
{ {
AddRegistryRuntimes(options.Architecture, RequiredSharedFrameworkName, detected); foreach (var frameworkName in RequiredSharedFrameworkNames)
{
AddRegistryRuntimes(options.Architecture, frameworkName, detected);
}
} }
if (options.IncludeDotNetCli) if (options.IncludeDotNetCli)
{ {
AddDotNetCliRuntimes(dotNetHostPath, RequiredSharedFrameworkName, detected); AddDotNetCliRuntimes(dotNetHostPath, detected);
} }
var isAvailable = detected.Any(runtime => var isAvailable = detected.Any(runtime =>
@@ -162,13 +189,23 @@ internal static class DotNetRuntimeProbe
!File.Exists(Path.Combine(directory, "System.Private.CoreLib.dll")); !File.Exists(Path.Combine(directory, "System.Private.CoreLib.dll"));
} }
private static string GetSharedFrameworkDirectory(DotNetRuntimeProbeOptions options, string sharedFrameworkName) private static IEnumerable<string> EnumerateDotNetInstallRoots(DotNetRuntimeProbeOptions options)
{ {
var root = options.Architecture == DotNetRuntimeArchitecture.X86 var programFilesRoot = options.Architecture == DotNetRuntimeArchitecture.X86
? GetProgramFilesX86Path(options) ? GetProgramFilesX86Path(options)
: GetProgramFilesPath(options); : GetProgramFilesPath(options);
return Path.Combine(root, "dotnet", "shared", sharedFrameworkName); yield return Path.Combine(programFilesRoot, "dotnet");
var localAppData = GetLocalAppDataPath(options);
if (!string.IsNullOrWhiteSpace(localAppData))
{
var perUserDotnet = Path.Combine(localAppData, "dotnet");
if (!string.Equals(perUserDotnet, Path.Combine(programFilesRoot, "dotnet"), StringComparison.OrdinalIgnoreCase))
{
yield return perUserDotnet;
}
}
} }
private static IEnumerable<string> EnumerateDotNetHostCandidates(DotNetRuntimeProbeOptions options) private static IEnumerable<string> EnumerateDotNetHostCandidates(DotNetRuntimeProbeOptions options)
@@ -186,11 +223,21 @@ internal static class DotNetRuntimeProbe
yield break; yield break;
} }
var root = options.Architecture == DotNetRuntimeArchitecture.X86 var programFilesRoot = options.Architecture == DotNetRuntimeArchitecture.X86
? GetProgramFilesX86Path(options) ? GetProgramFilesX86Path(options)
: GetProgramFilesPath(options); : GetProgramFilesPath(options);
yield return Path.Combine(root, "dotnet", OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); yield return Path.Combine(programFilesRoot, "dotnet", OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet");
var localAppData = GetLocalAppDataPath(options);
if (!string.IsNullOrWhiteSpace(localAppData))
{
var perUserHost = Path.Combine(localAppData, "dotnet", OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet");
if (!string.Equals(perUserHost, Path.Combine(programFilesRoot, "dotnet", OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"), StringComparison.OrdinalIgnoreCase))
{
yield return perUserHost;
}
}
} }
private static string GetProgramFilesPath(DotNetRuntimeProbeOptions options) private static string GetProgramFilesPath(DotNetRuntimeProbeOptions options)
@@ -215,6 +262,16 @@ internal static class DotNetRuntimeProbe
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
} }
private static string GetLocalAppDataPath(DotNetRuntimeProbeOptions options)
{
if (!string.IsNullOrWhiteSpace(options.LocalAppDataPath))
{
return Path.GetFullPath(options.LocalAppDataPath);
}
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
}
private static void AddDirectoryRuntimes( private static void AddDirectoryRuntimes(
string sharedFrameworkDirectory, string sharedFrameworkDirectory,
string sharedFrameworkName, string sharedFrameworkName,
@@ -271,7 +328,6 @@ internal static class DotNetRuntimeProbe
private static void AddDotNetCliRuntimes( private static void AddDotNetCliRuntimes(
string? dotNetHostPath, string? dotNetHostPath,
string sharedFrameworkName,
List<DotNetRuntimeInfo> detected) List<DotNetRuntimeInfo> detected)
{ {
if (string.IsNullOrWhiteSpace(dotNetHostPath) || !File.Exists(dotNetHostPath)) if (string.IsNullOrWhiteSpace(dotNetHostPath) || !File.Exists(dotNetHostPath))
@@ -300,7 +356,7 @@ internal static class DotNetRuntimeProbe
{ {
var parsed = ParseListRuntimeLine(line); var parsed = ParseListRuntimeLine(line);
if (parsed is not null && if (parsed is not null &&
string.Equals(parsed.Value.Name, sharedFrameworkName, StringComparison.OrdinalIgnoreCase)) RequiredSharedFrameworkNames.Contains(parsed.Value.Name, StringComparer.OrdinalIgnoreCase))
{ {
detected.Add(new DotNetRuntimeInfo( detected.Add(new DotNetRuntimeInfo(
parsed.Value.Name, parsed.Value.Name,

View File

@@ -8,14 +8,17 @@ public sealed class DotNetRuntimeProbeTests : IDisposable
private readonly string _root; private readonly string _root;
private readonly string _programFiles; private readonly string _programFiles;
private readonly string _programFilesX86; private readonly string _programFilesX86;
private readonly string _localAppData;
public DotNetRuntimeProbeTests() public DotNetRuntimeProbeTests()
{ {
_root = Path.Combine(Path.GetTempPath(), "LanMountainDesktop.DotNetRuntimeProbeTests", Guid.NewGuid().ToString("N")); _root = Path.Combine(Path.GetTempPath(), "LanMountainDesktop.DotNetRuntimeProbeTests", Guid.NewGuid().ToString("N"));
_programFiles = Path.Combine(_root, "ProgramFiles"); _programFiles = Path.Combine(_root, "ProgramFiles");
_programFilesX86 = Path.Combine(_root, "ProgramFilesX86"); _programFilesX86 = Path.Combine(_root, "ProgramFilesX86");
_localAppData = Path.Combine(_root, "LocalAppData");
Directory.CreateDirectory(_programFiles); Directory.CreateDirectory(_programFiles);
Directory.CreateDirectory(_programFilesX86); Directory.CreateDirectory(_programFilesX86);
Directory.CreateDirectory(_localAppData);
} }
[Fact] [Fact]
@@ -61,6 +64,104 @@ public sealed class DotNetRuntimeProbeTests : IDisposable
Assert.False(result.IsAvailable); Assert.False(result.IsAvailable);
} }
[Fact]
public void Probe_DetectsPerUserRuntime()
{
CreateRuntime(_localAppData, "10.0.5", DotNetRuntimeProbe.RequiredSharedFrameworkName);
var result = DotNetRuntimeProbe.Probe(CreateOptions(DotNetRuntimeArchitecture.X64));
Assert.True(result.IsAvailable);
Assert.Contains(result.DetectedRuntimes, runtime =>
runtime.Version == "10.0.5" &&
runtime.Source == "shared-framework-directory-per-user");
}
[Fact]
public void Probe_DetectsWindowsDesktopRuntime()
{
CreateRuntime(_programFiles, "10.0.5", DotNetRuntimeProbe.WindowsDesktopSharedFrameworkName);
var result = DotNetRuntimeProbe.Probe(CreateOptions(DotNetRuntimeArchitecture.X64));
Assert.False(result.IsAvailable);
Assert.Contains(result.DetectedRuntimes, runtime =>
runtime.Name == DotNetRuntimeProbe.WindowsDesktopSharedFrameworkName &&
runtime.Version == "10.0.5");
}
[Fact]
public void Probe_DetectsPerUserWindowsDesktopRuntime()
{
CreateRuntime(_localAppData, "10.0.5", DotNetRuntimeProbe.WindowsDesktopSharedFrameworkName);
var result = DotNetRuntimeProbe.Probe(CreateOptions(DotNetRuntimeArchitecture.X64));
Assert.Contains(result.DetectedRuntimes, runtime =>
runtime.Name == DotNetRuntimeProbe.WindowsDesktopSharedFrameworkName &&
runtime.Version == "10.0.5" &&
runtime.Source == "shared-framework-directory-per-user");
}
[Fact]
public void Probe_FindsDotNetHost_InPerUserPath()
{
var dotnetDir = Path.Combine(_localAppData, "dotnet");
Directory.CreateDirectory(dotnetDir);
File.WriteAllText(Path.Combine(dotnetDir, "dotnet.exe"), string.Empty);
var result = DotNetRuntimeProbe.Probe(new DotNetRuntimeProbeOptions
{
Architecture = DotNetRuntimeArchitecture.X64,
ProgramFilesPath = _programFiles,
ProgramFilesX86Path = _programFilesX86,
LocalAppDataPath = _localAppData,
IncludeRegistry = false,
IncludeDotNetCli = false
});
Assert.NotNull(result.DotNetHostPath);
Assert.Contains("LocalAppData", result.DotNetHostPath);
}
[Fact]
public void Probe_PrefersProgramFilesHost_OverPerUserHost()
{
var systemDotnetDir = Path.Combine(_programFiles, "dotnet");
Directory.CreateDirectory(systemDotnetDir);
File.WriteAllText(Path.Combine(systemDotnetDir, "dotnet.exe"), string.Empty);
var perUserDotnetDir = Path.Combine(_localAppData, "dotnet");
Directory.CreateDirectory(perUserDotnetDir);
File.WriteAllText(Path.Combine(perUserDotnetDir, "dotnet.exe"), string.Empty);
var result = DotNetRuntimeProbe.Probe(new DotNetRuntimeProbeOptions
{
Architecture = DotNetRuntimeArchitecture.X64,
ProgramFilesPath = _programFiles,
ProgramFilesX86Path = _programFilesX86,
LocalAppDataPath = _localAppData,
IncludeRegistry = false,
IncludeDotNetCli = false
});
Assert.NotNull(result.DotNetHostPath);
Assert.Contains("ProgramFiles", result.DotNetHostPath);
}
[Fact]
public void Probe_CombinesSystemAndPerUserRuntimes()
{
CreateRuntime(_programFiles, "10.0.5");
CreateRuntime(_localAppData, "10.0.3");
var result = DotNetRuntimeProbe.Probe(CreateOptions(DotNetRuntimeArchitecture.X64));
Assert.True(result.IsAvailable);
Assert.Contains(result.DetectedRuntimes, runtime => runtime.Version == "10.0.5");
Assert.Contains(result.DetectedRuntimes, runtime => runtime.Version == "10.0.3");
}
[Fact] [Fact]
public void ValidateDotNetRuntimePrerequisite_ReturnsStructuredFailure_WhenRuntimeIsMissing() public void ValidateDotNetRuntimePrerequisite_ReturnsStructuredFailure_WhenRuntimeIsMissing()
{ {
@@ -109,19 +210,21 @@ public sealed class DotNetRuntimeProbeTests : IDisposable
Architecture = architecture, Architecture = architecture,
ProgramFilesPath = _programFiles, ProgramFilesPath = _programFiles,
ProgramFilesX86Path = _programFilesX86, ProgramFilesX86Path = _programFilesX86,
LocalAppDataPath = _localAppData,
DotNetHostCandidates = [], DotNetHostCandidates = [],
IncludeRegistry = false, IncludeRegistry = false,
IncludeDotNetCli = false IncludeDotNetCli = false
}; };
} }
private static void CreateRuntime(string programFilesRoot, string version) private static void CreateRuntime(string root, string version, string? frameworkName = null)
{ {
frameworkName ??= DotNetRuntimeProbe.RequiredSharedFrameworkName;
Directory.CreateDirectory(Path.Combine( Directory.CreateDirectory(Path.Combine(
programFilesRoot, root,
"dotnet", "dotnet",
"shared", "shared",
DotNetRuntimeProbe.RequiredSharedFrameworkName, frameworkName,
version)); version));
} }

View File

@@ -564,6 +564,11 @@ begin
end; end;
end; end;
function GetPerUserDotNetDesktopRuntimePath(): String;
begin
Result := ExpandConstant('{localappdata}\dotnet\shared\Microsoft.WindowsDesktop.App');
end;
function GetDotNetRuntimeDownloadUrl(): String; function GetDotNetRuntimeDownloadUrl(): String;
begin begin
if '{#MyAppArch}' = 'x64' then if '{#MyAppArch}' = 'x64' then
@@ -590,7 +595,8 @@ end;
function IsDotNetDesktopRuntimeInstalled(): Boolean; function IsDotNetDesktopRuntimeInstalled(): Boolean;
begin begin
Result := IsDotNet10RuntimePresent(GetTargetDotNetDesktopRuntimePath()); Result := IsDotNet10RuntimePresent(GetTargetDotNetDesktopRuntimePath()) or
IsDotNet10RuntimePresent(GetPerUserDotNetDesktopRuntimePath());
end; end;
function DotNetDownloadProgress( function DotNetDownloadProgress(