diff --git a/CHANGELOG.md b/CHANGELOG.md index 63a855e3a3..3cf2a07d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ All changes to the project will be documented in this file. * Update the package that Arch Linux users need to install (PR: [#1921](https://github.com/OmniSharp/omnisharp-roslyn/pull/1921)) * Updated the docs to mention .NET 4.7.2 targeting pack (PR: [#1922](https://github.com/OmniSharp/omnisharp-roslyn/pull/1922)) * Support for configurations remapping in solution files ([#1828](https://github.com/OmniSharp/omnisharp-roslyn/issues/1828), PR: [#1835](https://github.com/OmniSharp/omnisharp-roslyn/pull/1835)) +* Only run dotnet --info once for the working directory (PR: [#1925](https://github.com/OmniSharp/omnisharp-roslyn/pull/1925)) * Update build tool versions for NET 5 RC1 (PR: [#1926](https://github.com/OmniSharp/omnisharp-roslyn/pull/1926)) * Update Roslyn to 3.8.0-3.20451.2 (PR: [#1927](https://github.com/OmniSharp/omnisharp-roslyn/pull/1927)) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs index cbfcdd879d..6c52b5a9da 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs @@ -113,7 +113,7 @@ public static (ProjectFileInfo, ImmutableArray, ProjectLoaded return (null, ImmutableArray.Empty, null); } - var (projectInstance, project, diagnostics) = loader.BuildProject(filePath); + var (projectInstance, project, diagnostics) = loader.BuildProject(filePath, projectIdInfo?.SolutionConfiguration); if (projectInstance == null) { return (null, diagnostics, null); @@ -137,7 +137,7 @@ public static (ProjectFileInfo, ImmutableArray, ProjectLoaded public (ProjectFileInfo, ImmutableArray, ProjectLoadedEventArgs) Reload(ProjectLoader loader) { - var (projectInstance, project, diagnostics) = loader.BuildProject(FilePath); + var (projectInstance, project, diagnostics) = loader.BuildProject(FilePath, ProjectIdInfo?.SolutionConfiguration); if (projectInstance == null) { return (null, diagnostics, null); diff --git a/src/OmniSharp.MSBuild/ProjectIdInfo.cs b/src/OmniSharp.MSBuild/ProjectIdInfo.cs index b1a46a1b26..975fe7c9db 100644 --- a/src/OmniSharp.MSBuild/ProjectIdInfo.cs +++ b/src/OmniSharp.MSBuild/ProjectIdInfo.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.CodeAnalysis; namespace OmniSharp.MSBuild @@ -13,5 +14,12 @@ public ProjectIdInfo(ProjectId id, bool isDefinedInSolution) public ProjectId Id { get; set; } public bool IsDefinedInSolution { get; set; } + + /// + /// Project configurations as defined in solution. + /// Keys are solution build configuration in '$(Configuration)|$(Platform)' format, + /// values are according project configurations. Null if there is no solution. + /// + public IReadOnlyDictionary SolutionConfiguration { get; set; } } } diff --git a/src/OmniSharp.MSBuild/ProjectLoader.cs b/src/OmniSharp.MSBuild/ProjectLoader.cs index 9755c63a4e..92cc4af90a 100644 --- a/src/OmniSharp.MSBuild/ProjectLoader.cs +++ b/src/OmniSharp.MSBuild/ProjectLoader.cs @@ -67,11 +67,12 @@ private static Dictionary CreateGlobalProperties( return globalProperties; } - public (MSB.Execution.ProjectInstance projectInstance, MSB.Evaluation.Project project, ImmutableArray diagnostics) BuildProject(string filePath) + public (MSB.Execution.ProjectInstance projectInstance, MSB.Evaluation.Project project, ImmutableArray diagnostics) BuildProject( + string filePath, IReadOnlyDictionary configurationsInSolution) { using (_sdksPathResolver.SetSdksPathEnvironmentVariable(filePath)) { - var evaluatedProject = EvaluateProjectFileCore(filePath); + var evaluatedProject = EvaluateProjectFileCore(filePath, configurationsInSolution); SetTargetFrameworkIfNeeded(evaluatedProject); @@ -115,10 +116,37 @@ public MSB.Evaluation.Project EvaluateProjectFile(string filePath) } } - private MSB.Evaluation.Project EvaluateProjectFileCore(string filePath) + private MSB.Evaluation.Project EvaluateProjectFileCore(string filePath, IReadOnlyDictionary projectConfigurationsInSolution = null) { + var localProperties = new Dictionary(_globalProperties); + if (projectConfigurationsInSolution != null + && localProperties.TryGetValue(PropertyNames.Configuration, out string solutionConfiguration)) + { + if (!localProperties.TryGetValue(PropertyNames.Platform, out string solutionPlatform)) + { + solutionPlatform = "Any CPU"; + } + + var solutionSelector = $"{solutionConfiguration}|{solutionPlatform}.ActiveCfg"; + _logger.LogDebug($"Found configuration `{solutionSelector}` in solution for '{filePath}'."); + + if (projectConfigurationsInSolution.TryGetValue(solutionSelector, out string projectSelector)) + { + var splitted = projectSelector.Split('|'); + if (splitted.Length == 2) + { + var projectConfiguration = splitted[0]; + localProperties[PropertyNames.Configuration] = projectConfiguration; + // NOTE: Solution often defines configuration as `Any CPU` whereas project relies on `AnyCPU` + var projectPlatform = splitted[1].Replace("Any CPU", "AnyCPU"); + localProperties[PropertyNames.Platform] = projectPlatform; + _logger.LogDebug($"Using configuration from solution: `{projectConfiguration}|{projectPlatform}`"); + } + } + } + // Evaluate the MSBuild project - var projectCollection = new MSB.Evaluation.ProjectCollection(_globalProperties); + var projectCollection = new MSB.Evaluation.ProjectCollection(localProperties); var toolsVersion = _options.ToolsVersion; if (string.IsNullOrEmpty(toolsVersion) || Version.TryParse(toolsVersion, out _)) diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index 0e52d59253..cbc2cf9aa9 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -171,6 +171,29 @@ private DotNetInfo GetDotNetInfo() var processedProjects = new HashSet(StringComparer.OrdinalIgnoreCase); var result = new List<(string, ProjectIdInfo)>(); + var solutionConfigurations = new Dictionary>(); + foreach (var globalSection in solutionFile.GlobalSections) + { + // Try parse project configurations if they are remapped in solution file + if (globalSection.Name == "ProjectConfigurationPlatforms") + { + _logger.LogDebug($"Parsing ProjectConfigurationPlatforms of '{solutionFilePath}'."); + foreach (var entry in globalSection.Properties) + { + var guid = Guid.Parse(entry.Name.Substring(0, 38)); + var projId = ProjectId.CreateFromSerialized(guid); + var solutionConfig = entry.Name.Substring(39); + + if (!solutionConfigurations.TryGetValue(projId, out var dict)) + { + dict = new Dictionary(); + solutionConfigurations.Add(projId, dict); + } + dict.Add(solutionConfig, entry.Value); + } + } + } + foreach (var project in solutionFile.Projects) { if (project.IsNotSupported) @@ -192,6 +215,10 @@ private DotNetInfo GetDotNetInfo() if (string.Equals(Path.GetExtension(projectFilePath), ".csproj", StringComparison.OrdinalIgnoreCase)) { var projectIdInfo = new ProjectIdInfo(ProjectId.CreateFromSerialized(new Guid(project.ProjectGuid)), true); + if (solutionConfigurations.TryGetValue(projectIdInfo.Id, out var configurations)) + { + projectIdInfo.SolutionConfiguration = configurations; + } result.Add((projectFilePath, projectIdInfo)); } diff --git a/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/App/App.csproj b/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/App/App.csproj new file mode 100644 index 0000000000..ae6e22bc3e --- /dev/null +++ b/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/App/App.csproj @@ -0,0 +1,21 @@ + + + + + + + + Debug1;Release1 + + + + Exe + netcoreapp2.1 + + + + Exe + netcoreapp2.1 + + + diff --git a/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/App/Program.cs b/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/App/Program.cs new file mode 100644 index 0000000000..be4278310c --- /dev/null +++ b/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/App/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace App +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/Lib/Class1.cs b/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/Lib/Class1.cs new file mode 100644 index 0000000000..44e7c59c4d --- /dev/null +++ b/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/Lib/Class1.cs @@ -0,0 +1,8 @@ +using System; + +namespace Lib +{ + public class Class1 + { + } +} diff --git a/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/Lib/Lib.csproj b/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/Lib/Lib.csproj new file mode 100644 index 0000000000..913be0853e --- /dev/null +++ b/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/Lib/Lib.csproj @@ -0,0 +1,15 @@ + + + + Debug2;Release2 + + + + netstandard1.3 + + + + netstandard1.3 + + + diff --git a/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/TwoProjectsWithSolutionAndCustomConfigurations.sln b/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/TwoProjectsWithSolutionAndCustomConfigurations.sln new file mode 100644 index 0000000000..7d7a37b495 --- /dev/null +++ b/test-assets/test-projects/TwoProjectsWithSolutionAndCustomConfigurations/TwoProjectsWithSolutionAndCustomConfigurations.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30204.135 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "App\App.csproj", "{632DFE45-B56E-4158-8F27-45E2BA0BAFCF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib", "Lib\Lib.csproj", "{CE41561B-5D13-4688-8686-EEFF744BE8B5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + DebugSln|Any CPU = DebugSln|Any CPU + ReleaseSln|Any CPU = ReleaseSln|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {632DFE45-B56E-4158-8F27-45E2BA0BAFCF}.DebugSln|Any CPU.ActiveCfg = Debug1|Any CPU + {632DFE45-B56E-4158-8F27-45E2BA0BAFCF}.DebugSln|Any CPU.Build.0 = Debug1|Any CPU + {632DFE45-B56E-4158-8F27-45E2BA0BAFCF}.ReleaseSln|Any CPU.ActiveCfg = Release1|Any CPU + {632DFE45-B56E-4158-8F27-45E2BA0BAFCF}.ReleaseSln|Any CPU.Build.0 = Release1|Any CPU + {CE41561B-5D13-4688-8686-EEFF744BE8B5}.DebugSln|Any CPU.ActiveCfg = Debug2|Any CPU + {CE41561B-5D13-4688-8686-EEFF744BE8B5}.DebugSln|Any CPU.Build.0 = Debug2|Any CPU + {CE41561B-5D13-4688-8686-EEFF744BE8B5}.ReleaseSln|Any CPU.ActiveCfg = Release2|Any CPU + {CE41561B-5D13-4688-8686-EEFF744BE8B5}.ReleaseSln|Any CPU.Build.0 = Release2|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D5E71A6E-0C92-4CB1-8260-00DE6C30724F} + EndGlobalSection +EndGlobal diff --git a/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs b/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs index f750728511..950eef2046 100644 --- a/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs +++ b/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs @@ -124,6 +124,31 @@ public async Task TwoProjectsWithSolution() } } + [Fact] + public async Task TwoProjectsWithSolutionAndCustomConfigurations() + { + var configData = new Dictionary { [$"MsBuild:{nameof(Options.MSBuildOptions.Configuration)}"] = "ReleaseSln" }; + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("TwoProjectsWithSolutionAndCustomConfigurations")) + using (var host = CreateMSBuildTestHost(testProject.Directory, configurationData: configData.ToConfiguration())) + { + var workspaceInfo = await host.RequestMSBuildWorkspaceInfoAsync(); + + Assert.Equal("TwoProjectsWithSolutionAndCustomConfigurations.sln", Path.GetFileName(workspaceInfo.SolutionPath)); + Assert.NotNull(workspaceInfo.Projects); + Assert.Equal(2, workspaceInfo.Projects.Count); + + var firstProject = workspaceInfo.Projects[0]; + Assert.Equal("App.csproj", Path.GetFileName(firstProject.Path)); + Assert.Equal(".NETCoreApp,Version=v2.1", firstProject.TargetFramework); + Assert.Equal("netcoreapp2.1", firstProject.TargetFrameworks[0].ShortName); + + var secondProject = workspaceInfo.Projects[1]; + Assert.Equal("Lib.csproj", Path.GetFileName(secondProject.Path)); + Assert.Equal(".NETStandard,Version=v1.3", secondProject.TargetFramework); + Assert.Equal("netstandard1.3", secondProject.TargetFrameworks[0].ShortName); + } + } + [Fact] public async Task TwoProjectWithGeneratedFile() {