Skip to content
Open
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
12 changes: 12 additions & 0 deletions src/MSBuildLocator.Tests/QueryInstanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Shouldly;
using System.IO;
using System.Linq;
using Xunit;

Expand All @@ -22,5 +23,16 @@ public void DefaultInstanceTest()
instance.DiscoveryType.ShouldNotBe(DiscoveryType.DotNetSdk);
#endif
}

#if NETCOREAPP
[Fact]
public void InstallLocationTest()
{
string installLocation = DotNetSdkLocationHelper.GetGlobalInstallLocation();
installLocation.ShouldNotBeNull();
string dotnetExecutablePath = Path.Combine(installLocation, DotNetSdkLocationHelper.ExeName);
File.Exists(dotnetExecutablePath).ShouldBeTrue();
}
#endif
}
}
47 changes: 46 additions & 1 deletion src/MSBuildLocator/DotNetSdkLocationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#if NETCOREAPP

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -11,6 +12,7 @@
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;

Expand All @@ -23,7 +25,7 @@ internal static partial class DotNetSdkLocationHelper
[GeneratedRegex(@"^(\d+)\.(\d+)\.(\d+)", RegexOptions.Multiline)]
private static partial Regex VersionRegex();

private static string ExeName => OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet";
internal static string ExeName => OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet";
private static readonly Lazy<List<string>> s_dotnetPathCandidates = new(() => ResolveDotnetPathCandidates());

public static VisualStudioInstance? GetInstance(string dotNetSdkPath, bool allowQueryAllRuntimeVersions)
Expand Down Expand Up @@ -264,6 +266,7 @@ private static List<string> ResolveDotnetPathCandidates()

AddIfValid(FindDotnetPathFromEnvVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR"));
AddIfValid(GetDotnetPathFromPATH());
AddIfValid(GetGlobalInstallLocation());

return pathCandidates.Count == 0
? throw new InvalidOperationException("Path to dotnet executable is not set. " +
Expand Down Expand Up @@ -312,6 +315,48 @@ void AddIfValid(string? path)
return dotnetPath;
}

/// <summary>
/// Reads the path to the .NET global install location, specified in
/// <see href="https://github.com/dotnet/designs/blob/main/accepted/2021/install-location-per-architecture.md"/>.
/// </summary>
internal static string? GetGlobalInstallLocation()
{
if (OperatingSystem.IsWindows())
{
using RegistryKey hklm32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
using RegistryKey? key = hklm32.OpenSubKey($@"SOFTWARE\dotnet\Setup\InstalledVersions\{RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant()}");
return key?.GetValue("InstallLocation")?.ToString();
}
else
{
// https://github.com/dotnet/runtime/blob/f942875639cf93fce159b0c1f66aba59b8b2d942/src/native/corehost/hostmisc/utils.cpp#L195-L238
string arch = RuntimeInformation.ProcessArchitecture switch
{
Architecture.X86 => "x86",
Architecture.X64 => "x64",
Architecture.Arm => "arm",
Architecture.Arm64 => "arm64",
var x => x.ToString().ToLowerInvariant(),
};
string? path = ReadPathFromFileIfExists($"/etc/dotnet/install_location_{arch}");
// Check the generic file only if the architecture-specific files does not exist.
path ??= ReadPathFromFileIfExists("/etc/dotnet/install_location");
return path;
}
}

private static string? ReadPathFromFileIfExists(string path)
{
try
{
return File.ReadAllText(path).Trim();
}
catch (FileNotFoundException)
{
return null;
}
}

private static string? FindDotnetPathFromEnvVariable(string environmentVariable)
{
string? dotnetPath = Environment.GetEnvironmentVariable(environmentVariable);
Expand Down