diff --git a/Roslyn.sln b/Roslyn.sln index 34db92123add8..476c2e69788d7 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -545,6 +545,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.VisualStudio.Service EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.ExternalAccess.CompilerDeveloperSDK", "src\Tools\ExternalAccess\CompilerDeveloperSDK\Microsoft.CodeAnalysis.ExternalAccess.CompilerDeveloperSDK.csproj", "{A833B11C-5072-4A1F-A32B-2700433B0D3D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost", "src\Workspaces\Core\MSBuild.BuildHost\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj", "{B1481D94-682E-46EC-ADBE-A16EB46FEEE9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1343,6 +1345,10 @@ Global {A833B11C-5072-4A1F-A32B-2700433B0D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {A833B11C-5072-4A1F-A32B-2700433B0D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {A833B11C-5072-4A1F-A32B-2700433B0D3D}.Release|Any CPU.Build.0 = Release|Any CPU + {B1481D94-682E-46EC-ADBE-A16EB46FEEE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1481D94-682E-46EC-ADBE-A16EB46FEEE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1481D94-682E-46EC-ADBE-A16EB46FEEE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1481D94-682E-46EC-ADBE-A16EB46FEEE9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1591,6 +1597,7 @@ Global {172F3A04-644B-492C-9632-B07B52A5C0C4} = {55A62CFA-1155-46F1-ADF3-BEEE51B58AB5} {09E88382-0D7B-4A15-B1AF-0B89A5B67227} = {8DBA5174-B0AA-4561-82B1-A46607697753} {A833B11C-5072-4A1F-A32B-2700433B0D3D} = {8977A560-45C2-4EC2-A849-97335B382C74} + {B1481D94-682E-46EC-ADBE-A16EB46FEEE9} = {55A62CFA-1155-46F1-ADF3-BEEE51B58AB5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} diff --git a/SpellingExclusions.dic b/SpellingExclusions.dic index a2cd10e8f97fb..1e27739a43869 100644 --- a/SpellingExclusions.dic +++ b/SpellingExclusions.dic @@ -1,2 +1,3 @@ awaitable Refactorings +Infos diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/BuildHostProcessManager.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/BuildHostProcessManager.cs new file mode 100644 index 0000000000000..b5c365b5fa168 --- /dev/null +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/BuildHostProcessManager.cs @@ -0,0 +1,269 @@ +// 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.Diagnostics; +using System.Runtime.InteropServices; +using System.Xml; +using System.Xml.Linq; +using Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; +using Microsoft.Extensions.Logging; +using Roslyn.Utilities; +using StreamJsonRpc; + +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; + +internal sealed class BuildHostProcessManager : IAsyncDisposable +{ + private readonly ILoggerFactory? _loggerFactory; + private readonly ILogger? _logger; + private readonly string? _binaryLogPath; + + private readonly SemaphoreSlim _gate = new(initialCount: 1); + private readonly Dictionary _processes = new(); + + public BuildHostProcessManager(ILoggerFactory? loggerFactory = null, string? binaryLogPath = null) + { + _loggerFactory = loggerFactory; + _logger = loggerFactory?.CreateLogger(); + _binaryLogPath = binaryLogPath; + } + + public async Task GetBuildHostAsync(string projectFilePath, CancellationToken cancellationToken) + { + var neededBuildHostKind = GetKindForProject(projectFilePath); + + _logger?.LogTrace($"Choosing a build host of type {neededBuildHostKind} for {projectFilePath}"); + + using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + if (!_processes.TryGetValue(neededBuildHostKind, out var buildHostProcess)) + { + var process = neededBuildHostKind switch + { + BuildHostProcessKind.NetCore => LaunchDotNetCoreBuildHost(), + BuildHostProcessKind.NetFramework => LaunchDotNetFrameworkBuildHost(), + _ => throw ExceptionUtilities.UnexpectedValue(neededBuildHostKind) + }; + + buildHostProcess = new BuildHostProcess(process, _loggerFactory); + buildHostProcess.Disconnected += BuildHostProcess_Disconnected; + _processes.Add(neededBuildHostKind, buildHostProcess); + } + + return buildHostProcess.BuildHost; + } + } + + private void BuildHostProcess_Disconnected(object? sender, EventArgs e) + { + Contract.ThrowIfNull(sender, $"{nameof(BuildHostProcess)}.{nameof(BuildHostProcess.Disconnected)} was raised with a null sender."); + + Task.Run(async () => + { + BuildHostProcess? processToDispose = null; + + using (await _gate.DisposableWaitAsync().ConfigureAwait(false)) + { + // Remove it from our map; it's possible it might have already been removed if we had more than one way we observed a disconnect. + var existingProcess = _processes.SingleOrNull(p => p.Value == sender); + if (existingProcess.HasValue) + { + processToDispose = existingProcess.Value.Value; + _processes.Remove(existingProcess.Value.Key); + } + } + + // Dispose outside of the lock (even though we don't expect much to happen at this point) + if (processToDispose != null) + { + processToDispose.LoggerForProcessMessages?.LogTrace("Process exited."); + await processToDispose.DisposeAsync(); + } + }); + } + + public async ValueTask DisposeAsync() + { + foreach (var process in _processes.Values) + await process.DisposeAsync(); + } + + private Process LaunchDotNetCoreBuildHost() + { + var processStartInfo = new ProcessStartInfo() + { + FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet", + }; + + // We need to roll forward to the latest runtime, since the project may be using an SDK (or an SDK required runtime) newer than we ourselves built with. + // We set the environment variable since --roll-forward LatestMajor doesn't roll forward to prerelease SDKs otherwise. + processStartInfo.Environment["DOTNET_ROLL_FORWARD_TO_PRERELEASE"] = "1"; + processStartInfo.ArgumentList.Add("--roll-forward"); + processStartInfo.ArgumentList.Add("LatestMajor"); + + processStartInfo.ArgumentList.Add(typeof(IBuildHost).Assembly.Location); + + AppendBuildHostCommandLineArgumentsConfigureProcess(processStartInfo); + + var process = Process.Start(processStartInfo); + Contract.ThrowIfNull(process, "Process.Start failed to launch a process."); + return process; + } + + private Process LaunchDotNetFrameworkBuildHost() + { + var netFrameworkBuildHost = Path.Combine(Path.GetDirectoryName(typeof(BuildHostProcessManager).Assembly.Location)!, "BuildHost-net472", "Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe"); + Contract.ThrowIfFalse(File.Exists(netFrameworkBuildHost), $"Unable to locate the .NET Framework build host at {netFrameworkBuildHost}"); + + var processStartInfo = new ProcessStartInfo() + { + FileName = netFrameworkBuildHost, + }; + + AppendBuildHostCommandLineArgumentsConfigureProcess(processStartInfo); + + var process = Process.Start(processStartInfo); + Contract.ThrowIfNull(process, "Process.Start failed to launch a process."); + return process; + } + + private void AppendBuildHostCommandLineArgumentsConfigureProcess(ProcessStartInfo processStartInfo) + { + if (_binaryLogPath is not null) + { + processStartInfo.ArgumentList.Add("--binlog"); + processStartInfo.ArgumentList.Add(_binaryLogPath); + } + + // MSBUILD_EXE_PATH is read by MSBuild to find related tasks and targets. We don't want this to be inherited by our build process, or otherwise + // it might try to load targets that aren't appropriate for the build host. + processStartInfo.Environment.Remove("MSBUILD_EXE_PATH"); + + processStartInfo.CreateNoWindow = true; + processStartInfo.UseShellExecute = false; + processStartInfo.RedirectStandardInput = true; + processStartInfo.RedirectStandardOutput = true; + processStartInfo.RedirectStandardError = true; + } + + private static readonly XmlReaderSettings s_xmlSettings = new() + { + DtdProcessing = DtdProcessing.Prohibit, + }; + + private static BuildHostProcessKind GetKindForProject(string projectFilePath) + { + // At the moment we don't have mono support here, so if we're not on Windows, we'll always force to .NET Core. + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return BuildHostProcessKind.NetCore; + + // This implements the algorithm as stated in https://github.com/dotnet/project-system/blob/9a761848e0f330a45e349685a266fea00ac3d9c5/docs/opening-with-new-project-system.md; + // we'll load the XML of the project directly, and inspect for certain elements. + XDocument document; + + // Read the XML, prohibiting DTD processing due the the usual concerns there. + using (var fileStream = new FileStream(projectFilePath, FileMode.Open, FileAccess.Read)) + using (var xmlReader = XmlReader.Create(fileStream, s_xmlSettings)) + document = XDocument.Load(xmlReader); + + // If we don't have a root, doesn't really matter which. This project is just malformed. + if (document.Root == null) + return BuildHostProcessKind.NetCore; + + // Look for SDK attribute on the root + if (document.Root.Attribute("Sdk") != null) + return BuildHostProcessKind.NetCore; + + // Look for + if (document.Root.Elements("Import").Attributes("Sdk").Any()) + return BuildHostProcessKind.NetCore; + + // Look for + if (document.Root.Elements("Sdk").Any()) + return BuildHostProcessKind.NetCore; + + // Looking for PropertyGroups that contain TargetFramework or TargetFrameworks nodes + var propertyGroups = document.Descendants("PropertyGroup"); + if (propertyGroups.Elements("TargetFramework").Any() || propertyGroups.Elements("TargetFrameworks").Any()) + return BuildHostProcessKind.NetCore; + + // Nothing that indicates it's an SDK-style project, so use our .NET framework host + return BuildHostProcessKind.NetFramework; + } + + private enum BuildHostProcessKind + { + NetCore, + NetFramework + } + + private sealed class BuildHostProcess : IAsyncDisposable + { + private readonly Process _process; + private readonly JsonRpc _jsonRpc; + + private int _disposed = 0; + + public BuildHostProcess(Process process, ILoggerFactory? loggerFactory) + { + LoggerForProcessMessages = loggerFactory?.CreateLogger($"BuildHost PID {process.Id}"); + + _process = process; + + _process.EnableRaisingEvents = true; + _process.Exited += Process_Exited; + + _process.ErrorDataReceived += Process_ErrorDataReceived; + + var messageHandler = new HeaderDelimitedMessageHandler(sendingStream: _process.StandardInput.BaseStream, receivingStream: _process.StandardOutput.BaseStream, new JsonMessageFormatter()); + + _jsonRpc = new JsonRpc(messageHandler); + _jsonRpc.StartListening(); + BuildHost = _jsonRpc.Attach(); + + // Call this last so our type is fully constructed before we start firing events + _process.BeginErrorReadLine(); + } + + private void Process_Exited(object? sender, EventArgs e) + { + Disconnected?.Invoke(this, EventArgs.Empty); + } + + private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e) + { + if (e.Data is not null) + LoggerForProcessMessages?.LogTrace($"Message from Process: {e.Data}"); + } + + public IBuildHost BuildHost { get; } + public ILogger? LoggerForProcessMessages { get; } + + public event EventHandler? Disconnected; + + public async ValueTask DisposeAsync() + { + // Ensure only one thing disposes; while we disconnect the process will go away, which will call us to do this again + if (Interlocked.CompareExchange(ref _disposed, value: 1, comparand: 0) != 0) + return; + + // We will call Shutdown in a try/catch; if the process has gone bad it's possible the connection is no longer functioning. + try + { + await BuildHost.ShutdownAsync(); + _jsonRpc.Dispose(); + + LoggerForProcessMessages?.LogTrace("Process gracefully shut down."); + } + catch (Exception e) + { + LoggerForProcessMessages?.LogError(e, "Exception while shutting down the BuildHost process."); + + // OK, process may have gone bad. + _process.Kill(); + } + } + } +} + diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs index 986eddde90724..0baab5879c9c7 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs @@ -6,18 +6,17 @@ using System.Collections.Immutable; using System.Composition; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Build.Locator; -using Microsoft.Build.Logging; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.ProjectTelemetry; using Microsoft.CodeAnalysis.MSBuild; -using Microsoft.CodeAnalysis.MSBuild.Build; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.ProjectSystem; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Composition; @@ -29,8 +28,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; [Export(typeof(LanguageServerProjectSystem)), Shared] internal sealed class LanguageServerProjectSystem { - private readonly ProjectFileLoaderRegistry _projectFileLoaderRegistry; - /// /// A single gate for code that is adding work to and modifying . /// This is just we don't have code simultaneously trying to load and unload solutions at once. @@ -53,6 +50,7 @@ internal sealed class LanguageServerProjectSystem private readonly LanguageServerWorkspaceFactory _workspaceFactory; private readonly IFileChangeWatcher _fileChangeWatcher; private readonly IGlobalOptionService _globalOptionService; + private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; private readonly ProjectLoadTelemetryReporter _projectLoadTelemetryReporter; @@ -77,12 +75,10 @@ public LanguageServerProjectSystem( _workspaceFactory = workspaceFactory; _fileChangeWatcher = fileChangeWatcher; _globalOptionService = globalOptionService; + _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(nameof(LanguageServerProjectSystem)); _projectLoadTelemetryReporter = projectLoadTelemetry; - // TODO: remove the DiagnosticReporter that's coupled to the Workspace here - _projectFileLoaderRegistry = new ProjectFileLoaderRegistry(workspaceFactory.Workspace.Services.SolutionServices, new DiagnosticReporter(workspaceFactory.Workspace)); - _projectsToLoadAndReload = new AsyncBatchingWorkQueue( TimeSpan.FromMilliseconds(100), LoadOrReloadProjectsAsync, @@ -93,7 +89,6 @@ public LanguageServerProjectSystem( public async Task OpenSolutionAsync(string solutionFilePath) { - if (await TryEnsureMSBuildLoadedAsync(Path.GetDirectoryName(solutionFilePath)!)) await OpenSolutionCoreAsync(solutionFilePath); } @@ -128,13 +123,6 @@ public async Task OpenProjectsAsync(ImmutableArray projectFilePaths) if (!projectFilePaths.Any()) return; - if (await TryEnsureMSBuildLoadedAsync(Path.GetDirectoryName(projectFilePaths.First())!)) - await OpenProjectsCoreAsync(projectFilePaths); - } - - [MethodImpl(MethodImplOptions.NoInlining)] // Don't inline; the caller needs to ensure MSBuild is loaded before we can use MSBuild types here - private async Task OpenProjectsCoreAsync(ImmutableArray projectFilePaths) - { using (await _gate.DisposableWaitAsync()) { _projectsToLoadAndReload.AddWork(projectFilePaths.Select(p => new ProjectToLoad(p, ProjectGuid: null))); @@ -162,7 +150,7 @@ private async Task TryEnsureMSBuildLoadedAsync(string workingDirectory) if (msbuildInstance != null) { MSBuildLocator.RegisterInstance(msbuildInstance); - _logger.LogInformation($"Loaded MSBuild at {msbuildInstance.MSBuildPath}"); + _logger.LogInformation($"Loaded MSBuild in-process from {msbuildInstance.MSBuildPath}"); _msbuildLoaded = true; return true; @@ -183,9 +171,15 @@ private async ValueTask LoadOrReloadProjectsAsync(ImmutableSegmentedList.Empty, msbuildLogger: CreateMSBuildLogger()); - projectBuildManager.StartBatchBuild(); + var binaryLogPath = GetMSBuildBinaryLogPath(); + var runBuildInProcess = _globalOptionService.GetOption(LanguageServerProjectSystemOptionsStorage.LoadInProcess); + + if (runBuildInProcess) + _logger.LogInformation("In-process project loading is enabled."); + + await using var buildHostProcessManager = !runBuildInProcess ? new BuildHostProcessManager(_loggerFactory, binaryLogPath) : null; + var inProcessBuildHost = runBuildInProcess ? new BuildHost(_loggerFactory, binaryLogPath) : null; var displayedToast = 0; @@ -197,7 +191,7 @@ private async ValueTask LoadOrReloadProjectsAsync(ImmutableSegmentedList { - var errorKind = await LoadOrReloadProjectAsync(projectToLoad, projectBuildManager, cancellationToken); + var errorKind = await LoadOrReloadProjectAsync(projectToLoad, buildHostProcessManager, inProcessBuildHost, cancellationToken); if (errorKind is LSP.MessageType.Error) { // We should display a toast when the value of displayedToast is 0. This will also update the value to 1 meaning we won't send any more toasts. @@ -215,13 +209,14 @@ private async ValueTask LoadOrReloadProjectsAsync(ImmutableSegmentedList LoadOrReloadProjectAsync(ProjectToLoad projectToLoad, ProjectBuildManager projectBuildManager, CancellationToken cancellationToken) + private async Task LoadOrReloadProjectAsync(ProjectToLoad projectToLoad, BuildHostProcessManager? buildHostProcessManager, BuildHost? inProcessBuildHost, CancellationToken cancellationToken) { try { var projectPath = projectToLoad.Path; - if (_projectFileLoaderRegistry.TryGetLoaderFromProjectPath(projectPath, out var loader)) + + // If we have a process manager, then get an OOP process; otherwise we're still using in-proc builds so just fetch one in-process + var buildHost = inProcessBuildHost ?? await buildHostProcessManager!.GetBuildHostAsync(projectPath, cancellationToken); + + if (await buildHost.IsProjectFileSupportedAsync(projectPath, cancellationToken)) { - var loadedFile = await loader.LoadProjectFileAsync(projectPath, projectBuildManager, cancellationToken); + var loadedFile = await buildHost.LoadProjectFileAsync(projectPath, cancellationToken); var loadedProjectInfos = await loadedFile.GetProjectFileInfosAsync(cancellationToken); + // The out-of-proc build host supports more languages than we may actually have Workspace binaries for, so ensure we can actually process that + // language. + var projectLanguage = loadedProjectInfos.FirstOrDefault()?.Language; + if (projectLanguage != null && _workspaceFactory.Workspace.Services.GetLanguageService(projectLanguage) == null) + { + return null; + } + var existingProjects = _loadedProjects.GetOrAdd(projectPath, static _ => new List()); Dictionary MetadataReferences, OutputKind OutputKind)> projectFileInfos = new(); @@ -254,7 +261,6 @@ private async ValueTask LoadOrReloadProjectsAsync(ImmutableSegmentedList logItem.Kind is WorkspaceDiagnosticKind.Failure) ? LSP.MessageType.Error : LSP.MessageType.Warning; + return diagnosticLogItems.Any(logItem => logItem.Kind is WorkspaceDiagnosticKind.Failure) ? LSP.MessageType.Error : LSP.MessageType.Warning; } else { diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLogMessageLogger.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLogMessageLogger.cs index a461af8c5ecf1..6605b60f3332f 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLogMessageLogger.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLogMessageLogger.cs @@ -60,7 +60,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except if (message != null && logLevel != LogLevel.None) { - message = $"[{_categoryName}]{message}"; + message = $"[{_categoryName}] {message}"; var _ = server.GetRequiredLspService().SendNotificationAsync(Methods.WindowLogMessageName, new LogMessageParams() { Message = message, diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj index b3a1a1115cd10..cb2e14585ff91 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj @@ -1,122 +1,152 @@  - - Exe - - false - net7.0 - enable - enable - - true - - true - - $(PackRuntimeIdentifier) - $(AssemblyName).$(PackRuntimeIdentifier) - - PackPublishContent;$(BeforePack) - - - NU5100 - - - false - $(ArtifactsDir)/LanguageServer/$(Configuration)/$(TargetFramework)/$(RuntimeIdentifier) - $(ArtifactsDir)/LanguageServer/$(Configuration)/$(TargetFramework)/neutral - - 7.0.0-preview.7.22362.8 - - - win-x64;win-x86;win-arm64;linux-x64;linux-arm64;alpine-x64;alpine-arm64;osx-x64;osx-arm64 - - true - - - - - - - - - - + + Exe + + false + net7.0 + enable + enable - - + true + + true + + $(PackRuntimeIdentifier) + $(AssemblyName).$(PackRuntimeIdentifier) + + PackPublishContent;$(BeforePack) + + + NU5100 + + + false + $(ArtifactsDir)/LanguageServer/$(Configuration)/$(TargetFramework)/$(RuntimeIdentifier) + $(ArtifactsDir)/LanguageServer/$(Configuration)/$(TargetFramework)/neutral + + 7.0.0-preview.7.22362.8 + + + win-x64;win-x86;win-arm64;linux-x64;linux-arm64;alpine-x64;alpine-arm64;osx-x64;osx-arm64 + + true + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + content\LanguageServer\$(PackRuntimeIdentifier) + false + None + + + + - - + <_NetFrameworkBuildHostProjectReference Include="..\..\..\Workspaces\Core\MSBuild.BuildHost\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj"> + net472 + + + + + + - + + + - - - - - - - - - - - - - true - content\LanguageServer\$(PackRuntimeIdentifier) - false - None - - - - - - + + diff --git a/src/Features/LanguageServer/Protocol/Features/Options/LanguageServerProjectSystemOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/LanguageServerProjectSystemOptionsStorage.cs index 1782271dced5d..95275ec147db0 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/LanguageServerProjectSystemOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/LanguageServerProjectSystemOptionsStorage.cs @@ -14,5 +14,11 @@ internal static class LanguageServerProjectSystemOptionsStorage /// A folder to log binlogs to when running design-time builds. /// public static readonly Option2 BinaryLogPath = new Option2("dotnet_binary_log_path", defaultValue: null, s_optionGroup); + + /// + /// Whether we are doing design-time builds in-process; this is only to offer a fallback if the OOP builds are broken, and should be removed once + /// we don't have folks using this. + /// + public static readonly Option2 LoadInProcess = new("dotnet_load_in_process", defaultValue: false, s_optionGroup); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs b/src/Features/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs index fbdd30b6b8c6d..ea13956a387b7 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs @@ -61,6 +61,7 @@ internal partial class DidChangeConfigurationNotificationHandler LspOptionsStorage.LspEnableReferencesCodeLens, LspOptionsStorage.LspEnableTestsCodeLens, // Project system - LanguageServerProjectSystemOptionsStorage.BinaryLogPath); + LanguageServerProjectSystemOptionsStorage.BinaryLogPath, + LanguageServerProjectSystemOptionsStorage.LoadInProcess); } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs b/src/Features/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs index a85dc33c8df89..d385486a0e9c5 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs @@ -144,7 +144,8 @@ public void VerifyLspClientOptionNames() "background_analysis.dotnet_compiler_diagnostics_scope", "code_lens.dotnet_enable_references_code_lens", "code_lens.dotnet_enable_tests_code_lens", - "projects.dotnet_binary_log_path" + "projects.dotnet_binary_log_path", + "projects.dotnet_load_in_process", }.OrderBy(name => name); Assert.Equal(expectedNames, actualNames); diff --git a/src/NuGet/VisualStudio/VS.ExternalAPIs.Roslyn.Package.csproj b/src/NuGet/VisualStudio/VS.ExternalAPIs.Roslyn.Package.csproj index 9d86097111d35..703118c475d5a 100644 --- a/src/NuGet/VisualStudio/VS.ExternalAPIs.Roslyn.Package.csproj +++ b/src/NuGet/VisualStudio/VS.ExternalAPIs.Roslyn.Package.csproj @@ -45,6 +45,7 @@ + @@ -52,6 +53,7 @@ + @@ -90,6 +92,7 @@ <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.VisualBasic.Workspaces\$(Configuration)\netstandard2.0\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll" TargetDir="" /> <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.VisualBasic\$(Configuration)\netstandard2.0\Microsoft.CodeAnalysis.VisualBasic.dll" TargetDir="" /> <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.Workspaces.MSBuild\$(Configuration)\net472\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll" TargetDir="" /> + <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost\$(Configuration)\net472\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe" TargetDir="" /> <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.Workspaces\$(Configuration)\netstandard2.0\Microsoft.CodeAnalysis.Workspaces.dll" TargetDir="" /> <_File Include="$(ArtifactsBinDir)Microsoft.CodeAnalysis.Workspaces.Desktop\$(Configuration)\net472\Microsoft.CodeAnalysis.Workspaces.Desktop.dll" TargetDir="" /> <_File Include="$(ArtifactsBinDir)Microsoft.VisualStudio.LanguageServices.CSharp\$(Configuration)\net472\Microsoft.VisualStudio.LanguageServices.CSharp.dll" TargetDir="" /> diff --git a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs index 57008a8bd6d4c..e58323f3942b5 100644 --- a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs +++ b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs @@ -336,7 +336,7 @@ void verifyFolder(string folderRelativeName) foreach (var part in GetPartsInFolder(package, folderRelativeName)) { var name = part.GetName(); - if (Path.GetExtension(name) != ".dll") + if (Path.GetExtension(name) is not (".dll" or ".exe")) { continue; } diff --git a/src/VisualStudio/Core/Test.Next/Options/VisualStudioOptionStorageTests.cs b/src/VisualStudio/Core/Test.Next/Options/VisualStudioOptionStorageTests.cs index 37046b4eb9713..bcb3ed3e7193a 100644 --- a/src/VisualStudio/Core/Test.Next/Options/VisualStudioOptionStorageTests.cs +++ b/src/VisualStudio/Core/Test.Next/Options/VisualStudioOptionStorageTests.cs @@ -226,6 +226,7 @@ public void OptionHasStorageIfNecessary(string configName) "dotnet_style_operator_placement_when_wrapping", // Doesn't have VS UI. TODO: https://github.com/dotnet/roslyn/issues/66062 "dotnet_style_prefer_foreach_explicit_cast_in_source", // For a small customer segment, doesn't warrant VS UI. "dotnet_binary_log_path", // VSCode only option for the VS Code project system; does not apply to VS + "dotnet_load_in_process", // VSCode only option for the VS Code project system; does not apply to VS "dotnet_lsp_using_devkit", // VSCode internal only option. Does not need any UI. "dotnet_enable_references_code_lens", // VSCode only option. Does not apply to VS. "dotnet_enable_tests_code_lens", // VSCode only option. Does not apply to VS. diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index 22e9dafc12cc9..e8dfcbd934932 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -178,6 +178,17 @@ TargetFramework=net472 BindingRedirect + + Workspaces.MSBuild + BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup + true + TargetFramework=net472 + BindingRedirect + + false + CSharpWorkspace BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Build/ProjectBuildManager.cs b/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/Build/ProjectBuildManager.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs diff --git a/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs b/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs new file mode 100644 index 0000000000000..292ac46cd3fdb --- /dev/null +++ b/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs @@ -0,0 +1,128 @@ +// 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.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Build.Locator; +using Microsoft.Build.Logging; +using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.CodeAnalysis.MSBuild.Build; +using Microsoft.Extensions.Logging; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; + +internal sealed class BuildHost : IBuildHost +{ + private readonly ILogger _logger; + private readonly string? _binaryLogPath; + + private readonly object _gate = new object(); + private ProjectBuildManager? _buildManager; + + public BuildHost(ILoggerFactory loggerFactory, string? binaryLogPath) + { + _logger = loggerFactory.CreateLogger(); + _binaryLogPath = binaryLogPath; + } + + private void EnsureMSBuildLoaded(string projectFilePath) + { + lock (_gate) + { + // If we've already created our MSBuild types, then there's nothing further to do. + if (MSBuildLocator.IsRegistered) + { + return; + } + + VisualStudioInstance instance; + +#if NETFRAMEWORK + + // In this case, we're just going to pick the highest VS install on the machine, in case the projects are using some newer + // MSBuild features. Since we don't have something like a global.json we can't really know what the minimum version is. + + // TODO: we should also check that the managed tools are actually installed + instance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(vs => vs.Version).First(); + +#else + + // Locate the right SDK for this particular project; MSBuildLocator ensures in this case the first one is the preferred one. + // TODO: we should pick the appropriate instance back in the main process and just use the one chosen here. + var options = new VisualStudioInstanceQueryOptions { DiscoveryTypes = DiscoveryType.DotNetSdk, WorkingDirectory = Path.GetDirectoryName(projectFilePath) }; + instance = MSBuildLocator.QueryVisualStudioInstances(options).First(); + +#endif + + MSBuildLocator.RegisterInstance(instance); + _logger.LogInformation($"Registered MSBuild instance at {instance.MSBuildPath}"); + } + } + + [MemberNotNull(nameof(_buildManager))] + [MethodImpl(MethodImplOptions.NoInlining)] // Do not inline this, since this creates MSBuild types which are being loaded by the caller + private void CreateBuildManager() + { + lock (_gate) + { + if (_buildManager != null) + return; + + BinaryLogger? logger = null; + + if (_binaryLogPath != null) + { + logger = new BinaryLogger { Parameters = _binaryLogPath }; + _logger.LogInformation($"Logging builds to {_binaryLogPath}"); + } + + _buildManager = new ProjectBuildManager(ImmutableDictionary.Empty, logger); + _buildManager.StartBatchBuild(); + } + } + + public Task IsProjectFileSupportedAsync(string projectFilePath, CancellationToken cancellationToken) + { + EnsureMSBuildLoaded(projectFilePath); + CreateBuildManager(); + + return Task.FromResult(TryGetLoaderForPath(projectFilePath) is not null); + } + + public async Task LoadProjectFileAsync(string projectFilePath, CancellationToken cancellationToken) + { + EnsureMSBuildLoaded(projectFilePath); + CreateBuildManager(); + + var projectLoader = TryGetLoaderForPath(projectFilePath); + Contract.ThrowIfNull(projectLoader, $"We don't support this project path; we should have called {nameof(IsProjectFileSupportedAsync)} first."); + _logger.LogInformation($"Loading {projectFilePath}"); + return new RemoteProjectFile(await projectLoader.LoadProjectFileAsync(projectFilePath, _buildManager, cancellationToken).ConfigureAwait(false)); + } + + private static IProjectFileLoader? TryGetLoaderForPath(string projectFilePath) + { + var extension = Path.GetExtension(projectFilePath); + + return extension switch + { + ".csproj" => new CSharp.CSharpProjectFileLoader(), + ".vbproj" => new VisualBasic.VisualBasicProjectFileLoader(), + _ => null + }; + } + + public Task ShutdownAsync() + { + _buildManager?.EndBatchBuild(); + + return Task.CompletedTask; + } +} diff --git a/src/Workspaces/Core/MSBuild.BuildHost/IBuildHost.cs b/src/Workspaces/Core/MSBuild.BuildHost/IBuildHost.cs new file mode 100644 index 0000000000000..b2219b2cb180f --- /dev/null +++ b/src/Workspaces/Core/MSBuild.BuildHost/IBuildHost.cs @@ -0,0 +1,23 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; + +/// +/// The RPC interface implemented by this host; called via JSON-RPC. +/// +internal interface IBuildHost +{ + /// + /// Returns whether this project's language is supported. + /// + Task IsProjectFileSupportedAsync(string projectFilePath, CancellationToken cancellationToken); + + Task LoadProjectFileAsync(string projectFilePath, CancellationToken cancellationToken); + + Task ShutdownAsync(); +} diff --git a/src/Workspaces/Core/MSBuild.BuildHost/IRemoteProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/IRemoteProjectFile.cs new file mode 100644 index 0000000000000..c58d075582056 --- /dev/null +++ b/src/Workspaces/Core/MSBuild.BuildHost/IRemoteProjectFile.cs @@ -0,0 +1,23 @@ +// 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; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.CodeAnalysis.MSBuild.Logging; +using StreamJsonRpc; + +namespace Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; + +/// +/// A trimmed down interface of that is usable for RPC to the build host process and meets all the requirements of being an interface. +/// +[RpcMarshalable] +internal interface IRemoteProjectFile : IDisposable +{ + Task> GetProjectFileInfosAsync(CancellationToken cancellationToken); + Task> GetDiagnosticLogItemsAsync(CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Core/MSBuild/MSBuild/CSharp/CSharpCommandLineArgumentReader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpCommandLineArgumentReader.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/CSharp/CSharpCommandLineArgumentReader.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpCommandLineArgumentReader.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/CSharp/CSharpProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFile.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/CSharp/CSharpProjectFile.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFile.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/CSharp/CSharpProjectFileLoader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFileLoader.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/CSharp/CSharpProjectFileLoader.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFileLoader.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/CSharp/CSharpProjectFileLoaderFactory.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFileLoaderFactory.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/CSharp/CSharpProjectFileLoaderFactory.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFileLoaderFactory.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Constants/ItemNames.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Constants/ItemNames.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/Constants/ItemNames.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Constants/ItemNames.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Constants/MetadataNames.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Constants/MetadataNames.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/Constants/MetadataNames.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Constants/MetadataNames.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Constants/PropertyNames.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Constants/PropertyNames.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/Constants/PropertyNames.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Constants/PropertyNames.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Constants/PropertyValues.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Constants/PropertyValues.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/Constants/PropertyValues.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Constants/PropertyValues.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Constants/TargetNames.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Constants/TargetNames.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/Constants/TargetNames.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Constants/TargetNames.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Logging/DiagnosticLog.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLog.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/Logging/DiagnosticLog.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLog.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Logging/DiagnosticLogItem.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLogItem.cs similarity index 81% rename from src/Workspaces/Core/MSBuild/MSBuild/Logging/DiagnosticLogItem.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLogItem.cs index a31f5a2829c70..2caa6b96d875b 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/Logging/DiagnosticLogItem.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLogItem.cs @@ -3,13 +3,21 @@ // See the LICENSE file in the project root for more information. using System; +using System.Runtime.Serialization; +using Newtonsoft.Json; namespace Microsoft.CodeAnalysis.MSBuild.Logging { + [DataContract] internal class DiagnosticLogItem { + [DataMember(Order = 0)] public WorkspaceDiagnosticKind Kind { get; } + + [DataMember(Order = 1)] public string Message { get; } + + [DataMember(Order = 2)] public string ProjectFilePath { get; } public DiagnosticLogItem(WorkspaceDiagnosticKind kind, string message, string projectFilePath) @@ -19,11 +27,6 @@ public DiagnosticLogItem(WorkspaceDiagnosticKind kind, string message, string pr ProjectFilePath = projectFilePath ?? throw new ArgumentNullException(nameof(message)); } - public DiagnosticLogItem(WorkspaceDiagnosticKind kind, Exception exception, string projectFilePath) - : this(kind, exception.Message, projectFilePath) - { - } - public override string ToString() => Message; } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Logging/MSBuildDiagnosticLogItem.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogItem.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/Logging/MSBuildDiagnosticLogItem.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogItem.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/Logging/MSBuildDiagnosticLogger.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogger.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/Logging/MSBuildDiagnosticLogger.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogger.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/CommandLineArgumentReader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/CommandLineArgumentReader.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/CommandLineArgumentReader.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/CommandLineArgumentReader.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/Conversions.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Conversions.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/Conversions.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Conversions.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/DocumentFileInfo.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/DocumentFileInfo.cs similarity index 87% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/DocumentFileInfo.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/DocumentFileInfo.cs index 8d614f81e7684..b411cdd5a873a 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/DocumentFileInfo.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/DocumentFileInfo.cs @@ -3,17 +3,20 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Runtime.Serialization; namespace Microsoft.CodeAnalysis.MSBuild { /// /// Represents a source file that is part of a project file. /// + [DataContract] internal sealed class DocumentFileInfo(string filePath, string logicalPath, bool isLinked, bool isGenerated, SourceCodeKind sourceCodeKind, ImmutableArray folders) { /// /// The absolute path to the document file on disk. /// + [DataMember(Order = 0)] public string FilePath { get; } = filePath; /// @@ -21,27 +24,32 @@ internal sealed class DocumentFileInfo(string filePath, string logicalPath, bool /// The document may not actually exist at this location, and is used /// to represent linked documents. This includes the file name. /// + [DataMember(Order = 1)] public string LogicalPath { get; } = logicalPath; /// /// True if the document has a logical path that differs from its /// absolute file path. /// + [DataMember(Order = 2)] public bool IsLinked { get; } = isLinked; /// /// True if the file was generated during build. /// + [DataMember(Order = 3)] public bool IsGenerated { get; } = isGenerated; /// /// The of this document. /// + [DataMember(Order = 4)] public SourceCodeKind SourceCodeKind { get; } = sourceCodeKind; /// /// Containing folders of the document relative to the containing project root path. /// + [DataMember(Order = 5)] public ImmutableArray Folders { get; } = folders; } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/Extensions.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/Extensions.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/IProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/IProjectFile.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/IProjectFile.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/IProjectFile.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/IProjectFileLoader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/IProjectFileLoader.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/IProjectFileLoader.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/IProjectFileLoader.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs similarity index 99% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFile.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs index 3f0478fc9eb5d..87fd393b8fc93 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFile.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs @@ -48,7 +48,7 @@ public async Task> GetProjectFileInfosAsync(Canc { if (_loadedProject is null) { - return ImmutableArray.Create(ProjectFileInfo.CreateEmpty(Language, _loadedProject?.FullPath, Log)); + return ImmutableArray.Create(ProjectFileInfo.CreateEmpty(Language, _loadedProject?.FullPath)); } var targetFrameworkValue = _loadedProject.GetPropertyValue(PropertyNames.TargetFramework); @@ -92,7 +92,7 @@ public async Task> GetProjectFileInfosAsync(Canc else { var projectFileInfo = await BuildProjectFileInfoAsync(cancellationToken).ConfigureAwait(false); - projectFileInfo ??= ProjectFileInfo.CreateEmpty(Language, _loadedProject?.FullPath, Log); + projectFileInfo ??= ProjectFileInfo.CreateEmpty(Language, _loadedProject?.FullPath); return ImmutableArray.Create(projectFileInfo); } } @@ -101,14 +101,14 @@ private async Task BuildProjectFileInfoAsync(CancellationToken { if (_loadedProject is null) { - return ProjectFileInfo.CreateEmpty(Language, _loadedProject?.FullPath, Log); + return ProjectFileInfo.CreateEmpty(Language, _loadedProject?.FullPath); } var project = await _buildManager.BuildProjectAsync(_loadedProject, Log, cancellationToken).ConfigureAwait(false); return project != null ? CreateProjectFileInfo(project, _loadedProject) - : ProjectFileInfo.CreateEmpty(Language, _loadedProject.FullPath, Log); + : ProjectFileInfo.CreateEmpty(Language, _loadedProject.FullPath); } private ProjectFileInfo CreateProjectFileInfo(MSB.Execution.ProjectInstance project, MSB.Evaluation.Project loadedProject) @@ -181,8 +181,7 @@ private ProjectFileInfo CreateProjectFileInfo(MSB.Execution.ProjectInstance proj project.GetProjectReferences().ToImmutableArray(), projectCapabilities, contentFileInfo, - isSdkStyle, - Log); + isSdkStyle); } private static ImmutableArray GetContentFiles(MSB.Execution.ProjectInstance project) diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileExtensionAttribute.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileExtensionAttribute.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileExtensionAttribute.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileExtensionAttribute.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileInfo.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileInfo.cs similarity index 91% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileInfo.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileInfo.cs index 1ff4cff51890a..a5d3805996acd 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileInfo.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileInfo.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Runtime.Serialization; using Microsoft.CodeAnalysis.MSBuild.Logging; using Roslyn.Utilities; @@ -14,35 +15,42 @@ namespace Microsoft.CodeAnalysis.MSBuild /// built with MSBuild. If the project is multi-targeting, this represents /// the information from a single target framework. /// + [DataContract] internal sealed class ProjectFileInfo { + [DataMember(Order = 0)] public bool IsEmpty { get; } /// /// The language of this project. /// + [DataMember(Order = 1)] public string Language { get; } /// /// The path to the project file for this project. /// + [DataMember(Order = 2)] public string? FilePath { get; } - /// - /// The path to the intermediate output file this project generates. - /// - public string? IntermediateOutputFilePath { get; } - /// /// The path to the output file this project generates. /// + [DataMember(Order = 3)] public string? OutputFilePath { get; } /// /// The path to the reference assembly output file this project generates. /// + [DataMember(Order = 4)] public string? OutputRefFilePath { get; } + /// + /// The path to the intermediate output file this project generates. + /// + [DataMember(Order = 5)] + public string? IntermediateOutputFilePath { get; } + /// /// The default namespace of the project ("" if not defined, which means global namespace), /// or null if it is unknown or not applicable. @@ -54,72 +62,77 @@ internal sealed class ProjectFileInfo /// In the future, we might consider officially exposing "default namespace" for VB project /// (e.g. through a "defaultnamespace" msbuild property) /// + [DataMember(Order = 6)] public string? DefaultNamespace { get; } /// /// The target framework of this project. /// This takes the form of the 'short name' form used by NuGet (e.g. net46, netcoreapp2.0, etc.) /// + [DataMember(Order = 7)] public string? TargetFramework { get; } /// /// The target framework identifier of this project. /// Used to determine if a project is targeting .net core. /// + [DataMember(Order = 8)] public string? TargetFrameworkIdentifier { get; } /// /// The command line args used to compile the project. /// + [DataMember(Order = 9)] public ImmutableArray CommandLineArgs { get; } /// /// The source documents. /// + [DataMember(Order = 10)] public ImmutableArray Documents { get; } /// /// The additional documents. /// + [DataMember(Order = 11)] public ImmutableArray AdditionalDocuments { get; } /// /// The analyzer config documents. /// + [DataMember(Order = 12)] public ImmutableArray AnalyzerConfigDocuments { get; } /// /// References to other projects. /// + [DataMember(Order = 13)] public ImmutableArray ProjectReferences { get; } /// /// The msbuild project capabilities. /// + [DataMember(Order = 14)] public ImmutableArray ProjectCapabilities { get; } /// /// The paths to content files included in the project. /// + [DataMember(Order = 15)] public ImmutableArray ContentFilePaths { get; } /// /// Whether or not we believe this project is an SDK style project. /// + [DataMember(Order = 16)] public bool IsSdkStyle { get; } - /// - /// The error message produced when a failure occurred attempting to get the info. - /// If a failure occurred some or all of the information may be inaccurate or incomplete. - /// - public DiagnosticLog Log { get; } - public override string ToString() => RoslynString.IsNullOrWhiteSpace(TargetFramework) ? FilePath ?? string.Empty : $"{FilePath} ({TargetFramework})"; - private ProjectFileInfo( + public ProjectFileInfo( bool isEmpty, string language, string? filePath, @@ -136,8 +149,7 @@ private ProjectFileInfo( ImmutableArray projectReferences, ImmutableArray projectCapabilities, ImmutableArray contentFilePaths, - bool isSdkStyle, - DiagnosticLog log) + bool isSdkStyle) { RoslynDebug.Assert(filePath != null); @@ -158,7 +170,6 @@ private ProjectFileInfo( this.ProjectCapabilities = projectCapabilities; this.ContentFilePaths = contentFilePaths; this.IsSdkStyle = isSdkStyle; - this.Log = log; } public static ProjectFileInfo Create( @@ -177,8 +188,7 @@ public static ProjectFileInfo Create( ImmutableArray projectReferences, ImmutableArray projectCapabilities, ImmutableArray contentFilePaths, - bool isSdkStyle, - DiagnosticLog log) + bool isSdkStyle) => new( isEmpty: false, language, @@ -196,10 +206,9 @@ public static ProjectFileInfo Create( projectReferences, projectCapabilities, contentFilePaths, - isSdkStyle, - log); + isSdkStyle); - public static ProjectFileInfo CreateEmpty(string language, string? filePath, DiagnosticLog log) + public static ProjectFileInfo CreateEmpty(string language, string? filePath) => new( isEmpty: true, language, @@ -217,7 +226,6 @@ public static ProjectFileInfo CreateEmpty(string language, string? filePath, Dia projectReferences: ImmutableArray.Empty, projectCapabilities: ImmutableArray.Empty, contentFilePaths: ImmutableArray.Empty, - isSdkStyle: false, - log); + isSdkStyle: false); } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileLoader.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileLoader.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileReference.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileReference.cs similarity index 90% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileReference.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileReference.cs index b3b75fe3bf606..6ed6024473d6a 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileReference.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileReference.cs @@ -4,23 +4,27 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Runtime.Serialization; namespace Microsoft.CodeAnalysis.MSBuild { /// /// Represents a reference to another project file. /// + [DataContract] internal sealed class ProjectFileReference { /// /// The path on disk to the other project file. /// This path may be relative to the referencing project's file or an absolute path. /// + [DataMember(Order = 0)] public string Path { get; } /// /// The aliases assigned to this reference, if any. /// + [DataMember(Order = 1)] public ImmutableArray Aliases { get; } public ProjectFileReference(string path, ImmutableArray aliases) diff --git a/src/Workspaces/Core/MSBuild/MSBuild/VisualBasic/VisualBasicCommandLineArgumentReader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicCommandLineArgumentReader.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/VisualBasic/VisualBasicCommandLineArgumentReader.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicCommandLineArgumentReader.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/VisualBasic/VisualBasicProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFile.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/VisualBasic/VisualBasicProjectFile.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFile.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs diff --git a/src/Workspaces/Core/MSBuild/MSBuild/VisualBasic/VisualBasicProjectFileLoaderFactory.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFileLoaderFactory.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/VisualBasic/VisualBasicProjectFileLoaderFactory.cs rename to src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFileLoaderFactory.cs diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj b/src/Workspaces/Core/MSBuild.BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj new file mode 100644 index 0000000000000..1da70bf07eb13 --- /dev/null +++ b/src/Workspaces/Core/MSBuild.BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj @@ -0,0 +1,50 @@ + + + + + Exe + Microsoft.CodeAnalysis + true + $(SourceBuildTargetFrameworks);net472 + + false + + win-x64;win-x86;win-arm64;linux-x64;linux-arm64;alpine-x64;alpine-arm64;osx-x64;osx-arm64 + + false + + + + InternalUtilities\GlobalAssemblyCache.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Program.cs b/src/Workspaces/Core/MSBuild.BuildHost/Program.cs new file mode 100644 index 0000000000000..78b259fbfd787 --- /dev/null +++ b/src/Workspaces/Core/MSBuild.BuildHost/Program.cs @@ -0,0 +1,53 @@ +// 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; +using System.CommandLine; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using StreamJsonRpc; + +namespace Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; + +internal static class Program +{ + internal static async Task Main(string[] args) + { + var binaryLogOption = new CliOption("--binlog") { Required = false }; + var command = new CliRootCommand { binaryLogOption }; + var parsedArguments = command.Parse(args); + var binaryLogPath = parsedArguments.GetValue(binaryLogOption); + + // Create a console logger that logs everything to standard error instead of standard out; by setting the threshold to Trace + // everything will go to standard error. + var loggerFactory = LoggerFactory.Create(builder => + builder.AddConsole(configure => + { + // DisableColors is deprecated in favor of us moving to simple console, but that loses the LogToStandardErrorThreshold + // which we also need +#pragma warning disable CS0618 + configure.DisableColors = true; +#pragma warning restore CS0618 + configure.LogToStandardErrorThreshold = LogLevel.Trace; + })); + + var logger = loggerFactory.CreateLogger(typeof(Program)); + + var messageHandler = new HeaderDelimitedMessageHandler(sendingStream: Console.OpenStandardOutput(), receivingStream: Console.OpenStandardInput(), new JsonMessageFormatter()); + + var jsonRpc = new JsonRpc(messageHandler) + { + ExceptionStrategy = ExceptionProcessing.CommonErrorData, + }; + + jsonRpc.AddLocalRpcTarget(new BuildHost(loggerFactory, binaryLogPath)); + jsonRpc.StartListening(); + + logger.LogInformation("RPC channel started."); + + await jsonRpc.Completion.ConfigureAwait(false); + + logger.LogInformation("RPC channel closed; process exiting."); + } +} diff --git a/src/Workspaces/Core/MSBuild.BuildHost/PublicAPI.Shipped.txt b/src/Workspaces/Core/MSBuild.BuildHost/PublicAPI.Shipped.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Workspaces/Core/MSBuild.BuildHost/PublicAPI.Unshipped.txt b/src/Workspaces/Core/MSBuild.BuildHost/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Workspaces/Core/MSBuild.BuildHost/RemoteProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/RemoteProjectFile.cs new file mode 100644 index 0000000000000..63586459646ff --- /dev/null +++ b/src/Workspaces/Core/MSBuild.BuildHost/RemoteProjectFile.cs @@ -0,0 +1,32 @@ +// 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.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.CodeAnalysis.MSBuild.Logging; + +namespace Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; + +internal class RemoteProjectFile : IRemoteProjectFile +{ + private readonly IProjectFile _projectFile; + + public RemoteProjectFile(IProjectFile projectFile) + { + _projectFile = projectFile; + } + + public void Dispose() + { + } + + public Task> GetProjectFileInfosAsync(CancellationToken cancellationToken) + => _projectFile.GetProjectFileInfosAsync(cancellationToken); + + public Task> GetDiagnosticLogItemsAsync(CancellationToken cancellationToken) + => Task.FromResult(_projectFile.Log.ToImmutableArray()); + +} diff --git a/src/Workspaces/Core/MSBuild/WorkspaceMSBuildResources.resx b/src/Workspaces/Core/MSBuild.BuildHost/WorkspaceMSBuildResources.resx similarity index 100% rename from src/Workspaces/Core/MSBuild/WorkspaceMSBuildResources.resx rename to src/Workspaces/Core/MSBuild.BuildHost/WorkspaceMSBuildResources.resx diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.cs.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.cs.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.cs.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.cs.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.de.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.de.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.de.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.de.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.es.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.es.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.es.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.es.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.fr.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.fr.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.fr.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.fr.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.it.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.it.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.it.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.it.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ja.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.ja.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ja.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.ja.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ko.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.ko.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ko.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.ko.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pl.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.pl.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pl.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.pl.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pt-BR.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.pt-BR.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.pt-BR.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.pt-BR.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ru.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.ru.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.ru.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.ru.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.tr.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.tr.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.tr.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.tr.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hans.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.zh-Hans.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hans.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.zh-Hans.xlf diff --git a/src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hant.xlf b/src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.zh-Hant.xlf similarity index 100% rename from src/Workspaces/Core/MSBuild/xlf/WorkspaceMSBuildResources.zh-Hant.xlf rename to src/Workspaces/Core/MSBuild.BuildHost/xlf/WorkspaceMSBuildResources.zh-Hant.xlf diff --git a/src/Workspaces/Core/MSBuild/Host/Mef/MSBuildMefHostServices.cs b/src/Workspaces/Core/MSBuild/Host/Mef/MSBuildMefHostServices.cs index 81c2683ccae6d..e1ca11b08659d 100644 --- a/src/Workspaces/Core/MSBuild/Host/Mef/MSBuildMefHostServices.cs +++ b/src/Workspaces/Core/MSBuild/Host/Mef/MSBuildMefHostServices.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Reflection; using System.Threading; +using Microsoft.CodeAnalysis.MSBuild.Build; namespace Microsoft.CodeAnalysis.Host.Mef { @@ -44,7 +45,8 @@ private static ImmutableArray CreateDefaultAssemblies() { var assemblyNames = new string[] { - typeof(MSBuildMefHostServices).Assembly.GetName().Name, + // All of our MEF types are actually in Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost + typeof(ProjectBuildManager).Assembly.GetName().Name, }; return MefHostServices.DefaultAssemblies.Concat( diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.AnalyzerReferencePathComparer.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.AnalyzerReferencePathComparer.cs index dd4b66985a96b..f0c7080f944e3 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.AnalyzerReferencePathComparer.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.AnalyzerReferencePathComparer.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.MSBuild diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs index 32b6db85b655d..edb978fc6ec27 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs @@ -197,7 +197,7 @@ private async Task> LoadProjectFileInfosAsync(st _diagnosticReporter.Report(projectFile.Log); return ImmutableArray.Create( - ProjectFileInfo.CreateEmpty(loader.Language, projectPath, projectFile.Log)); + ProjectFileInfo.CreateEmpty(loader.Language, projectPath)); } var projectFileInfos = await DoOperationAndReportProgressAsync( @@ -212,10 +212,8 @@ private async Task> LoadProjectFileInfosAsync(st foreach (var projectFileInfo in projectFileInfos) { // If any diagnostics were logged during build, we'll carry on and try to produce a meaningful project. - if (!projectFileInfo.Log.IsEmpty) - { - _diagnosticReporter.Report(projectFileInfo.Log); - } + // Note: any diagnostics would have been logged to the original project file's log. + _diagnosticReporter.Report(projectFile.Log); results.Add(projectFileInfo); } @@ -259,7 +257,7 @@ private async Task> LoadProjectInfosFromPathAsync( _projectIdToFileInfoMap.Add(projectId, projectFileInfo); } - // If this project resulted in more than a single project, a discrimator (e.g. TFM) should be + // If this project resulted in more than a single project, a discriminator (e.g. TFM) should be // added to the project name. var addDiscriminator = idsAndFileInfos.Count > 1; diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs index 6a1cbd3d44c9b..fbd2c01b66c7c 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs @@ -49,7 +49,7 @@ private class ResolvedReferencesBuilder private readonly ImmutableDictionary> _pathToIndicesMap; /// - /// A set of indeces into that are to be removed. + /// A set of indices into that are to be removed. /// private readonly HashSet _indicesToRemove; diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs index 52c2c65450278..dce2ebaa73655 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.MSBuild { /// - /// An API for loading msbuild project files. + /// An API for loading MSBuild project files. /// public partial class MSBuildProjectLoader { @@ -54,7 +54,7 @@ internal MSBuildProjectLoader( /// /// The workspace whose services this should use. /// An optional dictionary of additional MSBuild properties and values to use when loading projects. - /// These are the same properties that are passed to msbuild via the /property:<n>=<v> command line argument. + /// These are the same properties that are passed to MSBuild via the /property:<n>=<v> command line argument. public MSBuildProjectLoader(Workspace workspace, ImmutableDictionary? properties = null) : this(workspace.Services.SolutionServices, new DiagnosticReporter(workspace), projectFileLoaderRegistry: null, properties) { @@ -62,7 +62,7 @@ public MSBuildProjectLoader(Workspace workspace, ImmutableDictionary /// The MSBuild properties used when interpreting project files. - /// These are the same properties that are passed to msbuild via the /property:<n>=<v> command line argument. + /// These are the same properties that are passed to MSBuild via the /property:<n>=<v> command line argument. /// public ImmutableDictionary Properties { get; private set; } @@ -139,7 +139,7 @@ private DiagnosticReportingMode GetReportingModeForUnrecognizedProjects() /// The path to the solution file to be loaded. This may be an absolute path or a path relative to the /// current working directory. /// An optional that will receive updates as the solution is loaded. - /// An optional that will log msbuild results. + /// An optional that will log MSBuild results. /// An optional to allow cancellation of this operation. public async Task LoadSolutionInfoAsync( string solutionFilePath, @@ -158,9 +158,9 @@ public async Task LoadSolutionInfoAsync( return null!; } - var projectfilter = ImmutableHashSet.Empty; + var projectFilter = ImmutableHashSet.Empty; if (SolutionFilterReader.IsSolutionFilterFilename(absoluteSolutionPath) && - !SolutionFilterReader.TryRead(absoluteSolutionPath, _pathResolver, out absoluteSolutionPath, out projectfilter)) + !SolutionFilterReader.TryRead(absoluteSolutionPath, _pathResolver, out absoluteSolutionPath, out projectFilter)) { throw new Exception(string.Format(WorkspaceMSBuildResources.Failed_to_load_solution_filter_0, solutionFilePath)); } @@ -190,8 +190,8 @@ public async Task LoadSolutionInfoAsync( } // Load project if we have an empty project filter and the project path is present. - if (projectfilter.IsEmpty || - projectfilter.Contains(project.AbsolutePath)) + if (projectFilter.IsEmpty || + projectFilter.Contains(project.AbsolutePath)) { projectPaths.Add(project.RelativePath); } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoaderRegistry.cs b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFileLoaderRegistry.cs similarity index 100% rename from src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoaderRegistry.cs rename to src/Workspaces/Core/MSBuild/MSBuild/ProjectFileLoaderRegistry.cs diff --git a/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj b/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj index 7d0bdc9c66649..92b1b7f2e3acc 100644 --- a/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj +++ b/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj @@ -4,7 +4,6 @@ Library Microsoft.CodeAnalysis - true $(SourceBuildTargetFrameworks);net472 $(DefineConstants);WORKSPACE_MSBUILD @@ -16,11 +15,6 @@ - Microsoft.CodeAnalysis.VisualBasic.Workspaces - - - InternalUtilities\GlobalAssemblyCache.cs - - @@ -29,15 +23,13 @@ + - - - @@ -45,4 +37,17 @@ + + + $(TargetsForTfmSpecificBuildOutput);GetBuildHostBinaries + + + + + + diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 3a6bc580e4052..1d21e72e19f4a 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -78,6 +78,7 @@ +