From d75c8f57ab26ac059cfdef9818e67d2db1f37963 Mon Sep 17 00:00:00 2001 From: Nick Guerrera Date: Wed, 24 Aug 2022 13:46:47 -0500 Subject: [PATCH] Use configured cadl-server path in VS when using a solution --- .../solution-settings_2022-08-24-18-47.json | 10 ++ .../VS2019/Microsoft.Cadl.VS2019.csproj | 12 +- .../VS2022/Microsoft.Cadl.VS2022.csproj | 6 +- packages/cadl-vs/src/Exceptions.cs | 5 + packages/cadl-vs/src/VSExtension.cs | 136 +++++++++++++----- 5 files changed, 125 insertions(+), 44 deletions(-) create mode 100644 common/changes/cadl-vs/solution-settings_2022-08-24-18-47.json diff --git a/common/changes/cadl-vs/solution-settings_2022-08-24-18-47.json b/common/changes/cadl-vs/solution-settings_2022-08-24-18-47.json new file mode 100644 index 00000000000..fabdba529e0 --- /dev/null +++ b/common/changes/cadl-vs/solution-settings_2022-08-24-18-47.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "cadl-vs", + "comment": "Fix issue with configured cadl-server location not being found when opening a solution.", + "type": "patch" + } + ], + "packageName": "cadl-vs" +} \ No newline at end of file diff --git a/packages/cadl-vs/VS2019/Microsoft.Cadl.VS2019.csproj b/packages/cadl-vs/VS2019/Microsoft.Cadl.VS2019.csproj index d2b4a771440..04304a6779d 100644 --- a/packages/cadl-vs/VS2019/Microsoft.Cadl.VS2019.csproj +++ b/packages/cadl-vs/VS2019/Microsoft.Cadl.VS2019.csproj @@ -7,10 +7,14 @@ 17.0 - - - - + + + + + + + + diff --git a/packages/cadl-vs/VS2022/Microsoft.Cadl.VS2022.csproj b/packages/cadl-vs/VS2022/Microsoft.Cadl.VS2022.csproj index 6d14bd4047c..1725c8e917e 100644 --- a/packages/cadl-vs/VS2022/Microsoft.Cadl.VS2022.csproj +++ b/packages/cadl-vs/VS2022/Microsoft.Cadl.VS2022.csproj @@ -7,10 +7,14 @@ 18.0 + - + + + + diff --git a/packages/cadl-vs/src/Exceptions.cs b/packages/cadl-vs/src/Exceptions.cs index 50416d1befe..e8491f75d2e 100644 --- a/packages/cadl-vs/src/Exceptions.cs +++ b/packages/cadl-vs/src/Exceptions.cs @@ -21,6 +21,11 @@ public CadlUserErrorException(string message) { } + + public CadlUserErrorException(string message, Exception innerException) + : base(message, innerException) + { + } } diff --git a/packages/cadl-vs/src/VSExtension.cs b/packages/cadl-vs/src/VSExtension.cs index c3b4cd7f687..cfed753ccb4 100644 --- a/packages/cadl-vs/src/VSExtension.cs +++ b/packages/cadl-vs/src/VSExtension.cs @@ -1,22 +1,26 @@ +using EnvDTE; +using Microsoft.VisualStudio.LanguageServer.Client; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using Microsoft.VisualStudio.Utilities; +using Microsoft.VisualStudio.Workspace; +using Microsoft.VisualStudio.Workspace.Settings; +using Microsoft.VisualStudio.Workspace.VSIntegration.Contracts; +using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.Composition; using System.Diagnostics; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.VisualStudio.Workspace; -using Microsoft.VisualStudio.Workspace.Settings; -using Microsoft.VisualStudio.Workspace.VSIntegration.Contracts; -using Microsoft.VisualStudio.LanguageServer.Client; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Threading; -using Microsoft.VisualStudio.Utilities; +using Debugger = System.Diagnostics.Debugger; +using Process = System.Diagnostics.Process; using Task = System.Threading.Tasks.Task; -using System.Linq; -using System.ComponentModel; namespace Microsoft.Cadl.VisualStudio { @@ -51,23 +55,24 @@ public sealed class LanguageClient : ILanguageClient public event AsyncEventHandler? StartAsync; public event AsyncEventHandler? StopAsync { add { } remove { } } // unused - private readonly IVsFolderWorkspaceService workspaceService; + private readonly IVsFolderWorkspaceService _workspaceService; + private readonly SVsServiceProvider _serviceProvider; + private string? _workspaceFolder; + private string? _configuredCadlServerPath; [ImportingConstructor] - public LanguageClient([Import] IVsFolderWorkspaceService workspaceService) + public LanguageClient([Import] IVsFolderWorkspaceService workspaceService, [Import] SVsServiceProvider serviceProvider) { - this.workspaceService = workspaceService; + _workspaceService = workspaceService; + _serviceProvider = serviceProvider; } public async Task ActivateAsync(CancellationToken token) { await Task.Yield(); - - var workspace = workspaceService.CurrentWorkspace; - var settingsManager = workspace?.GetSettingsManager(); - var settings = settingsManager?.GetAggregatedSettings(SettingsTypes.Generic); + await ReadSettingsAsync(); var options = Environment.GetEnvironmentVariable("CADL_SERVER_NODE_OPTIONS"); - var (serverCommand, serverArgs, env) = resolveCadlServer(settings); + var (serverCommand, serverArgs, env) = resolveCadlServer(); var info = new ProcessStartInfo { // Use cadl-server on PATH in production @@ -79,7 +84,7 @@ public LanguageClient([Import] IVsFolderWorkspaceService workspaceService) UseShellExecute = false, CreateNoWindow = true, Environment = { new("NODE_OPTIONS", options) }, - WorkingDirectory = settings?.ScopePath, + WorkingDirectory = _workspaceFolder, }; foreach (var entry in env) @@ -110,7 +115,7 @@ public LanguageClient([Import] IVsFolderWorkspaceService workspaceService) { throw new CadlServerNotFoundException(info.FileName); } - throw e; + throw; } } @@ -140,17 +145,19 @@ public Task OnServerInitializeFailedAsync(Exception e) #endif #if VS2022 - public Task OnServerInitializeFailedAsync(ILanguageClientInitializationInfo initializationState) { - var exception = initializationState.InitializationException; - var message = exception is CadlUserErrorException - ? exception.Message - : $"File issue at https://github.com/microsoft/cadl\r\n\r\n{exception}"; - Debug.Assert(exception is CadlUserErrorException, "Unexpected error initializing cadl-server:\r\n\r\n" + exception); - return Task.FromResult( - new InitializationFailureContext { - FailureMessage = "Failed to activate Cadl language server!\r\n" + message - }); - } + public Task OnServerInitializeFailedAsync(ILanguageClientInitializationInfo initializationState) + { + var exception = initializationState.InitializationException; + var message = exception is CadlUserErrorException + ? exception.Message + : $"File issue at https://github.com/microsoft/cadl\r\n\r\n{exception}"; + Debug.Assert(exception is CadlUserErrorException, "Unexpected error initializing cadl-server:\r\n\r\n" + exception); + return Task.FromResult( + new InitializationFailureContext + { + FailureMessage = "Failed to activate Cadl language server!\r\n" + message + }); + } #endif public Task OnServerInitializedAsync() @@ -189,7 +196,7 @@ private static string GetDevelopmentCadlServerPath() } #endif - private (string, string[], IDictionary) resolveCadlServer(IWorkspaceSettings? settings) + private (string, string[], IDictionary) resolveCadlServer() { var env = new Dictionary(); var args = new string[] { "--stdio" }; @@ -197,23 +204,28 @@ private static string GetDevelopmentCadlServerPath() // Use local build of cadl-server in development (lauched from F5 in VS) if (InDevelopmentMode()) { - var options = Environment.GetEnvironmentVariable("CADL_SERVER_NODE_OPTIONS"); - var module = GetDevelopmentCadlServerPath(); - return ("node.exe", new string[] { module, options }.Concat(args).ToArray(), env); + var options = Environment.GetEnvironmentVariable("CADL_SERVER_NODE_OPTIONS"); + var module = GetDevelopmentCadlServerPath(); + return ("node.exe", new string[] { module, options }.Concat(args).ToArray(), env); } #endif - var serverPath = settings?.Property("cadl.cadl-server.path"); - if (serverPath == null) + var serverPath = _configuredCadlServerPath; + if (serverPath == null || serverPath.Length == 0) { return ("cadl-server.cmd", args, env); } var variables = new Dictionary(); - variables.Add("workspaceFolder", workspaceService.CurrentWorkspace.Location); - var variableResolver = new VariableResolver(variables); + if (_workspaceFolder != null) + { + variables.Add("workspaceFolder", _workspaceFolder); + } + var variableResolver = new VariableResolver(variables); serverPath = variableResolver.ResolveVariables(serverPath); + serverPath = Path.GetFullPath(serverPath); + if (!serverPath.EndsWith(".js")) { if (File.Exists(serverPath)) @@ -223,13 +235,59 @@ private static string GetDevelopmentCadlServerPath() } else { - serverPath = Path.Combine(serverPath, "cmd/cadl-server.js"); + serverPath = Path.Combine(serverPath, "cmd", "cadl-server.js"); } } + // We need to check this as the later check when process is started would + // only trigger if node.exe is not found, not if the .js file passed to it + // is not found. + if (!File.Exists(serverPath)) + { + throw new CadlServerNotFoundException(serverPath); + } + env["CADL_SKIP_COMPILER_RESOLVE"] = "1"; return ("node.exe", new string[] { serverPath }.Concat(args).ToArray(), env); + } + private async Task ReadSettingsAsync() + { + var workspace = _workspaceService?.CurrentWorkspace; + if (workspace != null) + { + var settings = workspace.GetSettingsManager()?.GetAggregatedSettings(SettingsTypes.Generic); + _configuredCadlServerPath = settings?.Property("cadl.cadl-server.path"); + _workspaceFolder = workspace.Location; + } + else + { + // When a solution is open, there is no workspace settings manager, + // so we read the settings file ourselves. + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var dte = (DTE)_serviceProvider.GetService(typeof(DTE)); + if (dte != null && dte.Solution != null) + { + _workspaceFolder = Path.GetDirectoryName(dte.Solution.FullName); + await Task.Run(() => + { + var settingsFile = Path.Combine(_workspaceFolder, ".vs", "VSWorkspaceSettings.json"); + if (File.Exists(settingsFile)) + { + try + { + var content = File.ReadAllText(settingsFile); + var settings = JsonConvert.DeserializeObject>(content); + settings?.TryGetValue("cadl.cadl-server.path", out _configuredCadlServerPath); + } + catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is JsonException) + { + throw new CadlUserErrorException($"Unable to read {settingsFile}: {ex.Message}", ex); + } + } + }); + } + } } } }