aboutsummaryrefslogtreecommitdiff
path: root/src/BuildPipeline.cs
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-05-07 17:01:22 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-05-07 17:01:22 -0400
commit79d824cfb0e0cc9ff4fab0e0c546a83c0edaae1c (patch)
treed69e962a15493b2e36882810a2cc43d5a0de1a0b /src/BuildPipeline.cs
parentb3015399591bd81b8519f0efa2ec177163f7d04a (diff)
initial commit
Diffstat (limited to 'src/BuildPipeline.cs')
-rw-r--r--src/BuildPipeline.cs305
1 files changed, 305 insertions, 0 deletions
diff --git a/src/BuildPipeline.cs b/src/BuildPipeline.cs
new file mode 100644
index 0000000..2435566
--- /dev/null
+++ b/src/BuildPipeline.cs
@@ -0,0 +1,305 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using Serilog;
+using Serilog.Core;
+
+using VNLib.Tools.Build.Executor.Model;
+using VNLib.Tools.Build.Executor.Modules;
+using VNLib.Tools.Build.Executor.Extensions;
+using VNLib.Tools.Build.Executor.Constants;
+
+namespace VNLib.Tools.Build.Executor
+{
+
+ public sealed class BuildPipeline(Logger Log) : IDisposable
+ {
+ private readonly List<ModuleBase> _allModules = new();
+ private readonly List<ModuleBase> _selected = new();
+ private readonly LinkedList<ModuleBase> _outdatedModules = new();
+ private readonly LinkedList<IProject> _modifiedProjects = new();
+ private readonly TaskfileVars _taskVars = new();
+
+ /// <summary>
+ /// Loads a modules within the working directory
+ /// </summary>
+ /// <returns>A task that completes when all modules and child projects are loaded</returns>
+ public async Task LoadAsync(BuildConfig config, string[] only, string[] exclude, IFeedManager[] feeds)
+ {
+ //Init task variables
+ SetTaskVariables(config.Index, feeds);
+
+ //Capture all modules within pwd
+ Log.Information("Discovering modules in {pwd}", config.Index.BaseDir.FullName);
+
+ //Search for .git repos
+ DirectoryInfo[] moduleDirs = config.Index.BaseDir.EnumerateDirectories(".git", SearchOption.AllDirectories)
+ .Select(static s => s.Parent!)
+ .ToArray();
+
+ //Add modules
+ foreach(DirectoryInfo dir in moduleDirs)
+ {
+ _allModules.Add(new GitCodeModule(config, dir));
+ }
+
+ Log.Information("Found {c} modules, loading modules...", moduleDirs.Length);
+
+ //Load all modules async and give them each a copy of our local task variables
+ await _allModules.RunAllAsync(p => p.LoadAsync(_taskVars.Clone()));
+
+ //Only include desired modules
+ if (only.Length > 0)
+ {
+ Log.Information("Only including modules {mods}", only);
+
+ ModuleBase[] onlyMods = _allModules.Where(m => only.Contains(m.ModuleName, StringComparer.OrdinalIgnoreCase)).ToArray();
+ _selected.AddRange(onlyMods);
+ }
+ //Exclude given modules
+ else if(exclude.Length > 0)
+ {
+ Log.Information("Excluding modules {mods}", exclude);
+
+ ModuleBase[] excludeMods = _allModules.Where(m => exclude.Contains(m.ModuleName, StringComparer.OrdinalIgnoreCase)).ToArray();
+ _selected.AddRange(_allModules.Except(excludeMods));
+ }
+ else
+ {
+ //Just all all modules to the list
+ _selected.AddRange(_allModules);
+ }
+
+ Log.Information("The following modules will be processed\n{mods}", _selected.Select(m => m.ModuleName));
+ }
+
+ private void SetTaskVariables(IDirectoryIndex dirIndex, IFeedManager[] feeds)
+ {
+ //Configure variables
+ _taskVars.Set("BUILD_DIR", dirIndex.BuildDir.FullName);
+ _taskVars.Set("SCRATCH_DIR", dirIndex.ScratchDir.FullName);
+ _taskVars.Set("UNIX_MS", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString());
+ _taskVars.Set("DATE", DateTimeOffset.Now.ToString("d"));
+
+ //Add all feed manager to task variables
+ Array.ForEach(feeds, f => f.AddVariables(_taskVars));
+ }
+
+ /// <summary>
+ /// Synchronizes all modules with their respective remote repositories
+ /// </summary>
+ /// <returns></returns>
+ public async Task DoStepUpdateSource()
+ {
+ //Clear outdated list before syncing sources
+ _outdatedModules.Clear();
+ _modifiedProjects.Clear();
+
+ //Must sync source serially to prevent git errors
+ foreach (ModuleBase module in _selected)
+ {
+ //Sync source
+ await module.DoStepSyncSource();
+ }
+ }
+
+ /// <summary>
+ /// Prepares the build pipeline for building, finds for changes and determines dependencies
+ /// then prepares modules for building
+ /// </summary>
+ /// <returns></returns>
+ public async Task<bool> CheckForChangesAsync()
+ {
+ //Clear outdated list before syncing sources
+ _outdatedModules.Clear();
+ _modifiedProjects.Clear();
+
+ //Conccurrently search for changes in all modules
+ await _selected.RunAllAsync(async m =>
+ {
+ if (await m.CheckForChangesAsync())
+ {
+ _outdatedModules.AddLast(m);
+ Log.Information("Module {m} MODIFIED. Queued for rebuild", m.ModuleName);
+ }
+ });
+
+ //if one or more modules have been modified, we need to determine dependencies
+ if (_outdatedModules.Count > 0)
+ {
+ //Get the initial list of projects that will be rebuilt
+ string[] outDatedProjects = _outdatedModules.SelectMany(static m => m.Projects.Where(static p => !p.UpToDate).Select(static p => p.ProjectFile.Name)).ToArray();
+
+ do
+ {
+
+ /*
+ * Select only up-to-date modules
+ * that have external project references to outdated
+ * projects
+ */
+ ModuleBase[] dependants = _selected.Where(m => !_outdatedModules.Contains(m))
+ .Where(
+ m => m.GetExternalDependencies()
+ .Where(externProj => outDatedProjects.Contains(externProj))
+ .Any())
+ .ToArray();
+
+ //If there are no more dependants, exit loop
+ if (dependants.Length == 0)
+ {
+ break;
+ }
+
+ //Add modules to oudated list
+ for (int i = 0; i < dependants.Length; i++)
+ {
+ Log.Information("Module {mod} OUTDATED because it depends on out-of-date modules", dependants[i].ModuleName);
+ _outdatedModules.AddLast(dependants[i]);
+ }
+
+ //update outdated projects list to include projects from the newly outdated modules
+ outDatedProjects = dependants.SelectMany(static p => p.GetExternalDependencies()).ToArray();
+ }
+ while (true);
+ }
+
+ Log.Information("{c} modules detected source code changes", _outdatedModules.Count);
+ return _outdatedModules.Count > 0;
+ }
+
+ public async Task DoStepBuild(bool force)
+ {
+ //Rebuild all modules
+ if (force)
+ {
+ //rebuild all selected modules
+ foreach (ModuleBase mod in _selected)
+ {
+ //Run each module independently
+ await BuildSingleModule(mod, Log);
+ }
+ }
+ else
+ {
+ if (_outdatedModules.Count == 0)
+ {
+ Log.Information("No modules detected changes");
+ }
+
+ //Only rebuild modified modules
+ foreach (ModuleBase mod in _outdatedModules)
+ {
+ //Run each module independently
+ await BuildSingleModule(mod, Log);
+ }
+ }
+ }
+
+ static async Task BuildSingleModule(IBuildable module, ILogger log)
+ {
+ log.Information("Building module {m}", (module as ModuleBase)!.ModuleName);
+
+ try
+ {
+ //Build module
+ await module.DoStepBuild();
+ }
+ catch
+ {
+ //failure
+ await module.DoStepPostBuild(false);
+ throw;
+ }
+
+ //Completed successfully, await the result of post-build
+ await module.DoStepPostBuild(true);
+ }
+
+ public async Task OnPublishingAsync()
+ {
+ /*
+ * Exec publish step on modules in order incase they
+ * need to access synchronous resources
+ */
+
+ foreach(ModuleBase module in _selected)
+ {
+ await module.DoStepPublish();
+ }
+ }
+
+ public async Task PrepareOutputAsync(BuildPublisher publisher)
+ {
+ Log.Information("Preparing pipline output");
+
+ if(publisher.SignEnabled)
+ {
+ //Sign all modules synchronously so gpg-agent doesn't get overloaded
+ foreach (IModuleData module in _selected)
+ {
+ //Sign module
+ await publisher.PrepareModuleOutput(module);
+ }
+ }
+ else
+ {
+ await _selected.RunAllAsync(publisher.PrepareModuleOutput);
+ }
+ }
+
+ /// <summary>
+ /// Executes test commands for all loaded modules
+ /// </summary>
+ /// <returns></returns>
+ public async Task ExecuteTestsAsync(bool failOnError)
+ {
+ foreach (ModuleBase module in _selected)
+ {
+ await module.DoRunTests(failOnError);
+ }
+ }
+
+ /// <summary>
+ /// Performs a manual upload step
+ /// </summary>
+ /// <returns></returns>
+ public async Task ManualUpload(BuildPublisher publisher, IUploadManager uploads)
+ {
+ //Upload module output
+ foreach (IModuleData module in _selected)
+ {
+ //Upload module
+ await publisher.UploadModuleOutput(uploads, module);
+ }
+ }
+
+ /// <summary>
+ /// Cleans all modules and child projects
+ /// </summary>
+ /// <returns>A task that resolves when all child projects have been cleaned</returns>
+ public async Task DoStepCleanAsync()
+ {
+ //Clean synchronously
+ foreach (IArtifact module in _selected)
+ {
+ await module.CleanAsync();
+ }
+ }
+
+ public void Dispose()
+ {
+ foreach (IArtifact module in _allModules)
+ {
+ module.Dispose();
+ }
+
+ //Cleanup internals
+ _outdatedModules.Clear();
+ }
+
+ }
+} \ No newline at end of file