diff --git a/src/QsCompiler/LanguageServer/LanguageServer.cs b/src/QsCompiler/LanguageServer/LanguageServer.cs index 2bb16ce941..1403861341 100644 --- a/src/QsCompiler/LanguageServer/LanguageServer.cs +++ b/src/QsCompiler/LanguageServer/LanguageServer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reactive.Linq; @@ -123,9 +124,45 @@ public void Dispose() internal Task NotifyClientAsync(string method, object args) => this.rpc.NotifyWithParameterObjectAsync(method, args); // no need to wait for completion + internal Task InvokeAsync(string method, object args) => + this.rpc.InvokeWithParameterObjectAsync(method, args); + internal Task PublishDiagnosticsAsync(PublishDiagnosticParams diagnostics) => this.NotifyClientAsync(Methods.TextDocumentPublishDiagnosticsName, diagnostics); + internal async void CheckDotNetSdkVersion() + { + var isDotNet31Installed = DotNetSdkHelper.IsDotNet31Installed(); + if (isDotNet31Installed == null) + { + this.LogToWindow("Unable to detect .NET SDK versions", MessageType.Error); + } + else + { + if (isDotNet31Installed != true) + { + const string dotnet31Url = "https://dotnet.microsoft.com/download/dotnet-core/3.1"; + this.LogToWindow($".NET Core SDK 3.1 not found. Quantum Development Kit Extension requires .NET Core SDK 3.1 to work properly ({dotnet31Url}).", MessageType.Error); + var downloadAction = new MessageActionItem { Title = "Download" }; + var cancelAction = new MessageActionItem { Title = "No, thanks" }; + var selectedAction = await this.ShowDialogInWindowAsync( + "Quantum Development Kit Extension requires .NET Core SDK 3.1 to work properly. Please install .NET Core SDK 3.1 and restart Visual Studio.", + MessageType.Error, + new[] { downloadAction, cancelAction }); + if (selectedAction != null + && selectedAction.Title == downloadAction.Title) + { + Process.Start(new ProcessStartInfo + { + FileName = dotnet31Url, + UseShellExecute = true, + CreateNoWindow = true, + }); + } + } + } + } + /// /// does not actually do anything unless the corresponding flag is defined upon compilation /// diff --git a/src/QsCompiler/LanguageServer/LanguageServer.csproj b/src/QsCompiler/LanguageServer/LanguageServer.csproj index ade2b39519..5e0c4656cf 100644 --- a/src/QsCompiler/LanguageServer/LanguageServer.csproj +++ b/src/QsCompiler/LanguageServer/LanguageServer.csproj @@ -33,4 +33,9 @@ + + + PreserveNewest + + diff --git a/src/QsCompiler/LanguageServer/Program.cs b/src/QsCompiler/LanguageServer/Program.cs index 8e9c64dcfa..5115feddd6 100644 --- a/src/QsCompiler/LanguageServer/Program.cs +++ b/src/QsCompiler/LanguageServer/Program.cs @@ -83,6 +83,11 @@ private static int LogAndExit(ReturnCode code, string? logFile = null, string? m public static int Main(string[] args) { + // We need to set the current directory to the same directory of + // the LanguageServer executable so that it will pick the global.json file + // and force the MSBuildLocator to use .NET Core SDK 3.1 + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + var parser = new Parser(parser => parser.HelpWriter = null); // we want our own custom format for the version info var options = parser.ParseArguments(args); return options.MapResult( @@ -120,8 +125,11 @@ private static int Run(Options options) } catch (Exception ex) { + // Don't exit here, since exiting without establishing a connection will result in a cryptic failure of the extension. + // Instead, we proceed to create a server instance and establish the connection. + // Any errors can then be properly processed via the standard server-client communication as needed. Log("[ERROR] MsBuildLocator could not register defaults.", options.LogFile); - return LogAndExit(ReturnCode.MSBUILD_UNINITIALIZED, options.LogFile, ex.ToString()); + Log(ex, options.LogFile); } QsLanguageServer server; @@ -140,6 +148,7 @@ private static int Run(Options options) Log("Listening...", options.LogFile); try { + server.CheckDotNetSdkVersion(); server.WaitForShutdown(); } catch (Exception ex) diff --git a/src/QsCompiler/LanguageServer/Utils.cs b/src/QsCompiler/LanguageServer/Utils.cs index fe43424be8..105c4f447a 100644 --- a/src/QsCompiler/LanguageServer/Utils.cs +++ b/src/QsCompiler/LanguageServer/Utils.cs @@ -4,6 +4,9 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Threading.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Microsoft.Quantum.QsCompiler; @@ -34,6 +37,21 @@ internal static void ShowInWindow(this QsLanguageServer server, string text, Mes _ = server.NotifyClientAsync(Methods.WindowShowMessageName, message); } + /// + /// Shows a dialog window with options (actions) to the user, and returns the selected option (action). + /// + internal static async Task ShowDialogInWindowAsync(this QsLanguageServer server, string text, MessageType severity, MessageActionItem[] actionItems) + { + var message = + new ShowMessageRequestParams() + { + Message = text, + MessageType = severity, + Actions = actionItems + }; + return await server.InvokeAsync(Methods.WindowShowMessageRequestName, message); + } + /// /// Logs the given text in the editor. /// @@ -124,4 +142,26 @@ public override void Initialize(IEventSource eventSource) } } } + + internal static class DotNetSdkHelper + { + private static readonly Regex DotNet31Regex = new Regex(@"^3\.1\.\d+", RegexOptions.Multiline | RegexOptions.Compiled); + + public static bool? IsDotNet31Installed() + { + var process = Process.Start(new ProcessStartInfo + { + FileName = "dotnet", + Arguments = "--list-sdks", + RedirectStandardOutput = true, + }); + if (process?.WaitForExit(3000) != true || process.ExitCode != 0) + { + return null; + } + + var sdks = process.StandardOutput.ReadToEnd(); + return DotNet31Regex.IsMatch(sdks); + } + } } diff --git a/src/QsCompiler/LanguageServer/global.language-server.json b/src/QsCompiler/LanguageServer/global.language-server.json new file mode 100644 index 0000000000..0b2cf8aa3c --- /dev/null +++ b/src/QsCompiler/LanguageServer/global.language-server.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "3.1.100", + "rollForward": "latestFeature" + } +}