aboutsummaryrefslogtreecommitdiff
path: root/src/Commands
diff options
context:
space:
mode:
Diffstat (limited to 'src/Commands')
-rw-r--r--src/Commands/BaseCommand.cs116
-rw-r--r--src/Commands/BuildCommand.cs66
-rw-r--r--src/Commands/CleanCommand.cs31
-rw-r--r--src/Commands/PublishCommand.cs80
-rw-r--r--src/Commands/TestCommand.cs31
-rw-r--r--src/Commands/TestDisplayCommand.cs22
-rw-r--r--src/Commands/UpdateCommand.cs25
7 files changed, 371 insertions, 0 deletions
diff --git a/src/Commands/BaseCommand.cs b/src/Commands/BaseCommand.cs
new file mode 100644
index 0000000..6362c22
--- /dev/null
+++ b/src/Commands/BaseCommand.cs
@@ -0,0 +1,116 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Typin;
+using Typin.Console;
+using Typin.Attributes;
+
+using VNLib.Tools.Build.Executor.Model;
+using VNLib.Tools.Build.Executor.Constants;
+using static VNLib.Tools.Build.Executor.Constants.Config;
+
+namespace VNLib.Tools.Build.Executor.Commands
+{
+ public abstract class BaseCommand(BuildPipeline pipeline, ConfigManager bm) : ICommand
+ {
+ [CommandOption("verbose", 'v', Description = "Prints verbose output")]
+ public bool Verbose { get; set; }
+
+ [CommandOption("force", 'f', Description = "Forces the operation even if steps are required")]
+ public bool Force { get; set; }
+
+ [CommandOption("modules", 'm', Description = "Only use the specified modules, comma separated list")]
+ public string? Modules { get; set; }
+
+ [CommandOption("exclude", 'x', Description = "Ignores the specified modules, comma separated list")]
+ public string? Exclude { get; set; }
+
+ [CommandOption("confirm", 'c', Description = "Wait for user input before continuing")]
+ public bool Confirm { get; set; }
+
+ //Allow users to specify build directory
+ [CommandOption("build-dir", 'B', Description = "Sets the base build directory to execute operations in")]
+ public string BuildDir { get; set; } = Directory.GetCurrentDirectory();
+
+ [CommandOption("max-logs", 'L', Description = "Sets the maximum number of logs to keep")]
+ public int MaxLogs { get; set; } = 50;
+
+ public IDirectoryIndex BuildIndex { get; private set; } = default!;
+
+ public BuildConfig Config { get; private set; } = default!;
+
+
+ /*
+ * Base exec command does basic init and cleanup on startup
+ */
+
+ public virtual async ValueTask ExecuteAsync(IConsole console)
+ {
+ try
+ {
+ CancellationToken ct = console.GetCancellationToken();
+
+ //Init build index
+ BuildIndex = GetIndex();
+ Config = await bm.GetOrCreateConfig(BuildIndex, false);
+
+ //Cleanup log files on init
+ TrimLogs(BuildIndex, MaxLogs);
+
+ string[] modules = Modules?.Split(',') ?? [];
+ string[] exclude = Exclude?.Split(',') ?? [];
+
+ //Always load the pipeline
+ await pipeline.LoadAsync(Config, modules, exclude, Feeds);
+
+ if (Confirm)
+ {
+ console.Output.WriteLine("---- Pipeline loaded. Press any key to continue ----");
+ await console.Input.ReadLineAsync(ct);
+
+ await Task.Delay(100, ct);
+ ct.ThrowIfCancellationRequested();
+ }
+
+ //Exec steps then exit
+ await ExecStepsAsync(console);
+ }
+ catch (OperationCanceledException)
+ {
+ console.WithForegroundColor(ConsoleColor.Red, static o => o.Output.WriteLine("Operation cancelled"));
+ }
+ catch(BuildStepFailedException be)
+ {
+ console.WithForegroundColor(ConsoleColor.Red, o => o.Output.WriteLine("FATAL: Build step failed on module {0}, msg -> {1}", be.ArtifactName, be.Message));
+ }
+ }
+
+ public abstract ValueTask ExecStepsAsync(IConsole console);
+
+ public abstract IFeedManager[] Feeds { get; }
+
+ private Dirs GetIndex() => new()
+ {
+ BaseDir = new(BuildDir),
+ BuildDir = BuildDirs.GetOrCreateDir(BUILD_DIR_NAME),
+ LogDir = BuildDirs.GetOrCreateDir(LOG_DIR_NAME),
+ ScratchDir = BuildDirs.GetOrCreateDir(SCRATCH_DIR),
+ SumDir = BuildDirs.GetOrCreateDir(SUM_DIR),
+ OutputDir = BuildDirs.GetOrCreateDir(OUTPUT_DIR)
+ };
+
+#nullable disable
+ protected sealed class Dirs : IDirectoryIndex
+ {
+ public DirectoryInfo BaseDir { get; set; }
+ public DirectoryInfo BuildDir { get; set; }
+ public DirectoryInfo LogDir { get; set; }
+ public DirectoryInfo ScratchDir { get; set; }
+ public DirectoryInfo SumDir { get; set; }
+ public DirectoryInfo OutputDir { get; set; }
+ }
+#nullable enable
+ }
+} \ No newline at end of file
diff --git a/src/Commands/BuildCommand.cs b/src/Commands/BuildCommand.cs
new file mode 100644
index 0000000..c1d9828
--- /dev/null
+++ b/src/Commands/BuildCommand.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Typin.Console;
+using Typin.Attributes;
+
+using VNLib.Tools.Build.Executor.Model;
+using VNLib.Tools.Build.Executor.Constants;
+
+namespace VNLib.Tools.Build.Executor.Commands
+{
+
+ [Command("build", Description = "Executes a build operation in pipeline")]
+ public class BuildCommand(BuildPipeline pipeline, ConfigManager bm) : BaseCommand(pipeline, bm)
+ {
+
+ [CommandOption("no-delay", 'S', Description = "Skips any built-in delay/wait")]
+ public bool SkipDelay { get; set; } = false;
+
+ public override async ValueTask ExecStepsAsync(IConsole console)
+ {
+ CancellationToken cancellation = console.GetCancellationToken();
+
+ console.Output.WriteLine("Starting build pipeline. Checking for source code changes");
+
+ if (Force)
+ {
+ console.WithForegroundColor(ConsoleColor.Yellow, static o => o.Output.WriteLine("Forcing build step"));
+ }
+
+ //Check for source code changes
+ bool changed = await pipeline.CheckForChangesAsync();
+
+ //continue build
+ if (!Force && !changed)
+ {
+ console.WithForegroundColor(ConsoleColor.Green, static o => o.Output.WriteLine("No source code changes detected. Skipping build step"));
+ return;
+ }
+
+ if (Confirm)
+ {
+ console.Output.WriteLine("Press any key to continue...");
+ await console.Input.ReadLineAsync(cancellation);
+ cancellation.ThrowIfCancellationRequested();
+ }
+ else if(!SkipDelay)
+ {
+ //wait for 10 seconds
+ for (int i = 10; i > 0; i--)
+ {
+ string seconds = i > 1 ? "seconds" : "second";
+ console.Output.WriteLine($"Starting build step in {i} {seconds}");
+ await Task.Delay(1000, cancellation);
+ }
+ }
+
+ await pipeline.DoStepBuild(Force);
+
+ console.WithForegroundColor(ConsoleColor.Green, static o => o.Output.WriteLine("Build completed successfully"));
+ }
+
+ public override IFeedManager[] Feeds => [];
+ }
+} \ No newline at end of file
diff --git a/src/Commands/CleanCommand.cs b/src/Commands/CleanCommand.cs
new file mode 100644
index 0000000..6570b31
--- /dev/null
+++ b/src/Commands/CleanCommand.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Threading.Tasks;
+
+using Typin.Console;
+using Typin.Attributes;
+
+using VNLib.Tools.Build.Executor.Model;
+using VNLib.Tools.Build.Executor.Constants;
+
+namespace VNLib.Tools.Build.Executor.Commands
+{
+
+ [Command("clean", Description = "Cleans the build pipeline")]
+ public sealed class CleanCommand(BuildPipeline pipeline, ConfigManager cfg) : BaseCommand(pipeline, cfg)
+ {
+ public override async ValueTask ExecStepsAsync(IConsole console)
+ {
+ console.Output.WriteLine("Begining clean step");
+
+ await pipeline.DoStepCleanAsync();
+
+ //Cleanup the sum dir and scratch dir
+ Config.Index.SumDir.Delete(true);
+ Config.Index.ScratchDir.Delete(true);
+
+ console.WithForegroundColor(ConsoleColor.Green, o => o.Output.WriteLine("Pipeline cleaned"));
+ }
+
+ public override IFeedManager[] Feeds => [];
+ }
+} \ No newline at end of file
diff --git a/src/Commands/PublishCommand.cs b/src/Commands/PublishCommand.cs
new file mode 100644
index 0000000..4179f86
--- /dev/null
+++ b/src/Commands/PublishCommand.cs
@@ -0,0 +1,80 @@
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Typin.Console;
+using Typin.Attributes;
+
+using VNLib.Tools.Build.Executor.Model;
+using VNLib.Tools.Build.Executor.Constants;
+
+namespace VNLib.Tools.Build.Executor.Commands
+{
+ [Command("publish", Description = "Runs publishig build steps on a completed build")]
+ public sealed class PublishCommand(BuildPipeline pipeline, ConfigManager bm) : BaseCommand(pipeline, bm)
+ {
+ [CommandOption("upload-path", 'p', Description = "The path to upload the build artifacts")]
+ public string? UploadPath { get; set; }
+
+ [CommandOption("sign", 's', Description = "Enables gpg signing of build artifacts")]
+ public bool Sign { get; set; } = false;
+
+ [CommandOption("gpg-key", 'k', Description = "Optional key to use when signing, otherwise uses the GPG default signing key")]
+ public string? GpgKey { get; set; }
+
+ [CommandOption("sleet-path", 'F', Description = "Specifies the Sleet feed index path")]
+ public string? SleetPath { get; set; }
+
+ [CommandOption("dry-run", 'd', Description = "Executes all publish steps without pushing the changes to the remote server")]
+ public bool DryRun { get; set; }
+
+ [CommandOption("output", 'o', Description = "Specifies the output directory for the published modules")]
+ public string? CustomOutDir { get; set; }
+
+ public override async ValueTask ExecStepsAsync(IConsole console)
+ {
+ //Specify custom output dir
+ (base.Config.Index as Dirs)!.OutputDir = BuildDirs.GetOrCreateDir(Constants.Config.OUTPUT_DIR, CustomOutDir);
+
+ IUploadManager? uploads = MinioUploadManager.Create(UploadPath);
+ IFeedManager? feed = Feeds.FirstOrDefault();
+
+ //Optional gpg signer for signing published artifacts
+ BuildPublisher pub = new(Config, new GpgSigner(Sign, GpgKey));
+
+ console.WithForegroundColor(ConsoleColor.DarkGreen, static o => o.Output.WriteLine("Publishing modules"));
+
+ //Run publish steps
+ await pipeline.OnPublishingAsync().ConfigureAwait(false);
+
+ console.WithForegroundColor(ConsoleColor.DarkGreen, static o => o.Output.WriteLine("Preparing module output for upload"));
+
+ //Prepare the output
+ await pipeline.PrepareOutputAsync(pub).ConfigureAwait(false);
+
+ if(uploads is null)
+ {
+ console.WithForegroundColor(ConsoleColor.DarkYellow, static o => o.Output.WriteLine("No upload path specified. Skipping upload"));
+ console.WithForegroundColor(ConsoleColor.Green, static o => o.Output.WriteLine("Upload build complete"));
+ return;
+ }
+
+ //Run upload
+ await pipeline.ManualUpload(pub, uploads).ConfigureAwait(false);
+
+ //Publish feeds
+ if (feed is not null)
+ {
+ console.WithForegroundColor(ConsoleColor.DarkGreen, static o => o.Output.WriteLine("Uploading feeds..."));
+
+ //Exec feed upload
+ await uploads.UploadDirectoryAsync(feed.FeedOutputDir);
+ }
+
+ console.WithForegroundColor(ConsoleColor.Green, static o => o.Output.WriteLine("Upload build complete"));
+ }
+
+ public override IFeedManager[] Feeds => SleetPath is null ? [] : [SleetFeedManager.GetSleetFeed(SleetPath)];
+ }
+} \ No newline at end of file
diff --git a/src/Commands/TestCommand.cs b/src/Commands/TestCommand.cs
new file mode 100644
index 0000000..d64fe64
--- /dev/null
+++ b/src/Commands/TestCommand.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Threading.Tasks;
+
+using Typin.Console;
+using Typin.Attributes;
+
+using VNLib.Tools.Build.Executor.Model;
+using VNLib.Tools.Build.Executor.Constants;
+
+namespace VNLib.Tools.Build.Executor.Commands
+{
+ [Command("test", Description = "Executes tests steps within the pipline for all loaded modules")]
+ public sealed class TestCommand(BuildPipeline pipeline, ConfigManager cfg) : BaseCommand(pipeline, cfg)
+ {
+ private readonly BuildPipeline _pipeline = pipeline;
+
+ [CommandOption("--no-fail", Description = "Exit testing on the first test failure")]
+ public bool FailOnTestFail { get; set; } = true;
+
+ public override async ValueTask ExecStepsAsync(IConsole console)
+ {
+ console.Output.WriteLine("Begining test step");
+
+ await _pipeline.ExecuteTestsAsync(FailOnTestFail);
+
+ console.WithForegroundColor(ConsoleColor.Green, o => o.Output.WriteLine("Pipeline tests compled"));
+ }
+
+ public override IFeedManager[] Feeds => [];
+ }
+} \ No newline at end of file
diff --git a/src/Commands/TestDisplayCommand.cs b/src/Commands/TestDisplayCommand.cs
new file mode 100644
index 0000000..c716ada
--- /dev/null
+++ b/src/Commands/TestDisplayCommand.cs
@@ -0,0 +1,22 @@
+using System.Threading.Tasks;
+
+using Typin.Console;
+using Typin.Attributes;
+
+using VNLib.Tools.Build.Executor.Model;
+using VNLib.Tools.Build.Executor.Constants;
+
+namespace VNLib.Tools.Build.Executor.Commands
+{
+ [Command("display", Description = "Test command for debugging")]
+ public sealed class TestDisplayCommand(BuildPipeline pipeline, ConfigManager bm) : BaseCommand(pipeline, bm)
+ {
+ public override IFeedManager[] Feeds => [];
+
+ public override async ValueTask ExecStepsAsync(IConsole console)
+ {
+ console.Output.WriteLine("Press any key to exit...");
+ await console.Input.ReadLineAsync(console.GetCancellationToken());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Commands/UpdateCommand.cs b/src/Commands/UpdateCommand.cs
new file mode 100644
index 0000000..e3bdf9c
--- /dev/null
+++ b/src/Commands/UpdateCommand.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Threading.Tasks;
+
+using Typin.Console;
+using Typin.Attributes;
+
+using VNLib.Tools.Build.Executor.Model;
+using VNLib.Tools.Build.Executor.Constants;
+
+namespace VNLib.Tools.Build.Executor.Commands
+{
+ [Command("update", Description = "Runs the build steps for updating application soure code")]
+ public sealed class UpdateCommand(BuildPipeline pipeline, ConfigManager cfg) : BaseCommand(pipeline, cfg)
+ {
+ public override async ValueTask ExecStepsAsync(IConsole console)
+ {
+ //Run the update step
+ await pipeline.DoStepUpdateSource();
+
+ console.WithForegroundColor(ConsoleColor.Green, o => o.Output.WriteLine("Source update complete"));
+ }
+
+ public override IFeedManager[] Feeds => [];
+ }
+} \ No newline at end of file