Skip to content

Commit

Permalink
Merge pull request #2403 from JoeRobich/return-meaningful-error
Browse files Browse the repository at this point in the history
Return meaningful error when pinned SDK version is not found.
  • Loading branch information
filipw authored May 28, 2022
2 parents c6f1e84 + a6d5fe1 commit c723326
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 14 deletions.
65 changes: 65 additions & 0 deletions src/OmniSharp.Abstractions/Services/DotNetVersion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;

namespace OmniSharp.Services
{
public class DotNetVersion
{
public static DotNetVersion FailedToStartError { get; } = new DotNetVersion("`dotnet --version` failed to start.");

public bool HasError { get; }
public string ErrorMessage { get; }

public SemanticVersion Version { get; }

private DotNetVersion(SemanticVersion version)
{
Version = version;
}

private DotNetVersion(string errorMessage)
{
HasError = true;
ErrorMessage = errorMessage;
}

public static DotNetVersion Parse(List<string> lines)
{
if (lines == null || lines.Count == 0)
{
return new DotNetVersion("`dotnet --version` produced no output.");
}

if (SemanticVersion.TryParse(lines[0], out var version))
{
return new DotNetVersion(version);
}

var requestedSdkVersion = string.Empty;
var globalJsonFile = string.Empty;

foreach (var line in lines)
{
var colonIndex = line.IndexOf(':');
if (colonIndex >= 0)
{
var name = line.Substring(0, colonIndex).Trim();
var value = line.Substring(colonIndex + 1).Trim();

if (string.IsNullOrEmpty(requestedSdkVersion) && name.Equals("Requested SDK version", StringComparison.OrdinalIgnoreCase))
{
requestedSdkVersion = value;
}
else if (string.IsNullOrEmpty(globalJsonFile) && name.Equals("global.json file", StringComparison.OrdinalIgnoreCase))
{
globalJsonFile = value;
}
}
}

return requestedSdkVersion.Length > 0 && globalJsonFile.Length > 0
? new DotNetVersion($"Install the [{requestedSdkVersion}] .NET SDK or update [{globalJsonFile}] to match an installed SDK.")
: new DotNetVersion($"Unexpected output from `dotnet --version`: {string.Join(Environment.NewLine, lines)}");
}
}
}
6 changes: 3 additions & 3 deletions src/OmniSharp.Abstractions/Services/IDotNetCliService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public interface IDotNetCliService

/// <summary>
/// Launches "dotnet --version" in the given working directory and returns a
/// <see cref="SemanticVersion"/> representing the returned version text.
/// <see cref="DotNetVersion"/> representing the returned version text.
/// </summary>
SemanticVersion GetVersion(string workingDirectory = null);
DotNetVersion GetVersion(string workingDirectory = null);

/// <summary>
/// Launches "dotnet --version" in the given working directory and determines
Expand All @@ -36,7 +36,7 @@ public interface IDotNetCliService
/// .NET CLI. If true, this .NET CLI supports project.json development;
/// otherwise, it supports .csproj development.
/// </summary>
bool IsLegacy(SemanticVersion version);
bool IsLegacy(DotNetVersion version);

/// <summary>
/// Launches "dotnet restore" in the given working directory.
Expand Down
17 changes: 14 additions & 3 deletions src/OmniSharp.DotNetTest/TestManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,18 @@ public static TestManager Create(Project project, IDotNetCliService dotNetCli, I

var version = dotNetCli.GetVersion(workingDirectory);

if (version.HasError)
{
EmitTestMessage(eventEmitter, TestMessageLevel.Error, version.ErrorMessage);
throw new Exception(version.ErrorMessage);
}

if (dotNetCli.IsLegacy(version))
{
throw new NotSupportedException("Legacy .NET SDK is not supported");
}

return (TestManager)new VSTestManager(project, workingDirectory, dotNetCli, version, eventEmitter, loggerFactory);
return (TestManager)new VSTestManager(project, workingDirectory, dotNetCli, version.Version, eventEmitter, loggerFactory);
}

protected abstract string GetCliTestArguments(int port, int parentProcessId);
Expand Down Expand Up @@ -204,16 +210,21 @@ protected void EmitTestComletedEvent(DotNetTestResult result)
EventEmitter.Emit("TestCompleted", result);
}

