using System; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Collections.Generic; using LibGit2Sharp; using VNLib.Tools.Build.Executor.Model; using VNLib.Tools.Build.Executor.Constants; using VNLib.Tools.Build.Executor.Extensions; using static VNLib.Tools.Build.Executor.Constants.Config; namespace VNLib.Tools.Build.Executor.Modules { /// /// Represents a base class for all modules to inherit from /// internal abstract class ModuleBase : IArtifact, IBuildable, IModuleData, ITaskfileScope { protected readonly BuildConfig Config; protected readonly TaskFile TaskFile; /// public TaskfileVars TaskVars { get; private set; } /// public DirectoryInfo WorkingDir { get; } /// public abstract string ModuleName { get; } /// public ICollection Projects { get; } = new LinkedList(); /// public IModuleFileManager FileManager { get; } /// public string? TaskfileName { get; protected set; } /// /// The git repository of the module /// public Repository Repository { get; } /// /// The project explorer for the module /// public abstract IProjectExplorer ModuleExplorer { get; } public ModuleBase(BuildConfig config, DirectoryInfo root) { WorkingDir = root; Config = config; //Default to module taskfile name TaskfileName = config.ModuleTaskFileName; //Init repo for root working dir Repository = new(root.FullName); FileManager = new ModuleFileManager(config, this); TaskVars = null!; TaskFile = new(config.TaskExeName, () => ModuleName); } /// public virtual async Task LoadAsync(TaskfileVars vars) { if(Repository?.Head?.Tip?.Sha is null) { throw new BuildStepFailedException("This repository does not have any commit history. Cannot continue"); } //Store paraent vars TaskVars = vars; string moduleSemVer = this.GetModuleCiVersion(Config.DefaultCiVersion, Config.SemverStyle); //Build module local environment variables TaskVars.Set("MODULE_NAME", ModuleName); TaskVars.Set("OUTPUT_DIR", FileManager.OutputDir); TaskVars.Set("MODULE_DIR", WorkingDir.FullName); //Store current head-sha before update step TaskVars.Set("HEAD_SHA", Repository.Head.Tip.Sha); TaskVars.Set("BRANCH_NAME", Repository.Head.FriendlyName); TaskVars.Set("BUILD_VERSION", moduleSemVer); //Full path to module archive file TaskVars.Set("FULL_ARCHIVE_FILE_NAME", Path.Combine(WorkingDir.FullName, Config.SourceArchiveName)); TaskVars.Set("ARCHIVE_FILE_NAME", Config.SourceArchiveName); TaskVars.Set("ARCHIVE_FILE_FORMAT", Config.SourceArchiveFormat); //Remove any previous projects Projects.Clear(); Log.Information("Discovering projects in module {sln}", ModuleName); //Discover all projects in for the module foreach (IProject project in ModuleExplorer.DiscoverProjects()) { //Store in collection Projects.Add(project); } //Load all projects await Projects.RunAllAsync(p => p.LoadAsync(TaskVars.Clone())); Log.Information("Sucessfully loaded {count} projects into module {sln}", Projects.Count, ModuleName); Log.Information("{modname} CI build SemVer will be {semver}", ModuleName, moduleSemVer); } /// public virtual async Task DoStepSyncSource() { Log.Information("Checking for source code updates in module {mod}", ModuleName); //Do a git pull to update our sources await TaskFile.ExecCommandAsync(this, TaskfileComamnd.Update, true); //Set the latest commit sha after an update TaskVars.Set("HEAD_SHA", Repository.Head.Tip.Sha); //Update lastest build number TaskVars.Set("BUILD_VERSION", this.GetModuleCiVersion(Config.DefaultCiVersion, Config.SemverStyle)); //Update module semver after source sync string moduleSemVer = this.GetModuleCiVersion(Config.DefaultCiVersion, Config.SemverStyle); TaskVars.Set("BUILD_VERSION", moduleSemVer); Log.Information("{modname} CI build SemVer will now be {semver}", ModuleName, moduleSemVer); } /// public virtual async Task CheckForChangesAsync() { //Check source for updates await Projects.RunAllAsync(p => FileManager.CheckSourceChangedAsync(p, Config, Repository.Head.Tip.Sha)); //Check if any project is not up-to-date return Projects.Any(static p => !p.UpToDate); } /// public virtual async Task DoStepBuild() { //Clean the output dir FileManager.CleanOutput(); //Recreate the output dir FileManager.CreateOutput(); //Run taskfile to build await TaskFile.ExecCommandAsync(this, TaskfileComamnd.Build, true); //Run build for all projects foreach (IProject proj in Projects) { //Exec await TaskFile.ExecCommandAsync(proj, TaskfileComamnd.Build, true); } } /// public virtual async Task DoStepPostBuild(bool success) { //Run taskfile postbuild, not required to produce a sucessful result await TaskFile.ExecCommandAsync(this, success ? TaskfileComamnd.PostbuildSuccess : TaskfileComamnd.PostbuildFailure, false); //Run postbuild for all projects foreach (IProject proj in Projects) { //Run postbuild for projects await TaskFile.ExecCommandAsync(proj, success ? TaskfileComamnd.PostbuildSuccess : TaskfileComamnd.PostbuildFailure, false); } //Run postbuild for all projects await Projects.RunAllAsync(async (p) => { //If the operation was a success, commit the sum change if (success) { Log.Verbose("Committing sum change for {sm}", p.ProjectName); //Commit sum changes now that build has completed successfully await FileManager.CommitSumChangeAsync(p); } }); } /// public virtual async Task DoStepPublish() { //Run taskfile postbuild, not required to produce a sucessful result await TaskFile.ExecCommandAsync(this, TaskfileComamnd.Publish, true); //Run postbuild for all projects foreach (IProject proj in Projects) { //Run postbuild for projects await TaskFile.ExecCommandAsync(proj, TaskfileComamnd.Publish, true); } } /// public virtual async Task DoRunTests(bool failOnError) { //Run taskfile to build await TaskFile.ExecCommandAsync(this, TaskfileComamnd.Test, failOnError); //Run build for all projects foreach (IProject proj in Projects) { //Exec await TaskFile.ExecCommandAsync(proj, TaskfileComamnd.Test, failOnError); } } /// public virtual async Task CleanAsync() { try { //Run taskfile to build await TaskFile.ExecCommandAsync(this, TaskfileComamnd.Clean, true); //Clean all projects foreach (IProject proj in Projects) { //Clean the project output dir await TaskFile.ExecCommandAsync(proj, TaskfileComamnd.Clean, true); } //Clean the output dir FileManager.CleanOutput(); } catch (BuildStepFailedException) { throw; } catch (Exception ex) { throw new BuildStepFailedException("Failed to remove the module output directory", ex, ModuleName); } } public override string ToString() => ModuleName; public virtual void Dispose() { //Dispose the respository Repository.Dispose(); //empty list Projects.Clear(); } } }