Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Added support to more .net runtimes and .net 7 #177

Merged
merged 5 commits into from
Nov 4, 2023
Merged
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
111 changes: 71 additions & 40 deletions src/Squirrel/RuntimeInfo.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32;
using Squirrel.SimpleSplat;
using Squirrel.Sources;

namespace Squirrel
Expand Down Expand Up @@ -156,14 +152,12 @@ public override Task<bool> CheckIsInstalled()
public class DotnetInfo : RuntimeInfo
{
/// <inheritdoc/>
public override string Id => MinVersion.Major >= 5
? $"net{TrimVersion(MinVersion)}-{CpuArchitecture.ToString().ToLower()}"
: $"netcoreapp{TrimVersion(MinVersion)}-{CpuArchitecture.ToString().ToLower()}";
public override string Id =>
$"{(MinVersion.Major >= 5 ? "net" : "netcoreapp")}{TrimVersion(MinVersion)}-{CpuArchitecture.ToString().ToLower()}-{_runtimeShortForm[RuntimeType]}";

/// <inheritdoc/>
public override string DisplayName => MinVersion.Major >= 5
? $".NET {TrimVersion(MinVersion)} Desktop Runtime ({CpuArchitecture.ToString().ToLower()})"
: $".NET Core {TrimVersion(MinVersion)} Desktop Runtime ({CpuArchitecture.ToString().ToLower()})";
public override string DisplayName =>
$"{(MinVersion.Major >= 5 ? ".NET" : ".NET Core")} {TrimVersion(MinVersion)} {RuntimeType} ({CpuArchitecture.ToString().ToLower()})";

/// <summary> The minimum compatible version that must be installed. </summary>
public SemanticVersion MinVersion { get; }
Expand All @@ -172,11 +166,22 @@ public class DotnetInfo : RuntimeInfo
/// For example, if the Squirrel app was deployed with 'win-x64', this must be X64 also. </summary>
public RuntimeCpu CpuArchitecture { get; }

/// <summary> The type of runtime required, eg. Windows Desktop, AspNetCore, Sdk.</summary>
public DotnetRuntimeType RuntimeType { get; }

private static readonly Dictionary<DotnetRuntimeType, string> _runtimeShortForm = new() {
{ DotnetRuntimeType.DotNet, "base" },
{ DotnetRuntimeType.SDK, "sdk" },
{ DotnetRuntimeType.WindowsDesktop, "desktop" },
{ DotnetRuntimeType.AspNetCore, "asp" },
};

/// <inheritdoc/>
protected DotnetInfo(Version minversion, RuntimeCpu architecture)
protected DotnetInfo(Version minversion, RuntimeCpu architecture, DotnetRuntimeType runtimeType = DotnetRuntimeType.WindowsDesktop)
{
MinVersion = new SemanticVersion(minversion);
CpuArchitecture = architecture;
RuntimeType = runtimeType;
if (minversion.Major == 6 && minversion.Build < 0) {
Log.Warn(
$"Automatically upgrading minimum dotnet version from net{minversion} to net6.0.2, " +
Expand All @@ -186,8 +191,8 @@ protected DotnetInfo(Version minversion, RuntimeCpu architecture)
}
}

internal DotnetInfo(string minversion, RuntimeCpu architecture)
: this(ParseVersion(minversion), architecture)
internal DotnetInfo(string minversion, RuntimeCpu architecture, DotnetRuntimeType runtimeType = DotnetRuntimeType.WindowsDesktop)
: this(ParseVersion(minversion), architecture, runtimeType)
{
}

Expand All @@ -197,13 +202,17 @@ internal DotnetInfo(string minversion, RuntimeCpu architecture)
/// <inheritdoc/>
public override Task<bool> CheckIsInstalled()
{
switch (CpuArchitecture) {
var versionDir = GetDotnetVersionDir(CpuArchitecture, RuntimeType);
if (!Directory.Exists(versionDir))
return Task.FromResult(false);

case RuntimeCpu.x64: return Task.FromResult(CheckIsInstalledX64());
case RuntimeCpu.x86: return Task.FromResult(CheckIsInstalledX86());
default: return Task.FromResult(false);
var dirs = Directory.EnumerateDirectories(versionDir)
.Select(d => Path.GetFileName(d))
.Where(d => SemanticVersion.TryParse(d, out var _))
.Select(d => SemanticVersion.Parse(d));

}
var foundCompatibleVer = dirs.Any(v => v.Major == MinVersion.Major && v.Minor == MinVersion.Minor && v >= MinVersion);
return Task.FromResult(foundCompatibleVer);
}

/// <inheritdoc/>
Expand All @@ -216,34 +225,43 @@ public override Task<bool> CheckIsSupported()
return Task.FromResult(true);
}

private bool CheckIsInstalledX86()
private static string GetDotnetVersionDir(RuntimeCpu runtimeArch, DotnetRuntimeType runtimeType)
{
var pf86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
return CheckIsInstalledInBaseDirectory(pf86);
var baseDir = GetDotnetBaseDir(runtimeArch);
if (String.IsNullOrEmpty(baseDir))
return null;

return runtimeType switch {
DotnetRuntimeType.DotNet => Path.Combine(baseDir, "shared", "Microsoft.NETCore.App"),
DotnetRuntimeType.AspNetCore => Path.Combine(baseDir, "shared", "Microsoft.AspNetCore.App"),
DotnetRuntimeType.WindowsDesktop => Path.Combine(baseDir, "shared", "Microsoft.WindowsDesktop.App"),
DotnetRuntimeType.SDK => Path.Combine(baseDir, "sdk"),
_ => throw new ArgumentOutOfRangeException(nameof(DotnetRuntimeType)),
};
}

private bool CheckIsInstalledX64()
private static string GetDotnetBaseDir(RuntimeCpu runtime)
{
if (!Environment.Is64BitOperatingSystem)
return false;
var system = SquirrelRuntimeInfo.SystemArchitecture;
var pf86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);

// we are probably an x86 process, and I don't know of any great ways to
// get the x64 ProgramFiles directory from an x86 process, so this code
// is extremely unfortunate.
if (runtime == RuntimeCpu.x86)
return Path.Combine(pf86, "dotnet");

if (Environment.Is64BitProcess) {
// this only works in a 64 bit process, otherwise it points to ProgramFilesX86
var pf64 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
if (CheckIsInstalledInBaseDirectory(pf64))
return true;
}
// this only works in a 64 bit process, otherwise it points to ProgramFilesX86
var pf64 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);

