/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/DotnetCliHelper.cs
165 строк
7 KB
David Barbet
Prevent diagnostic port inheritance by child processes (#84620)
25 июл 2026, 02:14
Не верифицирован
25 июл 2026, 02:14
70f2b43
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Composition; using System.Diagnostics; using System.Text; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.Extensions.Logging; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer; [ExportCSharpVisualBasicLspServiceFactory(typeof(DotnetCliHelper)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DotnetCliHelperFactory() : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) => new DotnetCliHelper(lspServices.GetRequiredService<ILoggerFactory>()); } internal sealed class DotnetCliHelper : ILspService { internal const string DotnetRootEnvVar = "DOTNET_ROOT"; private readonly ILogger _logger; private readonly Lazy<string> _dotnetExecutablePath; public DotnetCliHelper(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(".NET CLI Helper"); _dotnetExecutablePath = new Lazy<string>(() => GetDotNetPathOrDefault()); } /// <summary> /// The folder the dotnet executable is in could contain multiple SDK paths. /// In order to figure out which one is the right one, we need to run dotnet --info /// from the project directory (in order to respect any global.json that might be present) /// which will output the correct SDK path. /// </summary> private async Task<string> GetDotnetSdkFolderFromDotnetExecutableAsync(string projectOutputDirectory, CancellationToken cancellationToken) { using var process = Run(["--info"], workingDirectory: projectOutputDirectory, shouldLocalizeOutput: false); string? dotnetSdkFolderPath = null; process.OutputDataReceived += (_, e) => { if (!string.IsNullOrEmpty(e.Data)) { var line = e.Data.Trim(); if (line.StartsWith("Base Path", StringComparison.OrdinalIgnoreCase)) { dotnetSdkFolderPath = e.Data.Split(':', count: 2)[1].Trim(); } } }; var errorOutput = new StringBuilder(); var stdOutput = new StringBuilder(); process.ErrorDataReceived += (_, e) => errorOutput.AppendLine(e.Data); process.OutputDataReceived += (_, e) => stdOutput.AppendLine(e.Data); process.BeginOutputReadLine(); process.BeginErrorReadLine(); await process.WaitForExitAsync(cancellationToken); _logger.LogDebug(stdOutput.ToString()); if (process.ExitCode != 0 || dotnetSdkFolderPath == null) { _logger.LogError(errorOutput.ToString()); throw new InvalidOperationException("Failed to get dotnet SDK folder from dotnet --info"); } return dotnetSdkFolderPath; } public Process Run(string[] arguments, string? workingDirectory, bool shouldLocalizeOutput, bool keepStandardInputOpen = false) { _logger.LogDebug($"Running dotnet CLI command at {_dotnetExecutablePath.Value} in directory {workingDirectory} with arguments '{string.Join(' ', arguments)}'"); var startInfo = new ProcessStartInfo(_dotnetExecutablePath.Value) { CreateNoWindow = true, UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, }; startInfo.ArgumentList.AddRange(arguments); if (workingDirectory != null) { startInfo.WorkingDirectory = workingDirectory; } // If we're relying on the user's configured CLI to run this we need to remove our custom DOTNET_ROOT that we set // Wto start the server; otherwise we won't find the users installed sdks. startInfo.Environment.Remove(DotnetRootEnvVar); if (!shouldLocalizeOutput) { // The caller requested that we not localize the output of the command (typically be cause it needs to be parsed) // Set the appropriate environment variable for the process so that we don't get localized output. startInfo.Environment["DOTNET_CLI_UI_LANGUAGE"] = "en-US"; } // Since we depend on MSBuild APIs, the following environment variables get set to the version of dotnet that runs this server. // However want to use the user specified dotnet version to run the tests, so we need to unset these. startInfo.Environment.Remove("MSBUILD_EXE_PATH"); startInfo.Environment.Remove("MSBuildExtensionsPath"); startInfo.RemoveInheritedDotNetDiagnosticPorts(); var process = Process.Start(startInfo); Contract.ThrowIfNull(process, $"Unable to start dotnet CLI at {_dotnetExecutablePath.Value} with arguments {arguments} in directory {workingDirectory}"); if (!keepStandardInputOpen) process.StandardInput.Close(); return process; } public async Task<string> GetVsTestConsolePathAsync(string projectOutputDirectory, CancellationToken cancellationToken) { var dotnetSdkFolder = await GetDotnetSdkFolderFromDotnetExecutableAsync(projectOutputDirectory, cancellationToken); var vstestConsole = Path.Combine(dotnetSdkFolder, "vstest.console.dll"); Contract.ThrowIfFalse(File.Exists(vstestConsole), $"VSTestConsole was not found at {vstestConsole}"); _logger.LogDebug($"Using vstest console at {vstestConsole}"); return vstestConsole; } /// <summary> /// Finds the dotnet executable path from the PATH environment variable. /// Based on https://github.com/dotnet/msbuild/blob/main/src/Utilities/ToolTask.cs#L1259 /// We also do not include DOTNET_ROOT here, see https://github.com/dotnet/runtime/issues/88754 /// </summary> internal string GetDotNetPathOrDefault() { var (fileName, sep) = PlatformInformation.IsWindows ? ("dotnet.exe", ';') : ("dotnet", ':'); var path = Environment.GetEnvironmentVariable("PATH") ?? ""; foreach (var item in path.Split(sep, StringSplitOptions.RemoveEmptyEntries)) { try { var filePath = Path.Combine(item, fileName); if (File.Exists(filePath)) { _logger.LogInformation("Using dotnet executable configured on the PATH"); return filePath; } } catch { // If we can't read a directory for any reason just skip it } } _logger.LogInformation("Could not find dotnet executable from PATH"); return fileName; } }