Skip to content

Commit 8acc4c3

Browse files
Make SDK resolution error messages more helpful. (#341)
* Make SDK resolution error messages more helpful. * Shorten exception message.
1 parent 6bbaaa5 commit 8acc4c3

File tree

2 files changed

+59
-8
lines changed

2 files changed

+59
-8
lines changed

src/MSBuildLocator/DotNetSdkLocationHelper.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Reflection;
1212
using System.Runtime.InteropServices;
1313
using System.Runtime.Loader;
14+
using System.Text;
1415
using System.Text.RegularExpressions;
1516

1617
#nullable enable
@@ -117,11 +118,13 @@ public static IEnumerable<VisualStudioInstance> GetInstances(string workingDirec
117118
static IEnumerable<string> GetAllAvailableSDKs(bool allowAllDotnetLocations)
118119
{
119120
bool foundSdks = false;
121+
int rc = 0;
122+
StringBuilder? errorMessage = null;
120123
foreach (string dotnetPath in s_dotnetPathCandidates.Value)
121124
{
122-
int rc = NativeMethods.hostfxr_get_available_sdks(exe_dir: dotnetPath, out string[]? resolvedPaths);
125+
rc = NativeMethods.hostfxr_get_available_sdks(exe_dir: dotnetPath, out string[]? resolvedPaths, out errorMessage);
123126

124-
if (rc == 0 && resolvedPaths != null)
127+
if (resolvedPaths != null)
125128
{
126129
foundSdks = true;
127130

@@ -140,17 +143,19 @@ static IEnumerable<string> GetAllAvailableSDKs(bool allowAllDotnetLocations)
140143
// Errors are automatically printed to stderr. We should not continue to try to output anything if we failed.
141144
if (!foundSdks)
142145
{
143-
throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_get_available_sdks)));
146+
throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_get_available_sdks), rc, errorMessage));
144147
}
145148
}
146149

147150
// Determines the directory location of the SDK accounting for global.json and multi-level lookup policy.
148151
static string? GetSdkFromGlobalSettings(string workingDirectory)
149152
{
150153
string? resolvedSdk = null;
154+
int rc = 0;
155+
StringBuilder? errorMessage = null;
151156
foreach (string dotnetPath in s_dotnetPathCandidates.Value)
152157
{
153-
int rc = NativeMethods.hostfxr_resolve_sdk2(exe_dir: dotnetPath, working_dir: workingDirectory, flags: 0, out resolvedSdk, out _);
158+
rc = NativeMethods.hostfxr_resolve_sdk2(exe_dir: dotnetPath, working_dir: workingDirectory, flags: 0, out resolvedSdk, out _, out errorMessage);
154159

155160
if (rc == 0)
156161
{
@@ -160,7 +165,7 @@ static IEnumerable<string> GetAllAvailableSDKs(bool allowAllDotnetLocations)
160165
}
161166

162167
return string.IsNullOrEmpty(resolvedSdk)
163-
? throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_resolve_sdk2)))
168+
? throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_resolve_sdk2), rc, errorMessage))
164169
: resolvedSdk;
165170
}
166171
}
@@ -229,7 +234,8 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName)
229234
throw new InvalidOperationException(error);
230235
}
231236

232-
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.";
237+
private static string SdkResolutionExceptionMessage(string methodName, int rc, StringBuilder? errorMessage) =>
238+
$"Error while calling hostfxr function {methodName}. Error code: {rc} Detailed error: {errorMessage}";
233239