// https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details
// try to get the real 64 bit program files directory
var pf64compat = Environment.GetEnvironmentVariable("ProgramW6432");
if (Directory.Exists(pf64compat))
return CheckIsInstalledInBaseDirectory(pf64compat);
pf64 = pf64compat;

return false;
if (runtime == system) {
// looking for x64 on an x64 system will always be in pf64.
return Path.Combine(pf64, "dotnet");
}

return null;
}

private bool CheckIsInstalledInBaseDirectory(string baseDirectory)
Expand All @@ -270,10 +288,10 @@ public override async Task<string> GetDownloadUrl()
_ => throw new ArgumentOutOfRangeException(nameof(CpuArchitecture)),
};

return GetDotNetDownloadUrl(DotnetRuntimeType.WindowsDesktop, latest, architecture);
return GetDotNetDownloadUrl(RuntimeType, latest, architecture);
}

private static Regex _dotnetRegex = new Regex(@"^net(?:coreapp)?(?<version>[\d\.]{1,6})(?:-(?<arch>[\w\d]+))?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static Regex _dotnetRegex = new Regex(@"^net(?:coreapp)?(?<version>[\d\.]{1,6})(?:-(?<arch>[\w\d]+))?(?:-(?<type>\w+))?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

/// <summary>
/// Parses a string such as 'net6' or net5.0.14-x86 into a DotnetInfo class capable of checking
Expand All @@ -287,13 +305,26 @@ public static DotnetInfo Parse(string input)

var verstr = match.Groups["version"].Value;
var archstr = match.Groups["arch"].Value; // default is x64 if not specified
var typestr = match.Groups["type"].Value; // default is WindowsDesktop

var archValid = Enum.TryParse<RuntimeCpu>(String.IsNullOrWhiteSpace(archstr) ? "x64" : archstr, true, out var cpu);
if (!archValid)
throw new ArgumentException($"Invalid machine architecture '{archstr}'.");

var type = DotnetRuntimeType.WindowsDesktop;
if (!String.IsNullOrEmpty(typestr)) {
var q = _runtimeShortForm.Where(kvp => kvp.Value.Equals(typestr, StringComparison.InvariantCultureIgnoreCase));
if (Enum.TryParse<DotnetRuntimeType>(typestr, true, out var parsed)) {
type = parsed;
} else if (q.Any()) {
type = q.First().Key;
} else {
throw new ArgumentException($"Invalid dotnet runtime sku '{typestr}'. Valid values: {String.Join(", ", _runtimeShortForm.Values)}");
}
}

var ver = ParseVersion(verstr);
return new DotnetInfo(ver, cpu);
return new DotnetInfo(ver, cpu, type);
}

/// <inheritdoc cref="Parse(string)"/>
Expand Down Expand Up @@ -322,7 +353,7 @@ protected static Version ParseVersion(string input)
throw new ArgumentException("Version must only be a 3-part version string.", nameof(input));