protected void EmitTestMessage(TestMessageLevel messageLevel, string message)
private static void EmitTestMessage(IEventEmitter eventEmitter, TestMessageLevel messageLevel, string message)
{
EventEmitter.Emit(TestMessageEvent.Id,
eventEmitter.Emit(TestMessageEvent.Id,
new TestMessageEvent
{
MessageLevel = messageLevel.ToString().ToLowerInvariant(),
Message = message
});
}

protected void EmitTestMessage(TestMessageLevel messageLevel, string message)
{
EmitTestMessage(EventEmitter, messageLevel, message);
}

protected void EmitTestMessage(TestMessagePayload testMessage)
{
EmitTestMessage(testMessage.MessageLevel, testMessage.Message);
Expand Down
67 changes: 60 additions & 7 deletions src/OmniSharp.Host/Services/DotNetCliService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OmniSharp.Eventing;
Expand All @@ -17,6 +16,8 @@ namespace OmniSharp.Services
{
internal class DotNetCliService : IDotNetCliService
{
const string DOTNET_CLI_UI_LANGUAGE = nameof(DOTNET_CLI_UI_LANGUAGE);

private readonly ILogger _logger;
private readonly IEventEmitter _eventEmitter;
private readonly ConcurrentDictionary<string, object> _locks;
Expand Down Expand Up @@ -128,17 +129,62 @@ public Process Start(string arguments, string workingDirectory)
return Process.Start(startInfo);
}

public SemanticVersion GetVersion(string workingDirectory = null)
public DotNetVersion GetVersion(string workingDirectory = null)
{
var output = ProcessHelper.RunAndCaptureOutput(DotNetPath, "--version", workingDirectory);
// Ensure that we set the DOTNET_CLI_UI_LANGUAGE environment variable to "en-US" before
// running 'dotnet --version'. Otherwise, we may get localized results.
var originalValue = Environment.GetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE);
Environment.SetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE, "en-US");

try
{
Process process;
try
{
process = Start("--version", workingDirectory);
}
catch
{
return DotNetVersion.FailedToStartError;
}

if (process.HasExited)
{
return DotNetVersion.FailedToStartError;
}

var lines = new List<string>();
process.OutputDataReceived += (_, e) =>
{
if (!string.IsNullOrWhiteSpace(e.Data))
{
lines.Add(e.Data);
}
};

process.ErrorDataReceived += (_, e) =>
{
if (!string.IsNullOrWhiteSpace(e.Data))
{
lines.Add(e.Data);
}
};

process.BeginOutputReadLine();
process.BeginErrorReadLine();

return SemanticVersion.Parse(output);
process.WaitForExit();

return DotNetVersion.Parse(lines);
}
finally
{
Environment.SetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE, originalValue);
}
}

public DotNetInfo GetInfo(string workingDirectory = null)
{
const string DOTNET_CLI_UI_LANGUAGE = nameof(DOTNET_CLI_UI_LANGUAGE);

// Ensure that we set the DOTNET_CLI_UI_LANGUAGE environment variable to "en-US" before
// running 'dotnet --info'. Otherwise, we may get localized results.
var originalValue = Environment.GetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE);
Expand Down Expand Up @@ -197,8 +243,15 @@ public bool IsLegacy(string workingDirectory = null)
/// Determines whether the specified version is from a "legacy" .NET CLI.
/// If true, this .NET CLI supports project.json development; otherwise, it supports .csproj development.
/// </summary>
public bool IsLegacy(SemanticVersion version)
public bool IsLegacy(DotNetVersion dotnetVersion)
{
if (dotnetVersion.HasError)
{
return false;
}

var version = dotnetVersion.Version;

if (version.Major < 1)
{
return true;
Expand Down
6 changes: 5 additions & 1 deletion tests/OmniSharp.Tests/DotNetCliServiceFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ public void GetVersion()
{
var dotNetCli = host.GetExport<IDotNetCliService>();

var version = dotNetCli.GetVersion();
var cliVersion = dotNetCli.GetVersion();

Assert.False(cliVersion.HasError);

var version = cliVersion.Version;

Assert.Equal(Major, version.Major);
Assert.Equal(Minor, version.Minor);
Expand Down
64 changes: 64 additions & 0 deletions tests/OmniSharp.Tests/DotNetVersionFacts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Collections.Generic;
using OmniSharp.Services;
using TestUtility;
using Xunit;
using Xunit.Abstractions;

namespace OmniSharp.Tests
{
public class DotNetVersionFacts : AbstractTestFixture
{
public DotNetVersionFacts(ITestOutputHelper output)
: base(output)
{
}

[Theory]
[InlineData("6.0.201")]
[InlineData("7.0.100-preview.2.22153.17")]
public void ParseVersion(string versionString)
{
var cliVersion = DotNetVersion.Parse(new() { versionString });

Assert.False(cliVersion.HasError, $"{versionString} did not successfully parse.");

Assert.Equal(versionString, cliVersion.Version.ToString());
}

[Fact]
public void ParseErrorMessage()
{
const string RequestedSdkVersion = "6.0.301-rtm.22263.15";
const string GlobalJsonFile = "/Users/username/Source/format/global.json";
const string ExpectedErrorMessage = $"Install the [{RequestedSdkVersion}] .NET SDK or update [{GlobalJsonFile}] to match an installed SDK.";

var lines = new List<string>() {
"The command could not be loaded, possibly because:",
" * You intended to execute a .NET application:",
" The application '--version' does not exist.",
" * You intended to execute a .NET SDK command:",
" A compatible .NET SDK was not found.",
"",
$"Requested SDK version: {RequestedSdkVersion}",
$"global.json file: {GlobalJsonFile}",
"",
"Installed SDKs:",
"6.0.105 [/usr/local/share/dotnet/sdk]",
"6.0.202 [/usr/local/share/dotnet/sdk]",
"6.0.300 [/usr/local/share/dotnet/sdk]",
"7.0.100-preview.4.22252.9 [/usr/local/share/dotnet/sdk]",
"",
$"Install the [{RequestedSdkVersion}] .NET SDK or update [{GlobalJsonFile}] to match an installed SDK.",
"",
"Learn about SDK resolution:",
"https://aka.ms/dotnet/sdk-not-found"
};

var cliVersion = DotNetVersion.Parse(lines);

Assert.True(cliVersion.HasError);

Assert.Equal(ExpectedErrorMessage, cliVersion.ErrorMessage);
}
}
}

0 comments on commit c723326

Please # to comment.