234240
private static List<string> ResolveDotnetPathCandidates()
235241
{

src/MSBuildLocator/NativeMethods.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Runtime.CompilerServices;
88
using System.Runtime.InteropServices;
99
using System.Runtime.InteropServices.Marshalling;
10+
using System.Text;
1011

1112
namespace Microsoft.Build.Locator
1213
{
@@ -25,17 +26,19 @@ private enum hostfxr_resolve_sdk2_result_key_t
2526
global_json_path = 1,
2627
};
2728

28-
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)
29+
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)
2930
{
3031
Debug.Assert(t_resolve_sdk2_resolved_sdk_dir is null);
3132
Debug.Assert(t_resolve_sdk2_global_json_path is null);
3233
try
3334
{
3435
unsafe
3536
{
37+
using var errorHandler = new ErrorHandler();
3638
int result = hostfxr_resolve_sdk2(exe_dir, working_dir, flags, &hostfxr_resolve_sdk2_callback);
3739
resolved_sdk_dir = t_resolve_sdk2_resolved_sdk_dir;
3840
global_json_path = t_resolve_sdk2_global_json_path;
41+
errorMessage = t_hostfxr_error_builder;
3942
return result;
4043
}
4144
}
@@ -72,15 +75,17 @@ private static unsafe void hostfxr_resolve_sdk2_callback(hostfxr_resolve_sdk2_re
7275
}
7376
}
7477

75-
internal static int hostfxr_get_available_sdks(string exe_dir, out string[] sdks)
78+
internal static int hostfxr_get_available_sdks(string exe_dir, out string[] sdks, out StringBuilder errorMessage)
7679
{
7780
Debug.Assert(t_get_available_sdks_result is null);
7881
try
7982
{
8083
unsafe
8184
{
85+
using var errorHandler = new ErrorHandler();
8286
int result = hostfxr_get_available_sdks(exe_dir, &hostfxr_get_available_sdks_callback);
8387
sdks = t_get_available_sdks_result;
88+
errorMessage = t_hostfxr_error_builder;
8489
return result;
8590
}
8691
}
@@ -108,6 +113,29 @@ private static unsafe void hostfxr_get_available_sdks_callback(int count, void**
108113
t_get_available_sdks_result = result;
109114
}
110115

116+
[LibraryImport(HostFxrName)]
117+
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
118+
private static unsafe partial delegate* unmanaged[Cdecl]<void*, void> hostfxr_set_error_writer(delegate* unmanaged[Cdecl]<void*, void> error_writer);
119+
120+
[ThreadStatic]
121+
private static StringBuilder t_hostfxr_error_builder;
122+
123+
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
124+
private static unsafe void hostfxr_error_writer_callback(void* message)
125+
{
126+
t_hostfxr_error_builder ??= new StringBuilder();
127+
if (OperatingSystem.IsWindows())
128+
{
129+
// Avoid allocating temporary string on Windows.
130+
t_hostfxr_error_builder.Append(MemoryMarshal.CreateReadOnlySpanFromNullTerminated((char*)message));
131+
t_hostfxr_error_builder.AppendLine();
132+
}
133+
else
134+
{
135+
t_hostfxr_error_builder.AppendLine(Utf8StringMarshaller.ConvertToManaged((byte*)message));
136+
}
137+
}
138+
111139
[CustomMarshaller(typeof(string), MarshalMode.Default, typeof(AutoStringMarshaller))]
112140
internal static unsafe class AutoStringMarshaller
113141
{
@@ -117,6 +145,23 @@ internal static unsafe class AutoStringMarshaller
117145

118146
public static string ConvertToManaged(void* ptr) => Marshal.PtrToStringAuto((nint)ptr);
119147
}
148+
149+
private unsafe readonly ref struct ErrorHandler
150+
{
151+
private readonly delegate* unmanaged[Cdecl]<void*, void> _previousCallback;
152+
153+
public ErrorHandler()
154+
{
155+
Debug.Assert(t_hostfxr_error_builder is null);
156+
_previousCallback = hostfxr_set_error_writer(&hostfxr_error_writer_callback);
157+
}
158+
159+
public void Dispose()
160+
{
161+
hostfxr_set_error_writer(_previousCallback);
162+
t_hostfxr_error_builder = null;
163+
}
164+
}
120165
}
121166
}
122167
#endif

0 commit comments

Comments
 (0)