if ((v.Major == 3 && v.Minor == 1) || v.Major >= 5) {
if (v.Major > 7) {
if (v.Major > 8) {
Log.Warn(
$"Runtime version '{input}' was resolved to major version '{v.Major}', but this is greater than any known dotnet version, " +
$"and if this version does not exist this package may fail to install.");
Expand Down
12 changes: 8 additions & 4 deletions src/Squirrel/Runtimes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ public static partial class Runtimes
/// <summary> Runtime for .NET Core 3.1 Desktop Runtime (x64) </summary>
public static readonly DotnetInfo DOTNETCORE31_X64 = new("3.1", RuntimeCpu.x64); // eg. netcoreapp3.1-x64
/// <summary> Runtime for .NET 5.0 Desktop Runtime (x86) </summary>
public static readonly DotnetInfo DOTNET5_X86 = new("5.0", RuntimeCpu.x86); // eg. net5.0.14-x86
public static readonly DotnetInfo DOTNET5_X86 = new("5.0", RuntimeCpu.x86); // eg. net5.0-x86
/// <summary> Runtime for .NET 5.0 Desktop Runtime (x64) </summary>
public static readonly DotnetInfo DOTNET5_X64 = new("5.0", RuntimeCpu.x64); // eg. net5
public static readonly DotnetInfo DOTNET5_X64 = new("5.0", RuntimeCpu.x64); // eg. net5.0-x64
/// <summary> Runtime for .NET 6.0 Desktop Runtime (x86) </summary>
public static readonly DotnetInfo DOTNET6_X86 = new("6.0.2", RuntimeCpu.x86); // eg. net6-x86
public static readonly DotnetInfo DOTNET6_X86 = new("6.0.2", RuntimeCpu.x86); // eg. net6.0-x86
/// <summary> Runtime for .NET 6.0 Desktop Runtime (x64) </summary>
public static readonly DotnetInfo DOTNET6_X64 = new("6.0.2", RuntimeCpu.x64); // eg. net6.0.2
public static readonly DotnetInfo DOTNET6_X64 = new("6.0.2", RuntimeCpu.x64); // eg. net6.0-x64
/// <summary> Runtime for .NET 7.0 Desktop Runtime (x86) </summary>
public static readonly DotnetInfo DOTNET7_X86 = new("7.0", RuntimeCpu.x86); // eg. net7.0-x86
/// <summary> Runtime for .NET 7.0 Desktop Runtime (x64) </summary>
public static readonly DotnetInfo DOTNET7_X64 = new("7.0", RuntimeCpu.x64); // eg. net7.0-x64


/// <summary> Runtime for Visual C++ 2010 Redistributable (x86) </summary>
Expand Down
30 changes: 19 additions & 11 deletions test/RuntimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@ public class RuntimeTests
// we are upgrading net6 to a minimum version of 6.0.2 to work
// around a dotnet SDK bug right now.
[Theory]
[InlineData("net6", "net6.0.2-x64")]
[InlineData("net6.0", "net6.0.2-x64")]
[InlineData("net6-x64", "net6.0.2-x64")]
[InlineData("net6-x86", "net6.0.2-x86")]
[InlineData("net3.1", "netcoreapp3.1-x64")]
[InlineData("netcoreapp3.1", "netcoreapp3.1-x64")]
[InlineData("net3.1-x86", "netcoreapp3.1-x86")]
[InlineData("net6.0.2", "net6.0.2-x64")]
[InlineData("net6.0.2-x86", "net6.0.2-x86")]
[InlineData("net6.0.1-x86", "net6.0.1-x86")]
[InlineData("net6.0.0", "net6-x64")]
[InlineData("net6", "net6.0.2-x64-desktop")]
[InlineData("net6.0", "net6.0.2-x64-desktop")]
[InlineData("net6-x64", "net6.0.2-x64-desktop")]
[InlineData("net6-x86", "net6.0.2-x86-desktop")]
[InlineData("net3.1", "netcoreapp3.1-x64-desktop")]
[InlineData("netcoreapp3.1", "netcoreapp3.1-x64-desktop")]
[InlineData("net3.1-x86", "netcoreapp3.1-x86-desktop")]
[InlineData("net6.0.2", "net6.0.2-x64-desktop")]
[InlineData("net6.0.2-x86", "net6.0.2-x86-desktop")]
[InlineData("net6.0.1-x86", "net6.0.1-x86-desktop")]
[InlineData("net6.0.0", "net6-x64-desktop")]
[InlineData("net6.0-x64-desktop", "net6.0.2-x64-desktop")]
[InlineData("net7.0-x64-base", "net7-x64-base")]
[InlineData("net7.0-x64-asp", "net7-x64-asp")]
[InlineData("net7.0-x64-sdk", "net7-x64-sdk")]
public void DotnetParsesValidVersions(string input, string expected)
{
var p = Runtimes.DotnetInfo.Parse(input);
Expand All @@ -33,6 +37,8 @@ public void DotnetParsesValidVersions(string input, string expected)
[InlineData("net3.2")]
[InlineData("net4.9")]
[InlineData("net6.0.0.4")]
[InlineData("net6-basd")]
[InlineData("net6-x64-aakaka")]
public void DotnetParseThrowsInvalidVersion(string input)
{
Assert.ThrowsAny<Exception>(() => Runtimes.DotnetInfo.Parse(input));
Expand All @@ -54,6 +60,8 @@ public void DotnetParseThrowsInvalidVersion(string input)
[InlineData("asd", false)]
[InlineData("", false)]
[InlineData(null, false)]
[InlineData("net6-x64", true)]
[InlineData("net6-x64-sdk", true)]
public void GetRuntimeTests(string input, bool expected)
{
var dn = Runtimes.GetRuntimeByName(input);
Expand Down