Skip to content

Commit

Permalink
Fix .NET SDK resolution (#455)
Browse files Browse the repository at this point in the history
* Add proper error message to failure case for .NET SDK resolution
* Process result of SDK resolution and bubble up to main DevelopmentEnvironment
  • Loading branch information
michael-hawker authored Feb 21, 2023
1 parent 085038d commit 201237f
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 58 deletions.
62 changes: 28 additions & 34 deletions src/Shared/DevelopmentEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ namespace Microsoft.VisualStudio.SlnGen
/// </summary>
public sealed class DevelopmentEnvironment
{
private DevelopmentEnvironment(params string[] errors)
/// <summary>
/// Initializes a new instance of the <see cref="DevelopmentEnvironment" /> class with the specified errors.
/// </summary>
/// <param name="errors">The list of errors to associate with the current development environment.</param>
public DevelopmentEnvironment(params string[] errors)
: this()
{
Errors = errors.ToList();
Expand All @@ -29,14 +33,14 @@ private DevelopmentEnvironment()
}

/// <summary>
/// Gets the current .NET SDK major version.
/// Gets or sets the current .NET SDK major version.
/// </summary>
public string DotNetSdkMajorVersion { get; private set; }
public string DotNetSdkMajorVersion { get; set; }

/// <summary>
/// Gets the current .NET SDK version.
/// Gets or sets the current .NET SDK version.
/// </summary>
public string DotNetSdkVersion { get; private set; }
public string DotNetSdkVersion { get; set; }

/// <summary>
/// Gets any errors encountered while determining the development environment.
Expand All @@ -49,9 +53,9 @@ private DevelopmentEnvironment()
public bool IsCorext { get; private set; }

/// <summary>
/// Gets a <see cref="FileInfo" /> object for the MSBuild.dll that was found.
/// Gets or sets a <see cref="FileInfo" /> object for the MSBuild.dll that was found.
/// </summary>
public FileInfo MSBuildDll { get; private set; }
public FileInfo MSBuildDll { get; set; }

/// <summary>
/// Gets a <see cref="FileInfo" /> object for the MSBuild.exe that was found.
Expand Down Expand Up @@ -125,19 +129,9 @@ public static DevelopmentEnvironment LoadCurrentDevelopmentEnvironment(IEnvironm
return new DevelopmentEnvironment("SlnGen must be run from a command-line window where dotnet.exe is on the PATH.");
}

if (!DotNetCoreSdkResolver.TryResolveDotNetCoreSdk(environmentProvider, dotnetFileInfo, out DirectoryInfo dotnetCoreSdkDirectoryInfo))
{
return new DevelopmentEnvironment(string.Empty);
}

DevelopmentEnvironment developmentEnvironment = new DevelopmentEnvironment
{
DotNetSdkVersion = dotnetCoreSdkDirectoryInfo.Name,
DotNetSdkMajorVersion = dotnetCoreSdkDirectoryInfo.Name.Substring(0, dotnetCoreSdkDirectoryInfo.Name.IndexOf(".", StringComparison.OrdinalIgnoreCase)),
MSBuildDll = new FileInfo(Path.Combine(dotnetCoreSdkDirectoryInfo.FullName, "MSBuild.dll")),
};

if (Utility.RunningOnWindows && Utility.TryFindOnPath(environmentProvider, "MSBuild.exe", IsMSBuildExeCompatible, out FileInfo msbuildExeFileInfo))
if (DotNetCoreSdkResolver.TryResolveDotNetCoreSdk(environmentProvider, dotnetFileInfo, out DevelopmentEnvironment developmentEnvironment)
&& Utility.RunningOnWindows
&& Utility.TryFindOnPath(environmentProvider, "MSBuild.exe", IsMSBuildExeCompatible, out FileInfo msbuildExeFileInfo))
{
developmentEnvironment.MSBuildExe = GetPathToMSBuildExe(msbuildExeFileInfo);
developmentEnvironment.VisualStudio = VisualStudioConfiguration.GetInstanceForPath(msbuildExeFileInfo.FullName);
Expand All @@ -147,20 +141,6 @@ public static DevelopmentEnvironment LoadCurrentDevelopmentEnvironment(IEnvironm
#endif
}

private static bool IsMSBuildExeCompatible(FileInfo fileInfo)
{
try
{
FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(fileInfo.FullName);

return fileVersionInfo.FileMajorPart >= 15;
}
catch (Exception)
{
return false;
}
}

/// <summary>
/// Gets the path to MSBuild.exe based on the current processor architecture.
/// </summary>
Expand All @@ -185,5 +165,19 @@ private static FileInfo GetPathToMSBuildExe(FileInfo msbuildExeFileInfo)

return msbuildExeFileInfo;
}

private static bool IsMSBuildExeCompatible(FileInfo fileInfo)
{
try
{
FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(fileInfo.FullName);

return fileVersionInfo.FileMajorPart >= 15;
}
catch (Exception)
{
return false;
}
}
}
}
81 changes: 57 additions & 24 deletions src/Shared/DotNetCoreSdkResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,15 @@ public static class DotNetCoreSdkResolver
/// </summary>
/// <param name="environmentProvider">An <see cref="IEnvironmentProvider" /> to use when accessing the environment.</param>
/// <param name="dotnetFileInfo">A <see cref="FileInfo" /> representing the path to dotnet.exe.</param>
/// <param name="basePath">Receives the root path of the .NET Core SDK if one is found.</param>
/// <param name="developmentEnvironment">Receives a <see cref="DevelopmentEnvironment" /> instance containing details of the .NET Core SDK if one is found.</param>
/// <returns><code>true</code> if a .NET Core SDK could be located, otherwise <code>false</code>.</returns>
public static bool TryResolveDotNetCoreSdk(IEnvironmentProvider environmentProvider, FileInfo dotnetFileInfo, out DirectoryInfo basePath)
public static bool TryResolveDotNetCoreSdk(IEnvironmentProvider environmentProvider, FileInfo dotnetFileInfo, out DevelopmentEnvironment developmentEnvironment)
{
if (environmentProvider is null)
{
throw new ArgumentNullException(nameof(environmentProvider));
}

basePath = null;

string parsedBasePath = null;

using ManualResetEvent processExited = new ManualResetEvent(false);
Expand Down Expand Up @@ -78,11 +76,15 @@ public static bool TryResolveDotNetCoreSdk(IEnvironmentProvider environmentProvi
{
if (!process.Start())
{
developmentEnvironment = new DevelopmentEnvironment("Failed to resolve the .NET SDK. Verify the 'dotnet' command is available on the PATH.");

return false;
}
}
catch (Exception)
catch (Exception e)
{
developmentEnvironment = new DevelopmentEnvironment("Failed to resolve the .NET SDK. An exception occurred running the 'dotnet --info' command: ", e.ToString());

return false;
}

Expand All @@ -109,27 +111,63 @@ public static bool TryResolveDotNetCoreSdk(IEnvironmentProvider environmentProvi
}
}

DirectoryInfo basePath;

if (!string.IsNullOrWhiteSpace(parsedBasePath))
{
basePath = new DirectoryInfo(parsedBasePath);

return true;
}
else
{
(string sdkDirectory, string globalJsonPath, string requestedVersionNumber) = ResolveSdk(environmentProvider, dotnetFileInfo.Directory);

Console.ForegroundColor = ConsoleColor.Red;
Console.BackgroundColor = ConsoleColor.Black;
if (string.IsNullOrWhiteSpace(sdkDirectory))
{
developmentEnvironment = new DevelopmentEnvironment($"Failed to resolve the .NET SDK. The 'dotnet --info' command returned the path '{parsedBasePath}' and the .NET SDK resolver returned the path '{sdkDirectory}', a global.json path of '{globalJsonPath}', and a requested version of '{requestedVersionNumber}'.");

ResolveSdk(environmentProvider, dotnetFileInfo.Directory);
return false;
}

Console.ResetColor();
basePath = new DirectoryInfo(sdkDirectory);
}

developmentEnvironment = new DevelopmentEnvironment
{
DotNetSdkVersion = basePath.Name,
DotNetSdkMajorVersion = basePath.Name.Substring(0, basePath.Name.IndexOf(".", StringComparison.OrdinalIgnoreCase)),
MSBuildDll = new FileInfo(Path.Combine(basePath.FullName, "MSBuild.dll")),
};

return false;
return true;
}

