From e1d98ce7f8e155e48f64a04858f23e66395167c7 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 6 Jan 2025 14:32:46 -0800 Subject: [PATCH] Remove SlnFile references --- src/Cli/dotnet/ProjectInstanceExtensions.cs | 4 +- src/Cli/dotnet/SlnFileExtensions.cs | 541 ------------------ .../dotnet/SlnProjectCollectionExtensions.cs | 24 - src/Cli/dotnet/SlnProjectExtensions.cs | 36 -- .../commands/dotnet-sln/list/Program.cs | 1 - test/dotnet-sln.Tests/GivenDotnetSlnList.cs | 1 - .../dotnet-sln.Tests/GivenDotnetSlnMigrate.cs | 1 - 7 files changed, 2 insertions(+), 606 deletions(-) delete mode 100644 src/Cli/dotnet/SlnFileExtensions.cs delete mode 100644 src/Cli/dotnet/SlnProjectCollectionExtensions.cs delete mode 100644 src/Cli/dotnet/SlnProjectExtensions.cs diff --git a/src/Cli/dotnet/ProjectInstanceExtensions.cs b/src/Cli/dotnet/ProjectInstanceExtensions.cs index 29337fa22c35..67fddac7f032 100644 --- a/src/Cli/dotnet/ProjectInstanceExtensions.cs +++ b/src/Cli/dotnet/ProjectInstanceExtensions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Build.Execution; -using Microsoft.DotNet.Cli.Sln.Internal; namespace Microsoft.DotNet.Tools.Common { @@ -22,7 +21,8 @@ public static string GetDefaultProjectTypeGuid(this ProjectInstance projectInsta string projectTypeGuid = projectInstance.GetPropertyValue("DefaultProjectTypeGuid"); if (string.IsNullOrEmpty(projectTypeGuid) && projectInstance.FullPath.EndsWith(".shproj", StringComparison.OrdinalIgnoreCase)) { - projectTypeGuid = ProjectTypeGuids.SharedProjectGuid; + // TODO: Centralize project type guids + projectTypeGuid = "{D954291E-2A0B-460D-934E-DC6B0785DB48}"; } return projectTypeGuid; } diff --git a/src/Cli/dotnet/SlnFileExtensions.cs b/src/Cli/dotnet/SlnFileExtensions.cs deleted file mode 100644 index 0a981b9e2429..000000000000 --- a/src/Cli/dotnet/SlnFileExtensions.cs +++ /dev/null @@ -1,541 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Build.Construction; -using Microsoft.Build.Exceptions; -using Microsoft.Build.Execution; -using Microsoft.DotNet.Cli.Sln.Internal; -using Microsoft.DotNet.Cli.Utils; -using System.Collections.Generic; - -namespace Microsoft.DotNet.Tools.Common -{ - internal static class SlnFileExtensions - { - public static void AddProject(this SlnFile slnFile, string fullProjectPath, IList solutionFolders) - { - if (string.IsNullOrEmpty(fullProjectPath)) - { - throw new ArgumentException(); - } - - var relativeProjectPath = Path.GetRelativePath( - PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), - fullProjectPath); - - if (slnFile.Projects.Any((p) => - string.Equals(p.FilePath, relativeProjectPath, StringComparison.OrdinalIgnoreCase))) - { - Reporter.Output.WriteLine(string.Format( - CommonLocalizableStrings.SolutionAlreadyContainsProject, - slnFile.FullPath, - relativeProjectPath)); - } - else - { - ProjectRootElement rootElement = null; - ProjectInstance projectInstance = null; - try - { - rootElement = ProjectRootElement.Open(fullProjectPath); - projectInstance = new ProjectInstance(rootElement); - } - catch (InvalidProjectFileException e) - { - Reporter.Error.WriteLine(string.Format( - CommonLocalizableStrings.InvalidProjectWithExceptionMessage, - fullProjectPath, - e.Message)); - return; - } - - var slnProject = new SlnProject - { - Id = projectInstance.GetProjectId(), - TypeGuid = rootElement.GetProjectTypeGuid() ?? projectInstance.GetDefaultProjectTypeGuid(), - Name = Path.GetFileNameWithoutExtension(relativeProjectPath), - FilePath = relativeProjectPath - }; - - if (string.IsNullOrEmpty(slnProject.TypeGuid)) - { - Reporter.Error.WriteLine( - string.Format( - CommonLocalizableStrings.UnsupportedProjectType, - projectInstance.FullPath)); - return; - } - - // NOTE: The order you create the sections determines the order they are written to the sln - // file. In the case of an empty sln file, in order to make sure the solution configurations - // section comes first we need to add it first. This doesn't affect correctness but does - // stop VS from re-ordering things later on. Since we are keeping the SlnFile class low-level - // it shouldn't care about the VS implementation details. That's why we handle this here. - if (AreBuildConfigurationsApplicable(slnProject.TypeGuid)) - { - slnFile.AddDefaultBuildConfigurations(); - - slnFile.MapSolutionConfigurationsToProject( - projectInstance, - slnFile.ProjectConfigurationsSection.GetOrCreatePropertySet(slnProject.Id)); - } - - SetupSolutionFolders(slnFile, solutionFolders, relativeProjectPath, slnProject); - - slnFile.Projects.Add(slnProject); - - Reporter.Output.WriteLine( - string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, relativeProjectPath)); - } - } - - private static bool AreBuildConfigurationsApplicable(string projectTypeGuid) - { - return !projectTypeGuid.Equals(ProjectTypeGuids.SharedProjectGuid, StringComparison.OrdinalIgnoreCase); - } - - private static void SetupSolutionFolders(SlnFile slnFile, IList solutionFolders, string relativeProjectPath, SlnProject slnProject) - { - if (solutionFolders != null) - { - if (solutionFolders.Any()) - { - // Before adding a solution folder, check if the name conflicts with any existing projects in the solution - var duplicateProjects = slnFile.Projects.Where(p => solutionFolders.Contains(p.Name) - && p.TypeGuid != ProjectTypeGuids.SolutionFolderGuid).ToList(); - foreach (SlnProject duplicateProject in duplicateProjects) - { - slnFile.AddSolutionFolders(duplicateProject, new List() { Path.GetDirectoryName(duplicateProject.FilePath) }); - } - } - else - { - // If a project and solution folder have the same name, add it's own folder as a solution folder - // eg. foo\extensions.csproj and extensions\library\library.csproj would have a project and solution folder with conflicting names - var duplicateProject = slnFile.Projects.Where(p => string.Equals(p.Name, slnProject.Name, StringComparison.OrdinalIgnoreCase) - && p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid).FirstOrDefault(); - if (duplicateProject != null) - { - // Try making a new folder for the project to put it under so we can still add it despite there being one with the same name already in the parent folder - slnFile.AddSolutionFolders(slnProject, new List() { Path.GetDirectoryName(relativeProjectPath) }); - } - } - // Even if we added a solution folder above for a duplicate, we still need to add the expected folder for the current project - slnFile.AddSolutionFolders(slnProject, solutionFolders); - } - } - - private static void AddDefaultBuildConfigurations(this SlnFile slnFile) - { - var configurationsSection = slnFile.SolutionConfigurationsSection; - - if (!configurationsSection.IsEmpty) - { - return; - } - - var defaultConfigurations = new List() - { - "Debug|Any CPU", - "Debug|x64", - "Debug|x86", - "Release|Any CPU", - "Release|x64", - "Release|x86", - }; - - foreach (var config in defaultConfigurations) - { - configurationsSection[config] = config; - } - } - - private static void MapSolutionConfigurationsToProject( - this SlnFile slnFile, - ProjectInstance projectInstance, - SlnPropertySet solutionProjectConfigs) - { - var (projectConfigurations, defaultProjectConfiguration) = GetKeysDictionary(projectInstance.GetConfigurations()); - var (projectPlatforms, defaultProjectPlatform) = GetKeysDictionary(projectInstance.GetPlatforms()); - - foreach (var solutionConfigKey in slnFile.SolutionConfigurationsSection.Keys) - { - var projectConfigKey = MapSolutionConfigKeyToProjectConfigKey( - solutionConfigKey, - projectConfigurations, - defaultProjectConfiguration, - projectPlatforms, - defaultProjectPlatform); - if (projectConfigKey == null) - { - continue; - } - - var activeConfigKey = $"{solutionConfigKey}.ActiveCfg"; - if (!solutionProjectConfigs.ContainsKey(activeConfigKey)) - { - solutionProjectConfigs[activeConfigKey] = projectConfigKey; - } - - var buildKey = $"{solutionConfigKey}.Build.0"; - if (!solutionProjectConfigs.ContainsKey(buildKey)) - { - solutionProjectConfigs[buildKey] = projectConfigKey; - } - } - } - - private static (Dictionary Keys, string DefaultKey) GetKeysDictionary(IEnumerable keys) - { - // A dictionary mapping key -> key is used instead of a HashSet so the original case of the key can be retrieved from the set - var dictionary = new Dictionary(StringComparer.CurrentCultureIgnoreCase); - - foreach (var key in keys) - { - dictionary[key] = key; - } - - return (dictionary, keys.FirstOrDefault()); - } - - private static string GetMatchingProjectKey(IDictionary projectKeys, string solutionKey) - { - string projectKey; - if (projectKeys.TryGetValue(solutionKey, out projectKey)) - { - return projectKey; - } - - var keyWithoutWhitespace = string.Concat(solutionKey.Where(c => !char.IsWhiteSpace(c))); - if (projectKeys.TryGetValue(keyWithoutWhitespace, out projectKey)) - { - return projectKey; - } - - return null; - } - - private static string MapSolutionConfigKeyToProjectConfigKey( - string solutionConfigKey, - Dictionary projectConfigurations, - string defaultProjectConfiguration, - Dictionary projectPlatforms, - string defaultProjectPlatform) - { - var pair = solutionConfigKey.Split(new char[] { '|' }, 2); - if (pair.Length != 2) - { - return null; - } - - var projectConfiguration = GetMatchingProjectKey(projectConfigurations, pair[0]) ?? defaultProjectConfiguration; - if (projectConfiguration == null) - { - return null; - } - - var projectPlatform = GetMatchingProjectKey(projectPlatforms, pair[1]) ?? defaultProjectPlatform; - if (projectPlatform == null) - { - return null; - } - - // VS stores "Any CPU" platform in the solution regardless of how it is named at the project level - return $"{projectConfiguration}|{(projectPlatform == "AnyCPU" ? "Any CPU" : projectPlatform)}"; - } - - private static void AddSolutionFolders(this SlnFile slnFile, SlnProject slnProject, IList solutionFolders) - { - if (solutionFolders.Any()) - { - var nestedProjectsSection = slnFile.Sections.GetOrCreateSection( - "NestedProjects", - SlnSectionType.PreProcess); - - var pathToGuidMap = slnFile.GetSolutionFolderPaths(nestedProjectsSection.Properties); - - if (slnFile.HasSolutionFolder(nestedProjectsSection.Properties, slnProject)) - { - return; - } - - string parentDirGuid = null; - var solutionFolderHierarchy = string.Empty; - foreach (var dir in solutionFolders) - { - solutionFolderHierarchy = Path.Combine(solutionFolderHierarchy, dir); - if (pathToGuidMap.ContainsKey(solutionFolderHierarchy)) - { - parentDirGuid = pathToGuidMap[solutionFolderHierarchy]; - } - else - { - - if(HasDuplicateNameForSameValueOfNestedProjects(nestedProjectsSection, dir, parentDirGuid, slnFile.Projects)) - { - throw new GracefulException(CommonLocalizableStrings.SolutionFolderAlreadyContainsProject, slnFile.FullPath, slnProject.Name, slnFile.Projects.FirstOrDefault(p => p.Id == parentDirGuid).Name); - } - - var solutionFolder = new SlnProject - { - Id = Guid.NewGuid().ToString("B").ToUpper(), - TypeGuid = ProjectTypeGuids.SolutionFolderGuid, - Name = dir, - FilePath = dir - }; - - slnFile.Projects.Add(solutionFolder); - - if (parentDirGuid != null) - { - nestedProjectsSection.Properties[solutionFolder.Id] = parentDirGuid; - } - parentDirGuid = solutionFolder.Id; - } - } - if (HasDuplicateNameForSameValueOfNestedProjects(nestedProjectsSection, slnProject.Name, parentDirGuid, slnFile.Projects)) - { - throw new GracefulException(CommonLocalizableStrings.SolutionFolderAlreadyContainsProject, slnFile.FullPath, slnProject.Name, slnFile.Projects.FirstOrDefault(p => p.Id == parentDirGuid).Name); - } - nestedProjectsSection.Properties[slnProject.Id] = parentDirGuid; - } - } - - private static bool HasDuplicateNameForSameValueOfNestedProjects(SlnSection nestedProjectsSection, string name, string value, IList projects) - { - foreach (var property in nestedProjectsSection.Properties) - { - if (property.Value == value) - { - var existingProject = projects.FirstOrDefault(p => p.Id == property.Key); - - if (existingProject != null && existingProject.Name == name) - { - return true; - } - } - } - return false; - } - - private static IDictionary GetSolutionFolderPaths( - this SlnFile slnFile, - SlnPropertySet nestedProjects) - { - var solutionFolderPaths = new Dictionary(StringComparer.OrdinalIgnoreCase); - - var solutionFolderProjects = slnFile.Projects.GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid); - foreach (var slnProject in solutionFolderProjects) - { - var path = slnProject.FilePath; - var id = slnProject.Id; - while (nestedProjects.ContainsKey(id)) - { - id = nestedProjects[id]; - var parentSlnProject = solutionFolderProjects.Where(p => p.Id == id).SingleOrDefault(); - if (parentSlnProject == null) // see: https://github.com/dotnet/sdk/pull/28811 - throw new GracefulException(CommonLocalizableStrings.CorruptSolutionProjectFolderStructure, slnFile.FullPath, id); - path = Path.Combine(parentSlnProject.FilePath, path); - } - - solutionFolderPaths[path] = slnProject.Id; - } - - return solutionFolderPaths; - } - - private static bool HasSolutionFolder( - this SlnFile slnFile, - SlnPropertySet properties, - SlnProject slnProject) - { - return properties.ContainsKey(slnProject.Id); - } - - public static bool RemoveProject(this SlnFile slnFile, string projectPath) - { - if (string.IsNullOrEmpty(projectPath)) - { - throw new ArgumentException(); - } - - var projectPathNormalized = PathUtility.GetPathWithDirectorySeparator(projectPath); - - var projectsToRemove = slnFile.Projects.Where((p) => - string.Equals(p.FilePath, projectPathNormalized, StringComparison.OrdinalIgnoreCase)).ToList(); - - bool projectRemoved = false; - if (projectsToRemove.Count == 0) - { - Reporter.Output.WriteLine(string.Format( - CommonLocalizableStrings.ProjectNotFoundInTheSolution, - projectPath)); - } - else - { - foreach (var slnProject in projectsToRemove) - { - var buildConfigsToRemove = slnFile.ProjectConfigurationsSection.GetPropertySet(slnProject.Id); - if (buildConfigsToRemove != null) - { - slnFile.ProjectConfigurationsSection.Remove(buildConfigsToRemove); - } - - var nestedProjectsSection = slnFile.Sections.GetSection( - "NestedProjects", - SlnSectionType.PreProcess); - if (nestedProjectsSection != null && nestedProjectsSection.Properties.ContainsKey(slnProject.Id)) - { - nestedProjectsSection.Properties.Remove(slnProject.Id); - } - - slnFile.Projects.Remove(slnProject); - Reporter.Output.WriteLine( - string.Format(CommonLocalizableStrings.ProjectRemovedFromTheSolution, slnProject.FilePath)); - } - - foreach (var project in slnFile.Projects) - { - var dependencies = project.Dependencies; - if (dependencies == null) - { - continue; - } - - dependencies.SkipIfEmpty = true; - - foreach (var removed in projectsToRemove) - { - dependencies.Properties.Remove(removed.Id); - } - } - - projectRemoved = true; - } - - return projectRemoved; - } - - public static void RemoveEmptyConfigurationSections(this SlnFile slnFile) - { - if (slnFile.Projects.Count == 0) - { - var solutionConfigs = slnFile.Sections.GetSection("SolutionConfigurationPlatforms"); - if (solutionConfigs != null) - { - slnFile.Sections.Remove(solutionConfigs); - } - - var projectConfigs = slnFile.Sections.GetSection("ProjectConfigurationPlatforms"); - if (projectConfigs != null) - { - slnFile.Sections.Remove(projectConfigs); - } - } - } - - public static void RemoveEmptySolutionFolders(this SlnFile slnFile) - { - var solutionFolderProjects = slnFile.Projects - .GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid) - .ToList(); - - if (solutionFolderProjects.Any()) - { - var nestedProjectsSection = slnFile.Sections.GetSection( - "NestedProjects", - SlnSectionType.PreProcess); - - if (nestedProjectsSection == null) - { - foreach (var solutionFolderProject in solutionFolderProjects) - { - if (solutionFolderProject.Sections.Count() == 0) - { - slnFile.Projects.Remove(solutionFolderProject); - } - } - } - else - { - var solutionFoldersInUse = slnFile.GetSolutionFoldersThatContainProjectsInItsHierarchy( - nestedProjectsSection.Properties); - - solutionFoldersInUse.UnionWith(slnFile.GetSolutionFoldersThatContainSolutionItemsInItsHierarchy( - nestedProjectsSection.Properties)); - - foreach (var solutionFolderProject in solutionFolderProjects) - { - if (!solutionFoldersInUse.Contains(solutionFolderProject.Id)) - { - nestedProjectsSection.Properties.Remove(solutionFolderProject.Id); - if (solutionFolderProject.Sections.Count() == 0) - { - slnFile.Projects.Remove(solutionFolderProject); - } - } - } - - if (nestedProjectsSection.IsEmpty) - { - slnFile.Sections.Remove(nestedProjectsSection); - } - } - } - } - - private static HashSet GetSolutionFoldersThatContainProjectsInItsHierarchy( - this SlnFile slnFile, - SlnPropertySet nestedProjects) - { - var solutionFoldersInUse = new HashSet(); - - IEnumerable nonSolutionFolderProjects; - nonSolutionFolderProjects = slnFile.Projects.GetProjectsNotOfType( - ProjectTypeGuids.SolutionFolderGuid); - - foreach (var nonSolutionFolderProject in nonSolutionFolderProjects) - { - var id = nonSolutionFolderProject.Id; - while (nestedProjects.ContainsKey(id)) - { - id = nestedProjects[id]; - solutionFoldersInUse.Add(id); - } - } - - return solutionFoldersInUse; - } - - private static HashSet GetSolutionFoldersThatContainSolutionItemsInItsHierarchy( - this SlnFile slnFile, - SlnPropertySet nestedProjects) - { - var solutionFoldersInUse = new HashSet(); - - var solutionItemsFolderProjects = slnFile.Projects - .GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid) - .Where(ContainsSolutionItems); - - foreach (var solutionItemsFolderProject in solutionItemsFolderProjects) - { - var id = solutionItemsFolderProject.Id; - solutionFoldersInUse.Add(id); - - while (nestedProjects.ContainsKey(id)) - { - id = nestedProjects[id]; - solutionFoldersInUse.Add(id); - } - } - - return solutionFoldersInUse; - } - - private static bool ContainsSolutionItems(SlnProject project) - { - return project.Sections - .GetSection("SolutionItems", SlnSectionType.PreProcess) != null; - } - } -} diff --git a/src/Cli/dotnet/SlnProjectCollectionExtensions.cs b/src/Cli/dotnet/SlnProjectCollectionExtensions.cs deleted file mode 100644 index f0d70ae4e640..000000000000 --- a/src/Cli/dotnet/SlnProjectCollectionExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.DotNet.Cli.Sln.Internal; - -namespace Microsoft.DotNet.Tools.Common -{ - internal static class SlnProjectCollectionExtensions - { - public static IEnumerable GetProjectsByType( - this SlnProjectCollection projects, - string typeGuid) - { - return projects.Where(p => p.TypeGuid == typeGuid); - } - - public static IEnumerable GetProjectsNotOfType( - this SlnProjectCollection projects, - string typeGuid) - { - return projects.Where(p => p.TypeGuid != typeGuid); - } - } -} diff --git a/src/Cli/dotnet/SlnProjectExtensions.cs b/src/Cli/dotnet/SlnProjectExtensions.cs deleted file mode 100644 index 405c3b43cb88..000000000000 --- a/src/Cli/dotnet/SlnProjectExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.DotNet.Cli.Sln.Internal; - -namespace Microsoft.DotNet.Tools.Common -{ - internal static class SlnProjectExtensions - { - public static string GetFullSolutionFolderPath(this SlnProject slnProject) - { - var slnFile = slnProject.ParentFile; - var nestedProjects = slnFile.Sections - .GetOrCreateSection("NestedProjects", SlnSectionType.PreProcess) - .Properties; - var solutionFolders = slnFile.Projects - .GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid) - .ToArray(); - - string path = slnProject.Name; - string id = slnProject.Id; - - // If the nested projects contains this project's id then it has a parent - // Traverse from the project to each parent prepending the solution folder to the path - while (nestedProjects.ContainsKey(id)) - { - id = nestedProjects[id]; - - string solutionFolderPath = solutionFolders.Single(p => p.Id == id).FilePath; - path = Path.Combine(solutionFolderPath, path); - } - - return path; - } - } -} diff --git a/src/Cli/dotnet/commands/dotnet-sln/list/Program.cs b/src/Cli/dotnet/commands/dotnet-sln/list/Program.cs index 99b948207011..0a7522c46242 100644 --- a/src/Cli/dotnet/commands/dotnet-sln/list/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-sln/list/Program.cs @@ -3,7 +3,6 @@ using System.CommandLine; using Microsoft.DotNet.Cli; -using Microsoft.DotNet.Cli.Sln.Internal; using Microsoft.DotNet.Cli.Utils; using Microsoft.VisualStudio.SolutionPersistence; using Microsoft.VisualStudio.SolutionPersistence.Model; diff --git a/test/dotnet-sln.Tests/GivenDotnetSlnList.cs b/test/dotnet-sln.Tests/GivenDotnetSlnList.cs index f462ff1e6042..7cd5200208bd 100644 --- a/test/dotnet-sln.Tests/GivenDotnetSlnList.cs +++ b/test/dotnet-sln.Tests/GivenDotnetSlnList.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.DotNet.Cli.Sln.Internal; using Microsoft.DotNet.Tools; using Microsoft.DotNet.Tools.Common; using CommandLocalizableStrings = Microsoft.DotNet.Tools.Sln.LocalizableStrings; diff --git a/test/dotnet-sln.Tests/GivenDotnetSlnMigrate.cs b/test/dotnet-sln.Tests/GivenDotnetSlnMigrate.cs index 7af7267a2997..f18ac2d7fba5 100644 --- a/test/dotnet-sln.Tests/GivenDotnetSlnMigrate.cs +++ b/test/dotnet-sln.Tests/GivenDotnetSlnMigrate.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.DotNet.Cli.Sln.Internal; using Microsoft.DotNet.Tools; using Microsoft.DotNet.Tools.Common; using CommandLocalizableStrings = Microsoft.DotNet.Tools.Sln.LocalizableStrings;