diff --git a/src/MSBuildLocator/DotNetSdkLocationHelper.cs b/src/MSBuildLocator/DotNetSdkLocationHelper.cs index 58cb54d..9c668d9 100644 --- a/src/MSBuildLocator/DotNetSdkLocationHelper.cs +++ b/src/MSBuildLocator/DotNetSdkLocationHelper.cs @@ -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> 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> s_dotnetPathCandidates = new(() => ResolveDotnetPathCandidates()); public static VisualStudioInstance? GetInstance(string dotNetSdkPath, bool allowQueryAllRuntimeVersions) { @@ -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) { @@ -116,10 +117,9 @@ public static IEnumerable GetInstances(string workingDirec static IEnumerable 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) { @@ -150,13 +150,7 @@ static IEnumerable 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) { @@ -178,7 +172,7 @@ static IEnumerable GetAllAvailableSDKs(bool allowAllDotnetLocations) private static void ModifyUnmanagedDllResolver(Action resolverAction) { // For Windows hostfxr is loaded in the process. - if (!IsWindows) + if (!OperatingSystem.IsWindows()) { var loadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()); if (loadContext != null) @@ -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 @@ -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 ResolveDotnetPathCandidates() + private static List ResolveDotnetPathCandidates() { var pathCandidates = new List(); AddIfValid(GetDotnetPathFromROOT()); - string? dotnetExePath = GetCurrentProcessPath(); + string? dotnetExePath = Environment.ProcessPath; bool isRunFromDotnetExecutable = !string.IsNullOrEmpty(dotnetExePath) && Path.GetFileName(dotnetExePath).Equals(ExeName, StringComparison.InvariantCultureIgnoreCase); @@ -254,9 +248,9 @@ private static IList 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)); @@ -289,8 +283,6 @@ void AddIfValid(string? path) return dotnetPath; } - private static string? GetCurrentProcessPath() => Environment.ProcessPath; - private static string? GetDotnetPathFromPATH() { string? dotnetPath = null; @@ -314,19 +306,6 @@ void AddIfValid(string? path) return dotnetPath; } - /// - /// This native method call determines the actual location of path, including - /// resolving symbolic links. - /// - 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); @@ -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; } diff --git a/src/MSBuildLocator/Microsoft.Build.Locator.csproj b/src/MSBuildLocator/Microsoft.Build.Locator.csproj index bdfe73d..7308e50 100644 --- a/src/MSBuildLocator/Microsoft.Build.Locator.csproj +++ b/src/MSBuildLocator/Microsoft.Build.Locator.csproj @@ -14,6 +14,7 @@ msbuildlocator;locator;buildlocator true 1.6.1 + true $(DefineConstants);FEATURE_VISUALSTUDIOSETUP diff --git a/src/MSBuildLocator/NativeMethods.cs b/src/MSBuildLocator/NativeMethods.cs index 6f1f3dc..5072c7a 100644 --- a/src/MSBuildLocator/NativeMethods.cs +++ b/src/MSBuildLocator/NativeMethods.cs @@ -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"; @@ -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] 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] 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