Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,87 +1,37 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Fallout.Common;
using Fallout.Common.Execution;
using Fallout.Common.IO;
using Fallout.Common.Tooling;
using Fallout.Common.Utilities;
using Fallout.Common.Utilities.Collections;
using Spectre.Console;
using static Fallout.Common.Constants;
using static Fallout.Common.EnvironmentInfo;
using static Fallout.Common.Tooling.ProcessTasks;
using static Fallout.Common.Utilities.TemplateUtility;

namespace Fallout.Cli;

partial class Program
/// <summary>Writes the build scripts, configuration file and solution wiring when scaffolding a build.</summary>
public interface IBuildScaffolder
{
// ReSharper disable InconsistentNaming
void WriteBuildScripts(AbsolutePath scriptDirectory, AbsolutePath rootDirectory, AbsolutePath buildDirectory, string buildProjectName);
void WriteConfigurationFile(AbsolutePath rootDirectory, AbsolutePath solutionFile);
void UpdateSolutionFileContent(List<string> content, string buildProjectFileRelative, string buildProjectGuid, string buildProjectName);
string[] GetTemplate(string templateName);
}

/// <inheritdoc />
public sealed class BuildScaffolder : IBuildScaffolder
{
private const string PROJECT_KIND = "9A19103F-16F7-4668-BE54-9A1E7A4F7556";

// Transitional shim: cake (still a legacy handler) invokes setup directly. Removed once cake is
// converted; the dispatcher itself resolves SetupCommand from the registry, not this.
internal static int Setup(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
=> new Commands.SetupCommand(s_prompts).Execute(args, rootDirectory, buildScript);

// Residual after the :setup command moved to SetupCommand: these scaffolding helpers are shared
// with update (and UpdateSolutionFileContent is exercised directly by tests). They move into a
// scaffolding service in the #392 collapse PR.
internal static void UpdateSolutionFileContent(
List<string> content,
string buildProjectFileRelative,
string buildProjectGuid,
string buildProjectName)
{
if (content.Any(x => x.Contains(buildProjectFileRelative)))
return;

var globalIndex = content.IndexOf("Global");
Assert.True(globalIndex != -1, "Could not find a 'Global' section in solution file");

var projectConfigurationIndex = content.FindIndex(x => x.Contains("GlobalSection(ProjectConfigurationPlatforms)"));
if (projectConfigurationIndex == -1)
{
var solutionConfigurationIndex = content.FindIndex(x => x.Contains("GlobalSection(SolutionConfigurationPlatforms)"));
if (solutionConfigurationIndex == -1)
{
content.Insert(globalIndex + 1, "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution");
content.Insert(globalIndex + 2, "\t\tDebug|Any CPU = Debug|Any CPU");
content.Insert(globalIndex + 3, "\t\tRelease|Any CPU = Release|Any CPU");
content.Insert(globalIndex + 4, "\tEndGlobalSection");

solutionConfigurationIndex = globalIndex + 1;
}

var endGlobalSectionIndex = content.FindIndex(solutionConfigurationIndex, x => x.Contains("EndGlobalSection"));

content.Insert(endGlobalSectionIndex + 1, "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
content.Insert(endGlobalSectionIndex + 2, "\tEndGlobalSection");

projectConfigurationIndex = endGlobalSectionIndex + 1;
}

content.Insert(projectConfigurationIndex + 1, $"\t\t{{{buildProjectGuid}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU");
content.Insert(projectConfigurationIndex + 2, $"\t\t{{{buildProjectGuid}}}.Release|Any CPU.ActiveCfg = Release|Any CPU");

content.Insert(globalIndex,
$"Project(\"{{{PROJECT_KIND}}}\") = \"{buildProjectName}\", \"{buildProjectFileRelative}\", \"{{{buildProjectGuid}}}\"");
content.Insert(globalIndex + 1,
"EndProject");
}

internal static string[] GetTemplate(string templateName)
public string[] GetTemplate(string templateName)
{
return ResourceUtility.GetResourceAllLines<Program>($"templates.{templateName}");
// Anchored to the Fallout.Cli root namespace where the templates/* resources are embedded.
return ResourceUtility.GetResourceAllLines<BuildScaffolder>($"templates.{templateName}");
}


internal static void WriteBuildScripts(
public void WriteBuildScripts(
AbsolutePath scriptDirectory,
AbsolutePath rootDirectory,
AbsolutePath buildDirectory,
Expand Down Expand Up @@ -121,7 +71,7 @@ internal static void WriteBuildScripts(
tokens: GetDictionary(
new
{
FalloutCliVersion = typeof(Program).GetTypeInfo().Assembly.GetVersionText(),
FalloutCliVersion = typeof(BuildScaffolder).GetTypeInfo().Assembly.GetVersionText(),
})));
}

Expand All @@ -140,12 +90,55 @@ void MakeExecutable(AbsolutePath scriptFile)
}
}

internal static void WriteConfigurationFile(AbsolutePath rootDirectory, AbsolutePath solutionFile)
public void WriteConfigurationFile(AbsolutePath rootDirectory, AbsolutePath solutionFile)
{
var parametersFile = GetDefaultParametersFile(rootDirectory);
var dictionary = new Dictionary<string, string> { ["$schema"] = BuildSchemaFileName };
if (solutionFile != null)
dictionary["Solution"] = rootDirectory.GetUnixRelativePathTo(solutionFile).ToString();
parametersFile.WriteJson(dictionary, JsonExtensions.DefaultSerializerOptions);
}

public void UpdateSolutionFileContent(
List<string> content,
string buildProjectFileRelative,
string buildProjectGuid,
string buildProjectName)
{
if (content.Any(x => x.Contains(buildProjectFileRelative)))
return;

var globalIndex = content.IndexOf("Global");
Assert.True(globalIndex != -1, "Could not find a 'Global' section in solution file");

var projectConfigurationIndex = content.FindIndex(x => x.Contains("GlobalSection(ProjectConfigurationPlatforms)"));
if (projectConfigurationIndex == -1)
{
var solutionConfigurationIndex = content.FindIndex(x => x.Contains("GlobalSection(SolutionConfigurationPlatforms)"));
if (solutionConfigurationIndex == -1)
{
content.Insert(globalIndex + 1, "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution");
content.Insert(globalIndex + 2, "\t\tDebug|Any CPU = Debug|Any CPU");
content.Insert(globalIndex + 3, "\t\tRelease|Any CPU = Release|Any CPU");
content.Insert(globalIndex + 4, "\tEndGlobalSection");

solutionConfigurationIndex = globalIndex + 1;
}

var endGlobalSectionIndex = content.FindIndex(solutionConfigurationIndex, x => x.Contains("EndGlobalSection"));

content.Insert(endGlobalSectionIndex + 1, "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
content.Insert(endGlobalSectionIndex + 2, "\tEndGlobalSection");

projectConfigurationIndex = endGlobalSectionIndex + 1;
}

content.Insert(projectConfigurationIndex + 1, $"\t\t{{{buildProjectGuid}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU");
content.Insert(projectConfigurationIndex + 2, $"\t\t{{{buildProjectGuid}}}.Release|Any CPU.ActiveCfg = Release|Any CPU");

content.Insert(globalIndex,
$"Project(\"{{{PROJECT_KIND}}}\") = \"{buildProjectName}\", \"{buildProjectFileRelative}\", \"{{{buildProjectGuid}}}\"");
content.Insert(globalIndex + 1,
"EndProject");
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Fallout.Common;
using Fallout.Common.Execution;
using Fallout.Common.IO;
using Fallout.Solutions;
using Fallout.Common.Tooling;
using Fallout.Common.Utilities;
using Fallout.Cli.Rewriting.Cake;
Expand All @@ -15,19 +13,17 @@

namespace Fallout.Cli;

partial class Program
/// <summary>Best-effort syntax rewriting of Cake (<c>*.cake</c>) scripts into Fallout C#.</summary>
internal static class CakeConverter
{
public const string CAKE_FILE_PATTERN = "*.cake";
public const string FilePattern = "*.cake";

// Residual after the :cake-convert/:cake-clean commands moved to CakeConvertCommand/
// CakeCleanCommand: these .cake syntax-rewriting helpers stay on Program until the #392 collapse
// PR (GetCakeConvertedContent/GetCakePackages are exercised directly by tests).
internal static IEnumerable<AbsolutePath> GetCakeFiles()
public static IEnumerable<AbsolutePath> GetCakeFiles()
{
return (TryGetRootDirectoryFrom(WorkingDirectory) ?? WorkingDirectory).GlobFiles($"**/{CAKE_FILE_PATTERN}");
return (TryGetRootDirectoryFrom(WorkingDirectory) ?? WorkingDirectory).GlobFiles($"**/{FilePattern}");
}

internal static string GetCakeConvertedContent(string content)
public static string GetConvertedContent(string content)
{
var options = new CSharpParseOptions(LanguageVersion.Latest, DocumentationMode.None, SourceCodeKind.Script);
var syntaxTree = CSharpSyntaxTree.ParseText(content, options);
Expand All @@ -49,7 +45,7 @@ internal static string GetCakeConvertedContent(string content)
.ToFullString();
}

internal static IEnumerable<(string Type, string Id, string Version)> GetCakePackages(string content)
public static IEnumerable<(string Type, string Id, string Version)> GetPackages(string content)
{
IEnumerable<(string Type, string Id, string Version)> GetPackages(
string packageType,
Expand All @@ -66,8 +62,8 @@ internal static string GetCakeConvertedContent(string content)
}
}

return GetPackages(PACKAGE_TYPE_DOWNLOAD, @"#tool ""nuget:\?package=(?'packageId'[\w\d\.]+)(&version=(?'version'[\w\d\.]+))?S*""")
.Concat(GetPackages(PACKAGE_TYPE_REFERENCE, @"#addin ""nuget:\?package=(?'packageId'[\w\d\.]+)(&version=(?'version'[\w\d\.]+))?S*"""))
return GetPackages(PackageManager.DownloadType, @"#tool ""nuget:\?package=(?'packageId'[\w\d\.]+)(&version=(?'version'[\w\d\.]+))?S*""")
.Concat(GetPackages(PackageManager.ReferenceType, @"#addin ""nuget:\?package=(?'packageId'[\w\d\.]+)(&version=(?'version'[\w\d\.]+))?S*"""))
.Where(x => !x.Id.ContainsOrdinalIgnoreCase("Cake"));
}
}
18 changes: 18 additions & 0 deletions src/Fallout.Cli/CliConventions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Fallout.Common;
using Fallout.Common.Utilities;

namespace Fallout.Cli;

/// <summary>Small CLI-wide conventions shared by the entry point and commands.</summary>
internal static class CliConventions
{
/// <summary>The build-script file name for the current platform.</summary>
public static string CurrentBuildScriptName => EnvironmentInfo.IsWin ? "build.ps1" : "build.sh";
}

/// <summary>Prints the global-tool banner.</summary>
internal static class ToolBanner
{
public static void Print()
=> Host.Information($"NUKE Global Tool 🌐 {typeof(ToolBanner).Assembly.GetInformationalText()}");
}
21 changes: 14 additions & 7 deletions src/Fallout.Cli/Commands/AddPackageCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@ namespace Fallout.Cli.Commands;
/// </summary>
public sealed class AddPackageCommand : IFalloutCommand
{
private readonly IConfigurationReader _configuration;
private readonly IPackageManager _packages;

public AddPackageCommand(IConfigurationReader configuration, IPackageManager packages)
{
_configuration = configuration;
_packages = packages;
}

public string Name => "add-package";

public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
{
Program.PrintInfo();
ToolBanner.Print();
Logging.Configure();
Telemetry.AddPackage();
ProjectModelTasks.Initialize();
Expand All @@ -30,19 +39,17 @@ public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath build
NuGetPackageResolver.GetGlobalInstalledPackage(packageId, version: null, packagesConfigFile: null)?.Version.ToString())
.NotNull("packageVersion != null");

// GetConfiguration / AddOrReplacePackage / BUILD_PROJECT_FILE / PACKAGE_TYPE_* are shared
// helpers still on Program; they move into services in the final #392 collapse PR.
var configuration = Program.GetConfiguration(buildScript, evaluate: true);
var buildProjectFile = configuration[Program.BUILD_PROJECT_FILE];
var configuration = _configuration.Read(buildScript, evaluate: true);
var buildProjectFile = configuration[ConfigurationReader.BuildProjectFileKey];
Host.Information($"Installing {packageId}/{packageVersion} to {buildProjectFile} ...");
Program.AddOrReplacePackage(packageId, packageVersion, Program.PACKAGE_TYPE_DOWNLOAD, buildProjectFile);
_packages.AddOrReplacePackage(packageId, packageVersion, PackageManager.DownloadType, buildProjectFile);
DotNetTasks.DotNet($"restore {buildProjectFile}");

var installedPackage = NuGetPackageResolver.GetGlobalInstalledPackage(packageId, packageVersion, packagesConfigFile: null)
.NotNull("installedPackage != null");
var hasToolsDirectory = installedPackage.Directory.GlobDirectories("tools").Any();
if (!hasToolsDirectory)
Program.AddOrReplacePackage(packageId, packageVersion, Program.PACKAGE_TYPE_REFERENCE, buildProjectFile);
_packages.AddOrReplacePackage(packageId, packageVersion, PackageManager.ReferenceType, buildProjectFile);

Host.Information($"Done installing {packageId}/{packageVersion} to {buildProjectFile}");
return 0;
Expand Down
2 changes: 1 addition & 1 deletion src/Fallout.Cli/Commands/CakeCleanCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public sealed class CakeCleanCommand : IFalloutCommand

public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
{
var cakeFiles = Program.GetCakeFiles().ToList();
var cakeFiles = CakeConverter.GetCakeFiles().ToList();
Host.Information("Found .cake files:");
cakeFiles.ForEach(x => Host.Debug($" - {x}"));

Expand Down
36 changes: 23 additions & 13 deletions src/Fallout.Cli/Commands/CakeConvertCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,27 @@ namespace Fallout.Cli.Commands;
public sealed class CakeConvertCommand : IFalloutCommand
{
private readonly IConsolePrompts _prompts;
private readonly IConfigurationReader _configuration;
private readonly IPackageManager _packages;
private readonly SetupCommand _setup;

public CakeConvertCommand(IConsolePrompts prompts) => _prompts = prompts;
public CakeConvertCommand(
IConsolePrompts prompts,
IConfigurationReader configuration,
IPackageManager packages,
SetupCommand setup)
{
_prompts = prompts;
_configuration = configuration;
_packages = packages;
_setup = setup;
}

public string Name => "cake-convert";

// The .cake syntax-rewriting helpers (GetCakeFiles/GetCakeConvertedContent/GetCakePackages) and
// the shared GetConfiguration/AddOrReplacePackage helpers remain on Program until the #392
// collapse PR; GetCakeConvertedContent/GetCakePackages are also exercised directly by tests.
public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
{
Program.PrintInfo();
ToolBanner.Print();
Logging.Configure();
Telemetry.ConvertCake();
ProjectModelTasks.Initialize();
Expand All @@ -53,27 +63,27 @@ public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath build
if (buildScript == null &&
_prompts.PromptForConfirmation("Should a NUKE project be created for better results?"))
{
Program.Setup(args, rootDirectory: null, buildScript: null);
_setup.Execute(args, rootDirectory: null, buildScript: null);
}

var buildScriptFile = WorkingDirectory / Program.CurrentBuildScriptName;
var buildScriptFile = WorkingDirectory / CliConventions.CurrentBuildScriptName;
var buildProjectFile = buildScriptFile.Exists()
? Program.GetConfiguration(buildScriptFile, evaluate: true)
.GetValueOrDefault(Program.BUILD_PROJECT_FILE, defaultValue: null)
? _configuration.Read(buildScriptFile, evaluate: true)
.GetValueOrDefault(ConfigurationReader.BuildProjectFileKey, defaultValue: null)
: null;

foreach (var cakeFile in Program.GetCakeFiles())
foreach (var cakeFile in CakeConverter.GetCakeFiles())
{
var outputFile = cakeFile.Parent / cakeFile.NameWithoutExtension.Capitalize() + ".cs";
var content = Program.GetCakeConvertedContent(cakeFile.ReadAllText());
var content = CakeConverter.GetConvertedContent(cakeFile.ReadAllText());
outputFile.WriteAllText(content);
}

if (buildProjectFile != null)
{
var packages = Program.GetCakeFiles().SelectMany(x => Program.GetCakePackages(x.ReadAllText()));
var packages = CakeConverter.GetCakeFiles().SelectMany(x => CakeConverter.GetPackages(x.ReadAllText()));
foreach (var package in packages)
Program.AddOrReplacePackage(package.Id, package.Version, package.Type, buildProjectFile);
_packages.AddOrReplacePackage(package.Id, package.Version, package.Type, buildProjectFile);
}

return 0;
Expand Down
27 changes: 0 additions & 27 deletions src/Fallout.Cli/Commands/DelegateCommand.cs

This file was deleted.

Loading