private static (string sdkDirectory, string globalJsonPath) ResolveSdk(IEnvironmentProvider environmentProvider, DirectoryInfo dotnetExeDirectory)
private static (string sdkDirectory, string globalJsonPath, string requestedVersion) ResolveSdk(IEnvironmentProvider environmentProvider, DirectoryInfo dotnetExeDirectory)
{
string sdkDirectory = null;
string globalJsonPath = null;
string requestedVersionNumber = null;

// Set the console color to red in case the .NET SDK resolver logs any errors to the console
Console.ForegroundColor = ConsoleColor.Red;
Console.BackgroundColor = ConsoleColor.Black;

try
{
if (Utility.RunningOnWindows)
{
Windows.ResolveSdk(dotnetExeDirectory.FullName, environmentProvider.CurrentDirectory, 0 /* None */, HandleResolveSdkResult);
}
else
{
Unix.ResolveSdk(dotnetExeDirectory.FullName, environmentProvider.CurrentDirectory, 0 /* None */, HandleResolveSdkResult);
}

return (sdkDirectory, globalJsonPath, requestedVersionNumber);
}
finally
{
Console.ResetColor();
}

void HandleResolveSdkResult(int key, string value)
{
Expand All @@ -142,19 +180,12 @@ void HandleResolveSdkResult(int key, string value)
case 1: // GlobalJsonPath
globalJsonPath = value;
break;
}
}

