diff --git a/src/MSBuildLocator/DotNetSdkLocationHelper.cs b/src/MSBuildLocator/DotNetSdkLocationHelper.cs index 9c668d9..7fa678f 100644 --- a/src/MSBuildLocator/DotNetSdkLocationHelper.cs +++ b/src/MSBuildLocator/DotNetSdkLocationHelper.cs @@ -11,6 +11,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Loader; +using System.Text; using System.Text.RegularExpressions; #nullable enable @@ -117,11 +118,13 @@ public static IEnumerable GetInstances(string workingDirec static IEnumerable GetAllAvailableSDKs(bool allowAllDotnetLocations) { bool foundSdks = false; + int rc = 0; + StringBuilder? errorMessage = null; foreach (string dotnetPath in s_dotnetPathCandidates.Value) { - int rc = NativeMethods.hostfxr_get_available_sdks(exe_dir: dotnetPath, out string[]? resolvedPaths); + rc = NativeMethods.hostfxr_get_available_sdks(exe_dir: dotnetPath, out string[]? resolvedPaths, out errorMessage); - if (rc == 0 && resolvedPaths != null) + if (resolvedPaths != null) { foundSdks = true; @@ -140,7 +143,7 @@ static IEnumerable GetAllAvailableSDKs(bool allowAllDotnetLocations) // Errors are automatically printed to stderr. We should not continue to try to output anything if we failed. if (!foundSdks) { - throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_get_available_sdks))); + throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_get_available_sdks), rc, errorMessage)); } } @@ -148,9 +151,11 @@ static IEnumerable GetAllAvailableSDKs(bool allowAllDotnetLocations) static string? GetSdkFromGlobalSettings(string workingDirectory) { string? resolvedSdk = null; + int rc = 0; + StringBuilder? errorMessage = null; foreach (string dotnetPath in s_dotnetPathCandidates.Value) { - int rc = NativeMethods.hostfxr_resolve_sdk2(exe_dir: dotnetPath, working_dir: workingDirectory, flags: 0, out resolvedSdk, out _); + rc = NativeMethods.hostfxr_resolve_sdk2(exe_dir: dotnetPath, working_dir: workingDirectory, flags: 0, out resolvedSdk, out _, out errorMessage); if (rc == 0) { @@ -160,7 +165,7 @@ static IEnumerable GetAllAvailableSDKs(bool allowAllDotnetLocations) } return string.IsNullOrEmpty(resolvedSdk) - ? throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_resolve_sdk2))) + ? throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_resolve_sdk2), rc, errorMessage)) : resolvedSdk; } } @@ -229,7 +234,8 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName) throw new InvalidOperationException(error); } - 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 string SdkResolutionExceptionMessage(string methodName, int rc, StringBuilder? errorMessage) => + $"Error while calling hostfxr function {methodName}. Error code: {rc} Detailed error: {errorMessage}"; private static List ResolveDotnetPathCandidates() { diff --git a/src/MSBuildLocator/NativeMethods.cs b/src/MSBuildLocator/NativeMethods.cs index 5072c7a..f358271 100644 --- a/src/MSBuildLocator/NativeMethods.cs +++ b/src/MSBuildLocator/NativeMethods.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using System.Text; namespace Microsoft.Build.Locator { @@ -25,7 +26,7 @@ private enum hostfxr_resolve_sdk2_result_key_t global_json_path = 1, }; - 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) + 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, out StringBuilder errorMessage) { Debug.Assert(t_resolve_sdk2_resolved_sdk_dir is null); Debug.Assert(t_resolve_sdk2_global_json_path is null); @@ -33,9 +34,11 @@ internal static int hostfxr_resolve_sdk2(string exe_dir, string working_dir, hos { unsafe { + using var errorHandler = new ErrorHandler(); 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; + errorMessage = t_hostfxr_error_builder; return result; } } @@ -72,15 +75,17 @@ private static unsafe void hostfxr_resolve_sdk2_callback(hostfxr_resolve_sdk2_re } } - internal static int hostfxr_get_available_sdks(string exe_dir, out string[] sdks) + internal static int hostfxr_get_available_sdks(string exe_dir, out string[] sdks, out StringBuilder errorMessage) { Debug.Assert(t_get_available_sdks_result is null); try { unsafe { + using var errorHandler = new ErrorHandler(); int result = hostfxr_get_available_sdks(exe_dir, &hostfxr_get_available_sdks_callback); sdks = t_get_available_sdks_result; + errorMessage = t_hostfxr_error_builder; return result; } } @@ -108,6 +113,29 @@ private static unsafe void hostfxr_get_available_sdks_callback(int count, void** t_get_available_sdks_result = result; } + [LibraryImport(HostFxrName)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + private static unsafe partial delegate* unmanaged[Cdecl] hostfxr_set_error_writer(delegate* unmanaged[Cdecl] error_writer); + + [ThreadStatic] + private static StringBuilder t_hostfxr_error_builder; + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static unsafe void hostfxr_error_writer_callback(void* message) + { + t_hostfxr_error_builder ??= new StringBuilder(); + if (OperatingSystem.IsWindows()) + { + // Avoid allocating temporary string on Windows. + t_hostfxr_error_builder.Append(MemoryMarshal.CreateReadOnlySpanFromNullTerminated((char*)message)); + t_hostfxr_error_builder.AppendLine(); + } + else + { + t_hostfxr_error_builder.AppendLine(Utf8StringMarshaller.ConvertToManaged((byte*)message)); + } + } + [CustomMarshaller(typeof(string), MarshalMode.Default, typeof(AutoStringMarshaller))] internal static unsafe class AutoStringMarshaller { @@ -117,6 +145,23 @@ internal static unsafe class AutoStringMarshaller public static string ConvertToManaged(void* ptr) => Marshal.PtrToStringAuto((nint)ptr); } + + private unsafe readonly ref struct ErrorHandler + { + private readonly delegate* unmanaged[Cdecl] _previousCallback; + + public ErrorHandler() + { + Debug.Assert(t_hostfxr_error_builder is null); + _previousCallback = hostfxr_set_error_writer(&hostfxr_error_writer_callback); + } + + public void Dispose() + { + hostfxr_set_error_writer(_previousCallback); + t_hostfxr_error_builder = null; + } + } } } #endif