Skip to content
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
57 changes: 18 additions & 39 deletions src/MSBuildLocator/DotNetSdkLocationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@

namespace Microsoft.Build.Locator
{
internal static class DotNetSdkLocationHelper
internal static partial class DotNetSdkLocationHelper
{
private static readonly Regex VersionRegex = new Regex(@"^(\d+)\.(\d+)\.(\d+)", RegexOptions.Multiline);
private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static readonly string ExeName = IsWindows ? "dotnet.exe" : "dotnet";
private static readonly Lazy<IList<string>> s_dotnetPathCandidates = new(() => ResolveDotnetPathCandidates());
[GeneratedRegex(@"^(\d+)\.(\d+)\.(\d+)", RegexOptions.Multiline)]
private static partial Regex VersionRegex();

private 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 All @@ -38,7 +39,7 @@ internal static class DotNetSdkLocationHelper
}

// Preview versions contain a hyphen after the numeric part of the version. Version.TryParse doesn't accept that.
Match versionMatch = VersionRegex.Match(File.ReadAllText(versionPath));
Match versionMatch = VersionRegex().Match(File.ReadAllText(versionPath));

if (!versionMatch.Success)
{
Expand Down Expand Up @@ -116,10 +117,9 @@ public static IEnumerable<VisualStudioInstance> GetInstances(string workingDirec
static IEnumerable<string> GetAllAvailableSDKs(bool allowAllDotnetLocations)
{
bool foundSdks = false;
string[]? resolvedPaths = null;
foreach (string dotnetPath in s_dotnetPathCandidates.Value)
{
int rc = NativeMethods.hostfxr_get_available_sdks(exe_dir: dotnetPath, result: (key, value) => resolvedPaths = value);
int rc = NativeMethods.hostfxr_get_available_sdks(exe_dir: dotnetPath, out string[]? resolvedPaths);

if (rc == 0 && resolvedPaths != null)
{
Expand Down Expand Up @@ -150,13 +150,7 @@ static IEnumerable<string> GetAllAvailableSDKs(bool allowAllDotnetLocations)
string? resolvedSdk = null;
foreach (string dotnetPath in s_dotnetPathCandidates.Value)
{
int rc = NativeMethods.hostfxr_resolve_sdk2(exe_dir: dotnetPath, working_dir: workingDirectory, flags: 0, result: (key, value) =>
{
if (key == NativeMethods.hostfxr_resolve_sdk2_result_key_t.resolved_sdk_dir)
{
resolvedSdk = value;
}
});
int rc = NativeMethods.hostfxr_resolve_sdk2(exe_dir: dotnetPath, working_dir: workingDirectory, flags: 0, out resolvedSdk, out _);

if (rc == 0)
{
Expand All @@ -178,7 +172,7 @@ static IEnumerable<string> GetAllAvailableSDKs(bool allowAllDotnetLocations)
private static void ModifyUnmanagedDllResolver(Action<AssemblyLoadContext> resolverAction)
{
// For Windows hostfxr is loaded in the process.
if (!IsWindows)
if (!OperatingSystem.IsWindows())
{
var loadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly());
if (loadContext != null)
Expand All @@ -197,9 +191,9 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName)
}

string hostFxrLibName =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
OperatingSystem.IsWindows() ?
"hostfxr.dll" :
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "libhostfxr.dylib" : "libhostfxr.so";
OperatingSystem.IsMacOS() ? "libhostfxr.dylib" : "libhostfxr.so";
string hostFxrRoot = string.Empty;

// Get the dotnet path candidates
Expand Down Expand Up @@ -237,12 +231,12 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName)

private static string SdkResolutionExceptionMessage(string methodName) => $"Failed to find all versions of .NET Core MSBuild. Call to {methodName}. There may be more details in stderr.";

private static IList<string> ResolveDotnetPathCandidates()
private static List<string> ResolveDotnetPathCandidates()
{
var pathCandidates = new List<string>();
AddIfValid(GetDotnetPathFromROOT());

string? dotnetExePath = GetCurrentProcessPath();
string? dotnetExePath = Environment.ProcessPath;
bool isRunFromDotnetExecutable = !string.IsNullOrEmpty(dotnetExePath)
&& Path.GetFileName(dotnetExePath).Equals(ExeName, StringComparison.InvariantCultureIgnoreCase);

Expand All @@ -254,9 +248,9 @@ private static IList<string> ResolveDotnetPathCandidates()
string? hostPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH");
if (!string.IsNullOrEmpty(hostPath) && File.Exists(hostPath))
{
if (!IsWindows)
if (!OperatingSystem.IsWindows())
{
hostPath = realpath(hostPath) ?? hostPath;
hostPath = File.ResolveLinkTarget(hostPath, true)?.FullName ?? hostPath;
}

AddIfValid(Path.GetDirectoryName(hostPath));
Expand Down Expand Up @@ -289,8 +283,6 @@ void AddIfValid(string? path)
return dotnetPath;
}

private static string? GetCurrentProcessPath() => Environment.ProcessPath;

private static string? GetDotnetPathFromPATH()
{
string? dotnetPath = null;
Expand All @@ -314,19 +306,6 @@ void AddIfValid(string? path)
return dotnetPath;
}

/// <summary>
/// This native method call determines the actual location of path, including
/// resolving symbolic links.
/// </summary>
private static string? realpath(string path)
{
IntPtr ptr = NativeMethods.realpath(path, IntPtr.Zero);
string? result = Marshal.PtrToStringAuto(ptr);
NativeMethods.free(ptr);

return result;
}

private static string? FindDotnetPathFromEnvVariable(string environmentVariable)
{
string? dotnetPath = Environment.GetEnvironmentVariable(environmentVariable);
Expand All @@ -347,9 +326,9 @@ private static void SetEnvironmentVariableIfEmpty(string name, string value)
string fullPathToDotnetFromRoot = Path.Combine(dotnetPath, ExeName);
if (File.Exists(fullPathToDotnetFromRoot))
{
if (!IsWindows)
if (!OperatingSystem.IsWindows())
{
fullPathToDotnetFromRoot = realpath(fullPathToDotnetFromRoot) ?? fullPathToDotnetFromRoot;
fullPathToDotnetFromRoot = File.ResolveLinkTarget(fullPathToDotnetFromRoot, true)?.FullName ?? fullPathToDotnetFromRoot;
return File.Exists(fullPathToDotnetFromRoot) ? Path.GetDirectoryName(fullPathToDotnetFromRoot) : null;
}

Expand Down
1 change: 1 addition & 0 deletions src/MSBuildLocator/Microsoft.Build.Locator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PackageTags>msbuildlocator;locator;buildlocator</PackageTags>
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>1.6.1</PackageValidationBaselineVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net46'">
<DefineConstants>$(DefineConstants);FEATURE_VISUALSTUDIOSETUP</DefineConstants>
Expand Down
113 changes: 92 additions & 21 deletions src/MSBuildLocator/NativeMethods.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#if NETCOREAPP
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

namespace Microsoft.Build.Locator
{
internal class NativeMethods
internal partial class NativeMethods
{
internal const string HostFxrName = "hostfxr";

Expand All @@ -15,37 +19,104 @@ internal enum hostfxr_resolve_sdk2_flags_t
disallow_prerelease = 0x1,
};

internal enum hostfxr_resolve_sdk2_result_key_t
private enum hostfxr_resolve_sdk2_result_key_t
{
resolved_sdk_dir = 0,
global_json_path = 1,
};

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)]
internal delegate void hostfxr_resolve_sdk2_result_fn(
hostfxr_resolve_sdk2_result_key_t key,
string value);

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)]
internal delegate void hostfxr_get_available_sdks_result_fn(
int sdk_count,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
string[] value);
internal static int hostfxr_resolve_sdk2(string exe_dir, string working_dir, hostfxr_resolve_sdk2_flags_t flags, out string resolved_sdk_dir, out string global_json_path)
{
Debug.Assert(t_resolve_sdk2_resolved_sdk_dir is null);
Debug.Assert(t_resolve_sdk2_global_json_path is null);
try
{
unsafe
{
int result = hostfxr_resolve_sdk2(exe_dir, working_dir, flags, &hostfxr_resolve_sdk2_callback);
resolved_sdk_dir = t_resolve_sdk2_resolved_sdk_dir;
global_json_path = t_resolve_sdk2_global_json_path;
return result;
}
}
finally
{
t_resolve_sdk2_resolved_sdk_dir = null;
t_resolve_sdk2_global_json_path = null;
}
}

[DllImport(HostFxrName, CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int hostfxr_resolve_sdk2(
[LibraryImport(HostFxrName, StringMarshallingCustomType = typeof(AutoStringMarshaller))]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe partial int hostfxr_resolve_sdk2(
string exe_dir,
string working_dir,
hostfxr_resolve_sdk2_flags_t flags,
hostfxr_resolve_sdk2_result_fn result);
delegate* unmanaged[Cdecl]<hostfxr_resolve_sdk2_result_key_t, void*, void> result);

[ThreadStatic]
private static string t_resolve_sdk2_resolved_sdk_dir, t_resolve_sdk2_global_json_path;

[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void hostfxr_resolve_sdk2_callback(hostfxr_resolve_sdk2_result_key_t key, void* value)
{
string str = AutoStringMarshaller.ConvertToManaged(value);
switch (key)
{
case hostfxr_resolve_sdk2_result_key_t.resolved_sdk_dir:
t_resolve_sdk2_resolved_sdk_dir = str;
break;
case hostfxr_resolve_sdk2_result_key_t.global_json_path:
t_resolve_sdk2_global_json_path = str;
break;
}
}

internal static int hostfxr_get_available_sdks(string exe_dir, out string[] sdks)
{
Debug.Assert(t_get_available_sdks_result is null);
try
{
unsafe
{
int result = hostfxr_get_available_sdks(exe_dir, &hostfxr_get_available_sdks_callback);
sdks = t_get_available_sdks_result;
return result;
}
}
finally
{
t_get_available_sdks_result = null;
}
}

[LibraryImport(HostFxrName, StringMarshallingCustomType = typeof(AutoStringMarshaller))]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe partial int hostfxr_get_available_sdks(string exe_dir, delegate* unmanaged[Cdecl]<int, void**, void> result);

[ThreadStatic]
private static string[] t_get_available_sdks_result;

[DllImport(HostFxrName, CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int hostfxr_get_available_sdks(string exe_dir, hostfxr_get_available_sdks_result_fn result);
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void hostfxr_get_available_sdks_callback(int count, void** sdks)
{
string[] result = new string[count];
for (int i = 0; i < count; i++)
{
result[i] = AutoStringMarshaller.ConvertToManaged(sdks[i]);
}
t_get_available_sdks_result = result;
}

[CustomMarshaller(typeof(string), MarshalMode.Default, typeof(AutoStringMarshaller))]
internal static unsafe class AutoStringMarshaller
{
public static void* ConvertToUnmanaged(string s) => (void*)Marshal.StringToCoTaskMemAuto(s);

[DllImport("libc", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr realpath(string path, IntPtr buffer);
public static void Free(void* ptr) => Marshal.FreeCoTaskMem((nint)ptr);

[DllImport("libc", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern void free(IntPtr ptr);
public static string ConvertToManaged(void* ptr) => Marshal.PtrToStringAuto((nint)ptr);
}
}
}
#endif
Loading