From 27fabc354d7f4c7e937ec9a5da4bcad1db781940 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Sat, 19 Sep 2020 14:48:20 -0700 Subject: [PATCH 1/5] 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 bf9c5541be..b5a0d34104 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -228,7 +228,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 2853e36e148ab8905a56126b97b52bbbc6c4a061 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 21 Sep 2020 06:20:59 -0700 Subject: [PATCH 2/5] 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 f00ee05e20..7b67e2a72f 100644 --- a/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs +++ b/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs @@ -45,6 +45,34 @@ public async Task TestProjectAndSolution() Assert.Equal("netcoreapp3.1", targetFramework.ShortName); } + [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 35cf3b006c1f22194909494da00d8fef89874745 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 21 Sep 2020 15:06:20 -0700 Subject: [PATCH 3/5] 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 6044b3258abf911faaa4be850630fe77ea612a88 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 23 Oct 2020 17:13:08 -0700 Subject: [PATCH 4/5] 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 b5a0d34104..ca240b0ad1 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -225,11 +225,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) { From e493334a59894ad0527123b737b5356788165656 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Wed, 24 Mar 2021 18:44:37 -0700 Subject: [PATCH 5/5] Apply the solution filter at the project system layer. --- src/OmniSharp.MSBuild/ProjectSystem.cs | 34 ++++++--- .../SolutionParsing/ProjectBlock.cs | 8 --- .../SolutionParsing/SolutionFile.cs | 43 +----------- .../SolutionParsing/SolutionFilterReader.cs | 70 +++++++++++++++++++ .../WorkspaceInformationTests.cs | 2 +- 5 files changed, 95 insertions(+), 62 deletions(-) create mode 100644 src/OmniSharp.MSBuild/SolutionParsing/SolutionFilterReader.cs diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index ca240b0ad1..1159b17c4a 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -128,23 +128,21 @@ public void Initalize(IConfiguration configuration) } } - public Task WaitForIdleAsync() { return _manager.WaitForQueueEmptyAsync(); } + public Task WaitForIdleAsync() { return _manager.WaitForQueueEmptyAsync(); } private IEnumerable<(string, ProjectIdInfo)> GetInitialProjectPathsAndIds() { // If a solution was provided, use it. if (!string.IsNullOrEmpty(_environment.SolutionFilePath)) { - _solutionFileOrRootPath = _environment.SolutionFilePath; - return GetProjectPathsAndIdsFromSolution(_environment.SolutionFilePath); + return GetProjectPathsAndIdsFromSolutionOrFilter(_environment.SolutionFilePath, out _solutionFileOrRootPath); } // Otherwise, assume that the path provided is a directory and look for a solution there. var solutionFilePath = FindSolutionFilePath(_environment.TargetDirectory, _logger); if (!string.IsNullOrEmpty(solutionFilePath)) { - _solutionFileOrRootPath = solutionFilePath; - return GetProjectPathsAndIdsFromSolution(solutionFilePath); + return GetProjectPathsAndIdsFromSolutionOrFilter(solutionFilePath, out _solutionFileOrRootPath); } // Finally, if there isn't a single solution immediately available, @@ -158,10 +156,20 @@ public void Initalize(IConfiguration configuration) }); } - private IEnumerable<(string, ProjectIdInfo)> GetProjectPathsAndIdsFromSolution(string solutionFilePath) + private IEnumerable<(string, ProjectIdInfo)> GetProjectPathsAndIdsFromSolutionOrFilter(string solutionOrFilterFilePath, out string solutionFilePath) { - _logger.LogInformation($"Detecting projects in '{solutionFilePath}'."); + _logger.LogInformation($"Detecting projects in '{solutionOrFilterFilePath}'."); + solutionFilePath = solutionOrFilterFilePath; + + var projectFilter = ImmutableHashSet.Empty; + if (SolutionFilterReader.IsSolutionFilterFilename(solutionOrFilterFilePath) && + !SolutionFilterReader.TryRead(solutionOrFilterFilePath, out solutionFilePath, out projectFilter)) + { + throw new InvalidSolutionFileException($"Solution filter file was invalid."); + } + + var solutionFolder = Path.GetDirectoryName(solutionFilePath); var solutionFile = SolutionFile.ParseFile(solutionFilePath); var processedProjects = new HashSet(StringComparer.OrdinalIgnoreCase); var result = new List<(string, ProjectIdInfo)>(); @@ -196,10 +204,14 @@ public void Initalize(IConfiguration configuration) continue; } - // Solution files are assumed to contain relative paths to project files with Windows-style slashes. - var projectFilePath = project.RelativePath.Replace('\\', Path.DirectorySeparatorChar); - projectFilePath = Path.Combine(_environment.TargetDirectory, projectFilePath); - projectFilePath = Path.GetFullPath(projectFilePath); + // Solution files contain relative paths to project files with Windows-style slashes. + var relativeProjectfilePath = project.RelativePath.Replace('\\', Path.DirectorySeparatorChar); + var projectFilePath = Path.GetFullPath(Path.Combine(solutionFolder, relativeProjectfilePath)); + if (!projectFilter.IsEmpty && + !projectFilter.Contains(projectFilePath)) + { + continue; + } // Have we seen this project? If so, move on. if (processedProjects.Contains(projectFilePath)) diff --git a/src/OmniSharp.MSBuild/SolutionParsing/ProjectBlock.cs b/src/OmniSharp.MSBuild/SolutionParsing/ProjectBlock.cs index 4f3ecab41e..9cf83c4cfc 100644 --- a/src/OmniSharp.MSBuild/SolutionParsing/ProjectBlock.cs +++ b/src/OmniSharp.MSBuild/SolutionParsing/ProjectBlock.cs @@ -98,13 +98,5 @@ 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 b37749fc30..053414e7fa 100644 --- a/src/OmniSharp.MSBuild/SolutionParsing/SolutionFile.cs +++ b/src/OmniSharp.MSBuild/SolutionParsing/SolutionFile.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Immutable; using System.IO; -using System.Linq; -using Newtonsoft.Json.Linq; namespace OmniSharp.MSBuild.SolutionParsing { @@ -70,49 +68,10 @@ 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); - if (string.Equals(".slnf", Path.GetExtension(path), StringComparison.OrdinalIgnoreCase)) - return ParseSolutionFilter(basePath: Path.GetDirectoryName(path), text); - else - return Parse(text); + return Parse(text); } private static Version ParseHeaderAndVersion(Scanner scanner) diff --git a/src/OmniSharp.MSBuild/SolutionParsing/SolutionFilterReader.cs b/src/OmniSharp.MSBuild/SolutionParsing/SolutionFilterReader.cs new file mode 100644 index 0000000000..a2f089bb6d --- /dev/null +++ b/src/OmniSharp.MSBuild/SolutionParsing/SolutionFilterReader.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using Newtonsoft.Json.Linq; + +namespace OmniSharp.MSBuild.SolutionParsing +{ + internal static class SolutionFilterReader + { + public static bool IsSolutionFilterFilename(string filename) + { + return Path.GetExtension(filename).Equals(".slnf", StringComparison.OrdinalIgnoreCase); + } + + public static bool TryRead(string filterFilename, out string solutionFilename, out ImmutableHashSet projectFilter) + { + try + { + var filterDirectory = Path.GetDirectoryName(filterFilename); + + var document = JObject.Parse(File.ReadAllText(filterFilename)); + var solution = document["solution"]; + // Convert directory separators to the platform's default, since that is what MSBuild provide us. + var solutionPath = ((string)solution?["path"])?.Replace('\\', Path.DirectorySeparatorChar); + + solutionFilename = Path.GetFullPath(Path.Combine(filterDirectory, solutionPath)); + if (!File.Exists(solutionFilename)) + { + projectFilter = ImmutableHashSet.Empty; + return false; + } + + // The base directory for projects is the solution folder. + var solutionDirectory = Path.GetDirectoryName(solutionFilename); + + var filterProjects = ImmutableHashSet.CreateBuilder(StringComparer.OrdinalIgnoreCase); + var projects = (JArray)solution?["projects"] ?? new JArray(); + foreach (string project in projects) + { + // Convert directory separators to the platform's default, since that is what MSBuild provide us. + var projectPath = project?.Replace('\\', Path.DirectorySeparatorChar); + if (projectPath is null) + { + projectFilter = ImmutableHashSet.Empty; + return false; + } + + var projectFilename = Path.GetFullPath(Path.Combine(solutionDirectory, projectPath)); + if (!File.Exists(projectFilename)) + { + projectFilter = ImmutableHashSet.Empty; + return false; + } + + // Fill the filter with the absolute project paths. + filterProjects.Add(projectFilename); + } + + projectFilter = filterProjects.ToImmutable(); + return true; + } + catch + { + solutionFilename = string.Empty; + projectFilter = ImmutableHashSet.Empty; + return false; + } + } + } +} diff --git a/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs b/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs index 7b67e2a72f..ebfe009f9e 100644 --- a/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs +++ b/tests/OmniSharp.MSBuild.Tests/WorkspaceInformationTests.cs @@ -53,7 +53,7 @@ public async Task TestProjectAndSolutionFilter() { var workspaceInfo = await host.RequestMSBuildWorkspaceInfoAsync(); - Assert.Equal("ProjectAndSolutionFilter.slnf", Path.GetFileName(workspaceInfo.SolutionPath)); + Assert.Equal("ProjectAndSolutionFilter.sln", Path.GetFileName(workspaceInfo.SolutionPath)); Assert.NotNull(workspaceInfo.Projects); var project = Assert.Single(workspaceInfo.Projects);