Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return meaningful error when pinned SDK version is not found. #2403

Merged
merged 1 commit into from
May 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
}