From 8c31de587a391e391b65d6214517496366c249ae Mon Sep 17 00:00:00 2001 From: David Federman Date: Fri, 16 Aug 2024 11:06:14 -0700 Subject: [PATCH] Add option to use MSBuild's graph to discover projects (#56) --- Directory.Packages.props | 1 + .../PackagesConfigConverter.csproj | 3 +- src/PackagesConfigConverter/Program.cs | 11 ++++ .../ProgramArguments.cs | 3 + .../ProjectConverter.cs | 66 ++++++++++++++++++- .../ProjectConverterSettings.cs | 2 + 6 files changed, 83 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1648f83..a038900 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,6 +2,7 @@ + diff --git a/src/PackagesConfigConverter/PackagesConfigConverter.csproj b/src/PackagesConfigConverter/PackagesConfigConverter.csproj index 362fce3..940e46e 100644 --- a/src/PackagesConfigConverter/PackagesConfigConverter.csproj +++ b/src/PackagesConfigConverter/PackagesConfigConverter.csproj @@ -5,7 +5,8 @@ - + + diff --git a/src/PackagesConfigConverter/Program.cs b/src/PackagesConfigConverter/Program.cs index eb7ae77..4dcbdb0 100644 --- a/src/PackagesConfigConverter/Program.cs +++ b/src/PackagesConfigConverter/Program.cs @@ -7,6 +7,7 @@ using System.IO; using System.Threading; using CommandLine; +using Microsoft.Build.Locator; using Microsoft.Extensions.Logging; using NuGet.Frameworks; using Serilog; @@ -62,15 +63,24 @@ public static void Run(ProgramArguments arguments) using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddSerilog()); ILogger logger = factory.CreateLogger("PackagesConfigConverter"); + MSBuildLocator.RegisterDefaults(); + ProjectConverterSettings settings = new ProjectConverterSettings { RepositoryRoot = arguments.RepoRoot, Include = arguments.Include.ToRegex(), Exclude = arguments.Exclude.ToRegex(), + Graph = arguments.Graph, Log = logger, TrimPackages = arguments.Trim, }; + if (settings.Graph && settings.Include != null) + { + logger.LogError("Cannot specify using graph discovery and specific project inclusion."); + return; + } + if (arguments.DefaultTargetFramework != null) { settings.DefaultTargetFramework = NuGetFramework.Parse(arguments.DefaultTargetFramework); @@ -79,6 +89,7 @@ public static void Run(ProgramArguments arguments) logger.LogInformation($" RepositoryRoot: '{settings.RepositoryRoot}'"); logger.LogInformation($" Include regex: '{settings.Include}'"); logger.LogInformation($" Exclude regex: '{settings.Exclude}'"); + logger.LogInformation($" Graph discovery: '{settings.Graph}'"); logger.LogInformation(string.Empty); if (!arguments.Yes) diff --git a/src/PackagesConfigConverter/ProgramArguments.cs b/src/PackagesConfigConverter/ProgramArguments.cs index 4967df6..0deab0b 100644 --- a/src/PackagesConfigConverter/ProgramArguments.cs +++ b/src/PackagesConfigConverter/ProgramArguments.cs @@ -17,6 +17,9 @@ public class ProgramArguments [Option('i', HelpText = "Regex for project files to include", MetaValue = "regex")] public string Include { get; set; } + [Option('g', HelpText = "Whether to use the MSBuild graph to discover project files")] + public bool Graph { get; set; } + [Option('l', HelpText = "Log file to write to", MetaValue = "log")] public string LogFile { get; set; } diff --git a/src/PackagesConfigConverter/ProjectConverter.cs b/src/PackagesConfigConverter/ProjectConverter.cs index 2d0dc7b..de735d4 100644 --- a/src/PackagesConfigConverter/ProjectConverter.cs +++ b/src/PackagesConfigConverter/ProjectConverter.cs @@ -11,6 +11,7 @@ using System.Xml.Linq; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using Microsoft.Build.Graph; using Microsoft.Extensions.Logging; using NuGet.Commands; using NuGet.Common; @@ -87,9 +88,13 @@ public void ConvertRepository(CancellationToken cancellationToken) Log.LogInformation($" NuGet configuration file : \"{_converterSettings.NuGetConfigPath}\""); - foreach (string file in Directory.EnumerateFiles(_converterSettings.RepositoryRoot, "*.csproj", SearchOption.AllDirectories) - .TakeWhile(_ => !cancellationToken.IsCancellationRequested)) + foreach (string file in GetProjectFiles()) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + if (_converterSettings.Exclude != null && _converterSettings.Exclude.IsMatch(file)) { Log.LogDebug($" Excluding file \"{file}\""); @@ -142,6 +147,63 @@ private static ISettings GetNuGetSettings(ProjectConverterSettings converterSett return Settings.LoadDefaultSettings(converterSettings.RepositoryRoot, Settings.DefaultSettingsFileName, new XPlatMachineWideSetting()); } + private IEnumerable GetProjectFiles() + { + if (_converterSettings.Graph) + { + string entryProjectFile; + + string[] rootSolutionFiles = Directory.GetFiles(_converterSettings.RepositoryRoot, "*.sln", SearchOption.TopDirectoryOnly); + if (rootSolutionFiles.Length > 1) + { + Log.LogError("Found more than one sln file in the repo root. Cannot use graph discovery"); + return Array.Empty(); + } + + if (rootSolutionFiles.Length == 1) + { + entryProjectFile = rootSolutionFiles[0]; + } + else + { + string[] rootProjectFiles = Directory.GetFiles(_converterSettings.RepositoryRoot, "*.*proj", SearchOption.TopDirectoryOnly); + if (rootProjectFiles.Length > 1) + { + Log.LogError("Found more than one project file in the repo root. Cannot use graph discovery"); + return Array.Empty(); + } + + if (rootProjectFiles.Length == 1) + { + entryProjectFile = rootProjectFiles[0]; + } + else + { + Log.LogError("Did not find any solutions or projects in the repo root. Cannot use graph discovery"); + return Array.Empty(); + } + } + + // Using a set to avoid duplicates due to inner/outer builds. Sorting for a consistent ordering. + SortedSet projectFiles = new(StringComparer.OrdinalIgnoreCase); + ProjectGraph graph = new(entryProjectFile); + foreach (ProjectGraphNode node in graph.ProjectNodes) + { + string projectFile = node.ProjectInstance.FullPath; + if (projectFile.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase)) + { + projectFiles.Add(projectFile); + } + } + + return projectFiles; + } + else + { + return Directory.EnumerateFiles(_converterSettings.RepositoryRoot, "*.csproj", SearchOption.AllDirectories); + } + } + private ProjectItemElement AddPackageReference(ProjectItemGroupElement itemGroupElement, PackageUsage package, LockFileTargetLibrary lockFileTargetLibrary, bool isDevelopmentDependency) { LibraryIncludeFlags existingAssets = LibraryIncludeFlags.None; diff --git a/src/PackagesConfigConverter/ProjectConverterSettings.cs b/src/PackagesConfigConverter/ProjectConverterSettings.cs index da7d3f4..7c29f82 100644 --- a/src/PackagesConfigConverter/ProjectConverterSettings.cs +++ b/src/PackagesConfigConverter/ProjectConverterSettings.cs @@ -14,6 +14,8 @@ public sealed class ProjectConverterSettings public Regex Include { get; set; } + public bool Graph { get; set; } + public ILogger Log { get; set; } public string NuGetConfigPath { get; set; } = "(Default)";