From 44b24a0f2471cf04dae28c72f5361b8c99351d0f Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Sat, 19 Sep 2020 14:48:20 -0700 Subject: [PATCH 1/4] Support solution filters (*.slnf) --- src/OmniSharp.Host/Services/OmniSharpEnvironment.cs | 4 ++-- src/OmniSharp.MSBuild/ProjectSystem.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OmniSharp.Host/Services/OmniSharpEnvironment.cs b/src/OmniSharp.Host/Services/OmniSharpEnvironment.cs index d09a412411..991b067010 100644 --- a/src/OmniSharp.Host/Services/OmniSharpEnvironment.cs +++ b/src/OmniSharp.Host/Services/OmniSharpEnvironment.cs @@ -27,7 +27,7 @@ public OmniSharpEnvironment( { TargetDirectory = path; } - else if (File.Exists(path) && Path.GetExtension(path).Equals(".sln", StringComparison.OrdinalIgnoreCase)) + else if (File.Exists(path) && (Path.GetExtension(path).Equals(".sln", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(path).Equals(".slnf", StringComparison.OrdinalIgnoreCase))) { SolutionFilePath = path; TargetDirectory = Path.GetDirectoryName(path); @@ -35,7 +35,7 @@ public OmniSharpEnvironment( if (TargetDirectory == null) { - throw new ArgumentException("OmniSharp only supports being launched with a directory path or a path to a solution (.sln) file.", nameof(path)); + throw new ArgumentException("OmniSharp only supports being launched with a directory path or a path to a solution (.sln, .slnf) file.", nameof(path)); } HostProcessId = hostPid; diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index 01f6db98cd..38089b8c90 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -226,7 +226,7 @@ private static string FindSolutionFilePath(string rootPath, ILogger logger) // currently, Directory.GetFiles collects files that the file extension has 'sln' prefix. // this causes collecting unexpected files like 'x.slnx', or 'x.slnproj'. // see https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netframework-4.7.2 ('Note' description) - var solutionsFilePaths = Directory.GetFiles(rootPath, "*.sln").Where(x => Path.GetExtension(x).Equals(".sln", StringComparison.OrdinalIgnoreCase)).ToArray(); + var solutionsFilePaths = Directory.GetFiles(rootPath, "*.sln").Where(x => Path.GetExtension(x).Equals(".sln", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(x).Equals(".slnf", StringComparison.OrdinalIgnoreCase)).ToArray(); var result = SolutionSelector.Pick(solutionsFilePaths, rootPath); if (result.Message != null) From 8170216025ed08581b9b3c58e5144d9a69875dd2 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 21 Sep 2020 06:20:59 -0700 Subject: [PATCH 2/4] Add test for solution filters --- .../Project/Program.cs | 12 +++++++ .../Project/ProjectAndSolutionFilter.csproj | 8 +++++ .../ProjectAndSolutionFilter.slnf | 8 +++++ .../Solution/ProjectAndSolutionFilter.sln | 34 +++++++++++++++++++ .../WorkspaceInformationTests.cs | 28 +++++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 test-assets/test-projects/ProjectAndSolutionFilter/Project/Program.cs create mode 100644 test-assets/test-projects/ProjectAndSolutionFilter/Project/ProjectAndSolutionFilter.csproj create mode 100644 test-assets/test-projects/ProjectAndSolutionFilter/ProjectAndSolutionFilter.slnf create mode 100644 test-assets/test-projects/ProjectAndSolutionFilter/Solution/ProjectAndSolutionFilter.sln diff --git a/test-assets/test-projects/ProjectAndSolutionFilter/Project/Program.cs b/test-assets/test-projects/ProjectAndSolutionFilter/Project/Program.cs new file mode 100644 index 0000000000..dbae8113d5 --- /dev/null +++ b/test-assets/test-projects/ProjectAndSolutionFilter/Project/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace ProjectAndSolution +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/test-assets/test-projects/ProjectAndSolutionFilter/Project/ProjectAndSolutionFilter.csproj b/test-assets/test-projects/ProjectAndSolutionFilter/Project/ProjectAndSolutionFilter.csproj new file mode 100644 index 0000000000..23df6047ff --- /dev/null +++ b/test-assets/test-projects/ProjectAndSolutionFilter/Project/ProjectAndSolutionFilter.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp2.1 + + + diff --git a/test-assets/test-projects/ProjectAndSolutionFilter/ProjectAndSolutionFilter.slnf b/test-assets/test-projects/ProjectAndSolutionFilter/ProjectAndSolutionFilter.slnf new file mode 100644 index 0000000000..9897e17301 --- /dev/null +++ b/test-assets/test-projects/ProjectAndSolutionFilter/ProjectAndSolutionFilter.slnf @@ -0,0 +1,8 @@ +{ + "solution": { + "path": "Solution\\ProjectAndSolutionFilter.sln", + "projects": [ + "..\\Project\\ProjectAndSolutionFilter.csproj" + ] + } +} \ No newline at end of file diff --git a/test-assets/test-projects/ProjectAndSolutionFilter/Solution/ProjectAndSolutionFilter.sln b/test-assets/test-projects/ProjectAndSolutionFilter/Solution/ProjectAndSolutionFilter.sln new file mode 100644 index 0000000000..8c5339d9bc --- /dev/null +++ b/test-assets/test-projects/ProjectAndSolutionFilter/Solution/ProjectAndSolutionFilter.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectAndSolutionFilter", "..\Project\ProjectAndSolutionFilter.csproj", "{A4C2694D-AEB4-4CB1-8951-5290424EF883}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Debug|x64.ActiveCfg = Debug|x64 + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Debug|x64.Build.0 = Debug|x64 + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Debug|x86.ActiveCfg = Debug|x86 + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Debug|x86.Build.0 = Debug|x86 + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Release|Any CPU.Build.0 = Release|Any CPU + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Release|x64.ActiveCfg = Release|x64 + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Release|x64.Build.0 = Release|x64 + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Release|x86.ActiveCfg = Release|x86 + {A4C2694D-AEB4-4CB1-8951-5290424EF883}.Release|x86.Build.0 = Release|x86 + EndGlobalSection +EndGlobal diff --git a/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs b/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs index 950eef2046..b723f9b8b9 100644 --- a/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs +++ b/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs @@ -47,6 +47,34 @@ public async Task TestProjectAndSolution() } } + [Fact] + public async Task TestProjectAndSolutionFilter() + { + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectAndSolutionFilter")) + using (var host = CreateMSBuildTestHost(testProject.Directory)) + { + var workspaceInfo = await host.RequestMSBuildWorkspaceInfoAsync(); + + Assert.Equal("ProjectAndSolutionFilter.slnf", Path.GetFileName(workspaceInfo.SolutionPath)); + Assert.NotNull(workspaceInfo.Projects); + var project = Assert.Single(workspaceInfo.Projects); + + Assert.Equal("ProjectAndSolutionFilter", project.AssemblyName); + Assert.Equal("bin/Debug/netcoreapp2.1/", project.OutputPath.EnsureForwardSlashes()); + Assert.Equal("obj/Debug/netcoreapp2.1/", project.IntermediateOutputPath.EnsureForwardSlashes()); + var expectedTargetPath = $"{testProject.Directory}/Project/{project.OutputPath}ProjectAndSolutionFilter.dll".EnsureForwardSlashes(); + Assert.Equal(expectedTargetPath, project.TargetPath.EnsureForwardSlashes()); + Assert.Equal("Debug", project.Configuration); + Assert.Equal("AnyCPU", project.Platform); + Assert.True(project.IsExe); + Assert.False(project.IsUnityProject); + + Assert.Equal(".NETCoreApp,Version=v2.1", project.TargetFramework); + var targetFramework = Assert.Single(project.TargetFrameworks); + Assert.Equal("netcoreapp2.1", targetFramework.ShortName); + } + } + [Fact] public async Task ProjectAndSolutionWithProjectSection() { From 7ee7deaf049d0705b6ab32d12f76b70ab956b389 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 21 Sep 2020 15:06:20 -0700 Subject: [PATCH 3/4] Fix parsing of solution filters --- .../SolutionParsing/ProjectBlock.cs | 8 ++++ .../SolutionParsing/SolutionFile.cs | 43 ++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.MSBuild/SolutionParsing/ProjectBlock.cs b/src/OmniSharp.MSBuild/SolutionParsing/ProjectBlock.cs index 9cf83c4cfc..4f3ecab41e 100644 --- a/src/OmniSharp.MSBuild/SolutionParsing/ProjectBlock.cs +++ b/src/OmniSharp.MSBuild/SolutionParsing/ProjectBlock.cs @@ -98,5 +98,13 @@ public static ProjectBlock Parse(string headerLine, Scanner scanner) return new ProjectBlock(projectTypeGuid, projectName, relativePath, projectGuid, sections.ToImmutable()); } + + public ProjectBlock WithRelativePath(string relativePath) + { + if (relativePath == RelativePath) + return this; + + return new ProjectBlock(ProjectTypeGuid, ProjectName, relativePath, ProjectGuid, Sections); + } } } diff --git a/src/OmniSharp.MSBuild/SolutionParsing/SolutionFile.cs b/src/OmniSharp.MSBuild/SolutionParsing/SolutionFile.cs index 053414e7fa..b37749fc30 100644 --- a/src/OmniSharp.MSBuild/SolutionParsing/SolutionFile.cs +++ b/src/OmniSharp.MSBuild/SolutionParsing/SolutionFile.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Immutable; using System.IO; +using System.Linq; +using Newtonsoft.Json.Linq; namespace OmniSharp.MSBuild.SolutionParsing { @@ -68,10 +70,49 @@ public static SolutionFile Parse(string text) } } + public static SolutionFile ParseSolutionFilter(string basePath, string text) + { + var root = JObject.Parse(text); + var solutionPath = ((string)root["solution"]?["path"])?.Replace('\\', Path.DirectorySeparatorChar); + var includedProjects = (JArray)root["solution"]?["projects"] ?? new JArray(); + var includedProjectPaths = includedProjects.Select(t => ((string)t)?.Replace('\\', Path.DirectorySeparatorChar)).ToArray(); + + var fullSolutionDirectory = Path.GetDirectoryName(Path.Combine(basePath, solutionPath)); + var fullSolutionFile = Parse(File.ReadAllText(Path.Combine(basePath, solutionPath))); + + var formatVersion = fullSolutionFile.FormatVersion; + var visualStudioVersion = fullSolutionFile.VisualStudioVersion; + var globalSections = fullSolutionFile.GlobalSections; + + var projects = ImmutableArray.CreateBuilder(); + foreach (var fullProject in fullSolutionFile.Projects) + { + var fullProjectPath = Path.GetFullPath(Path.Combine(fullSolutionDirectory, fullProject.RelativePath)); + var includedPath = includedProjectPaths.FirstOrDefault(included => string.Equals(Path.GetFullPath(Path.Combine(fullSolutionDirectory, included)), fullProjectPath, StringComparison.OrdinalIgnoreCase)); + if (includedPath is null) + continue; + + string relativeProjectPath; +#if NETCOREAPP + relativeProjectPath = Path.GetRelativePath(basePath, fullProjectPath); +#else + var projectUri = new Uri(fullProjectPath, UriKind.Absolute); + var solutionFilterDirectoryUri = new Uri(Path.GetFullPath(basePath) + Path.DirectorySeparatorChar, UriKind.Absolute); + relativeProjectPath = solutionFilterDirectoryUri.MakeRelativeUri(projectUri).OriginalString.Replace('/', Path.DirectorySeparatorChar); +#endif + projects.Add(fullProject.WithRelativePath(relativeProjectPath)); + } + + return new SolutionFile(formatVersion, visualStudioVersion, projects.ToImmutable(), globalSections); + } + public static SolutionFile ParseFile(string path) { var text = File.ReadAllText(path); - return Parse(text); + if (string.Equals(".slnf", Path.GetExtension(path), StringComparison.OrdinalIgnoreCase)) + return ParseSolutionFilter(basePath: Path.GetDirectoryName(path), text); + else + return Parse(text); } private static Version ParseHeaderAndVersion(Scanner scanner) From 878ddd76ec4d45ce9aab4406aeeff8ad0bd87c53 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 23 Oct 2020 17:13:08 -0700 Subject: [PATCH 4/4] Account for different GetFiles behavior of Mono --- src/OmniSharp.MSBuild/ProjectSystem.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index 38089b8c90..d4e0688269 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -223,11 +223,12 @@ public void Initalize(IConfiguration configuration) private static string FindSolutionFilePath(string rootPath, ILogger logger) { - // currently, Directory.GetFiles collects files that the file extension has 'sln' prefix. - // this causes collecting unexpected files like 'x.slnx', or 'x.slnproj'. + // currently, Directory.GetFiles on Windows collects files that the file extension has 'sln' prefix, while + // GetFiles on Mono looks for an exact match. Use an approach that works for both. // see https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netframework-4.7.2 ('Note' description) - var solutionsFilePaths = Directory.GetFiles(rootPath, "*.sln").Where(x => Path.GetExtension(x).Equals(".sln", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(x).Equals(".slnf", StringComparison.OrdinalIgnoreCase)).ToArray(); - var result = SolutionSelector.Pick(solutionsFilePaths, rootPath); + var solutionsFilePaths = Directory.GetFiles(rootPath, "*.sln").Where(x => Path.GetExtension(x).Equals(".sln", StringComparison.OrdinalIgnoreCase)).ToArray(); + var solutionFiltersFilePaths = Directory.GetFiles(rootPath, "*.slnf").Where(x => Path.GetExtension(x).Equals(".slnf", StringComparison.OrdinalIgnoreCase)).ToArray(); + var result = SolutionSelector.Pick(solutionsFilePaths.Concat(solutionFiltersFilePaths).ToArray(), rootPath); if (result.Message != null) {