|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.PhysicalFileSystem;
using Microsoft.TemplateEngine.Cli.PostActionProcessors;
namespace Microsoft.DotNet.Cli.Commands.New.PostActions;
internal class DotnetSlnPostActionProcessor(Func<string, IReadOnlyList<string>, string?, bool?, bool>? addProjToSolutionCallback = null) : PostActionProcessorBase
{
private readonly Func<string, IReadOnlyList<string>, string?, bool?, bool> _addProjToSolutionCallback = addProjToSolutionCallback ?? DotnetCommandCallbacks.AddProjectsToSolution;
public override Guid Id => ActionProcessorId;
internal static Guid ActionProcessorId { get; } = new Guid("D396686C-DE0E-4DE6-906D-291CD29FC5DE");
internal static IReadOnlyList<string> FindSolutionFilesAtOrAbovePath(IPhysicalFileSystem fileSystem, string outputBasePath)
{
// Search for .sln and .slnx at each directory level before walking up.
// This prevents finding an unrelated .sln in a distant parent directory when
// the intended .slnx exists in the output directory (or a closer ancestor).
string? directory = fileSystem.DirectoryExists(outputBasePath) ? outputBasePath : Path.GetDirectoryName(outputBasePath);
while (directory != null)
{
List<string> slnFiles = fileSystem.EnumerateFileSystemEntries(directory, "*.sln", SearchOption.TopDirectoryOnly)
.Where(f => f.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
.ToList();
if (slnFiles.Count > 0)
{
return slnFiles;
}
List<string> slnxFiles = fileSystem.EnumerateFileSystemEntries(directory, "*.slnx", SearchOption.TopDirectoryOnly)
.Where(f => f.EndsWith(".slnx", StringComparison.OrdinalIgnoreCase))
.ToList();
if (slnxFiles.Count > 0)
{
return slnxFiles;
}
directory = Path.GetPathRoot(directory) != directory ? Directory.GetParent(directory)?.FullName : null;
}
return [];
}
// The project files to add are a subset of the primary outputs, specifically the primary outputs indicated by the primaryOutputIndexes post action argument (semicolon separated)
// If any indexes are out of range or non-numeric, this method returns false and projectFiles is set to null.
internal static bool TryGetProjectFilesToAdd(IPostAction actionConfig, ICreationResult templateCreationResult, string outputBasePath, [NotNullWhen(true)] out IReadOnlyList<string> projectFiles)
{
List<string> filesToAdd = [];
projectFiles = [];
if (actionConfig.Args != null && actionConfig.Args.TryGetValue("primaryOutputIndexes", out string? projectIndexes))
{
foreach (string indexString in projectIndexes.Split([';'], StringSplitOptions.RemoveEmptyEntries))
{
if (int.TryParse(indexString.Trim(), out int index))
{
if (templateCreationResult.PrimaryOutputs.Count <= index || index < 0)
{
return false;
}
filesToAdd.Add(Path.GetFullPath(templateCreationResult.PrimaryOutputs[index].Path, outputBasePath));
}
else
{
return false;
}
}
projectFiles = filesToAdd;
return true;
}
else
{
foreach (string pathString in templateCreationResult.PrimaryOutputs.Select(x => x.Path))
{
filesToAdd.Add(Path.GetFullPath(pathString, outputBasePath));
}
projectFiles = filesToAdd;
return true;
}
}
protected override bool ProcessInternal(IEngineEnvironmentSettings environment, IPostAction action, ICreationEffects creationEffects, ICreationResult templateCreationResult, string outputBasePath)
{
IReadOnlyList<string> nearestSlnFilesFound = FindSolutionFilesAtOrAbovePath(environment.Host.FileSystem, outputBasePath);
if (nearestSlnFilesFound.Count != 1)
{
Reporter.Error.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Error_NoSolutionFile);
return false;
}
IReadOnlyList<string> projectFiles = GetConfiguredFiles(action.Args, creationEffects, "projectFiles", outputBasePath, (path) => Path.GetExtension(path).EndsWith("proj", StringComparison.OrdinalIgnoreCase));
if (!projectFiles.Any())
{
//If the author didn't opt in to the new behavior by specifying "projectFiles", use the old behavior
if (!TryGetProjectFilesToAdd(action, templateCreationResult, outputBasePath, out projectFiles))
{
Reporter.Error.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Error_NoProjectsToAdd);
return false;
}
}
if (projectFiles.Count == 0)
{
Reporter.Error.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Error_NoProjectsToAdd);
return false;
}
string? solutionFolder = GetSolutionFolder(action);
bool? inRoot = GetInRoot(action);
if (!string.IsNullOrWhiteSpace(solutionFolder) && inRoot is true)
{
Reporter.Error.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Error_BothInRootAndSolutionFolderSpecified);
return false;
}
if (inRoot is true)
{
Reporter.Output.WriteLine(string.Format(CliCommandStrings.PostAction_AddProjToSln_InRoot_Running, string.Join(" ", projectFiles), nearestSlnFilesFound[0]));
}
else
{
Reporter.Output.WriteLine(string.Format(CliCommandStrings.PostAction_AddProjToSln_Running, string.Join(" ", projectFiles), nearestSlnFilesFound[0], solutionFolder));
}
return AddProjectsToSolution(nearestSlnFilesFound[0], projectFiles, solutionFolder, inRoot);
}
private bool AddProjectsToSolution(string solutionPath, IReadOnlyList<string> projectsToAdd, string? solutionFolder, bool? inRoot)
{
try
{
bool succeeded = _addProjToSolutionCallback(solutionPath, projectsToAdd, solutionFolder, inRoot);
if (!succeeded)
{
Reporter.Error.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Failed_NoReason);
}
else
{
Reporter.Output.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Succeeded);
}
return succeeded;
}
catch (Exception e)
{
Reporter.Error.WriteLine(string.Format(CliCommandStrings.PostAction_AddProjToSln_Failed, e.Message));
return false;
}
}
private static string? GetSolutionFolder(IPostAction actionConfig)
{
if (actionConfig.Args != null && actionConfig.Args.TryGetValue("solutionFolder", out string? solutionFolder))
{
return solutionFolder;
}
return null;
}
private static bool? GetInRoot(IPostAction actionConfig)
{
if (actionConfig.Args != null && actionConfig.Args.TryGetValue("inRoot", out string? inRoot))
{
if (bool.TryParse(inRoot, out bool result))
{
return result;
}
}
return null;
}
}
|