if (Utility.RunningOnWindows)
{
Windows.ResolveSdk(dotnetExeDirectory.FullName, environmentProvider.CurrentDirectory, 0 /* None */, HandleResolveSdkResult);
}
else
{
Unix.ResolveSdk(dotnetExeDirectory.FullName, environmentProvider.CurrentDirectory, 0 /* None */, HandleResolveSdkResult);
case 2: // RequestedVersion
requestedVersionNumber = value;
break;
}
}

return (sdkDirectory, globalJsonPath);
}

private static class Unix
Expand All @@ -164,6 +195,7 @@ private static class Unix
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = UTF8)]
public delegate void HandleResolveSdkResult(int key, string value);

//// https://github.com/dotnet/dotnet/blob/83d91d61d4a5f16ceaef2e6f3e5f18970e5d2d27/src/runtime/src/native/corehost/fxr/hostfxr.cpp#L237
[DllImport(HostFxr, EntryPoint = "hostfxr_resolve_sdk2", CharSet = UTF8, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ResolveSdk(string dotnetExeDirectory, string workingDirectory, int flags, HandleResolveSdkResult handleSdkResult);
}
Expand All @@ -175,6 +207,7 @@ private static class Windows
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = UTF16)]
public delegate void HandleResolveSdkResult(int key, string value);

//// https://github.com/dotnet/dotnet/blob/83d91d61d4a5f16ceaef2e6f3e5f18970e5d2d27/src/runtime/src/native/corehost/fxr/hostfxr.cpp#L237
[DllImport(HostFxr, EntryPoint = "hostfxr_resolve_sdk2", CharSet = UTF16, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ResolveSdk(string dotnetExeDirectory, string workingDirectory, int flags, HandleResolveSdkResult handleSdkResult);
}
Expand Down

0 comments on commit 201237f

Please sign in to comment.