diff options
author | vnugent <public@vaughnnugent.com> | 2024-05-07 17:01:22 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-05-07 17:01:22 -0400 |
commit | 79d824cfb0e0cc9ff4fab0e0c546a83c0edaae1c (patch) | |
tree | d69e962a15493b2e36882810a2cc43d5a0de1a0b /src/Constants | |
parent | b3015399591bd81b8519f0efa2ec177163f7d04a (diff) |
initial commit
Diffstat (limited to 'src/Constants')
-rw-r--r-- | src/Constants/BuildConfig.cs | 77 | ||||
-rw-r--r-- | src/Constants/BuildDirs.cs | 35 | ||||
-rw-r--r-- | src/Constants/Config.cs | 95 | ||||
-rw-r--r-- | src/Constants/ConfigManager.cs | 41 | ||||
-rw-r--r-- | src/Constants/ConsoleCancelToken.cs | 29 | ||||
-rw-r--r-- | src/Constants/Utils.cs | 151 |
6 files changed, 428 insertions, 0 deletions
diff --git a/src/Constants/BuildConfig.cs b/src/Constants/BuildConfig.cs new file mode 100644 index 0000000..39758ea --- /dev/null +++ b/src/Constants/BuildConfig.cs @@ -0,0 +1,77 @@ +using System.Text.Json.Serialization; + +using Semver; + +using VNLib.Tools.Build.Executor.Model; + +namespace VNLib.Tools.Build.Executor.Constants +{ + public sealed class BuildConfig + { + [JsonPropertyName("soure_file_extensions")] + public string[] SourceFileEx { get; set; } = [ + "c", + "cpp", + "cxx", + "h", + "hpp", + "cs", + "proj", + "sln", + "ts", + "js", + "java", + "json", + "yaml", + "yml", + ]; + + [JsonPropertyName("excluded_dirs")] + public string[] ExcludedSourceDirs { get; set; } = [ + "bin", + "obj", + "packages", + "node_modules", + "dist", + "build", + "out", + "target", + ]; + + [JsonPropertyName("default_sha_method")] + public string HashFuncName { get; set; } = "sha256"; + + [JsonPropertyName("head_file_name")] + public string HeadFileName { get; set; } = "@latest"; + + [JsonPropertyName("module_task_file_name")] + public string ModuleTaskFileName { get; set; } = "Module.Taskfile.yaml"; + + [JsonPropertyName("main_taskfile_name")] + public string MainTaskFileName { get; set; } = "build.taskfile.yaml"; + + [JsonPropertyName("output_file_type")] + public string OutputFileType { get; set; } = "*.tgz"; + + [JsonPropertyName("task_exe_name")] + public string TaskExeName { get; set; } = "task"; + + [JsonPropertyName("source_archive_name")] + public string SourceArchiveName { get; set; } = "archive.tgz"; + + [JsonPropertyName("source_archive_format")] + public string SourceArchiveFormat { get; set; } = "tgz"; + + [JsonPropertyName("project_bin_dir")] + public string ProjectBinDir { get; set; } = "bin"; + + [JsonPropertyName("default_ci_version")] + public string DefaultCiVersion { get; set; } = "0.1.0"; + + [JsonPropertyName("semver_style")] + public SemVersionStyles SemverStyle { get; set; } + + [JsonIgnore] + public IDirectoryIndex Index { get; set; } = default!; + } +}
\ No newline at end of file diff --git a/src/Constants/BuildDirs.cs b/src/Constants/BuildDirs.cs new file mode 100644 index 0000000..78609f5 --- /dev/null +++ b/src/Constants/BuildDirs.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; + +namespace VNLib.Tools.Build.Executor.Constants +{ + internal static class BuildDirs + { + + private static DirectoryInfo GetProjectDir() + { + //See if dir was specified on command line + string[] args = Environment.GetCommandLineArgs(); + + //Get the build dir + DirectoryInfo dir = new(args.Length > 1 && Directory.Exists(args[1]) ? args[1] : Directory.GetCurrentDirectory()); + + if (!dir.Exists) + { + dir.Create(); + } + return dir; + } + + public static DirectoryInfo GetOrCreateDir(string @default, string? other = null) + { + //Get the scratch dir + DirectoryInfo logDir = new(Path.Combine(GetProjectDir().FullName, other?? @default)); + if (!logDir.Exists) + { + logDir.Create(); + } + return logDir; + } + } +}
\ No newline at end of file diff --git a/src/Constants/Config.cs b/src/Constants/Config.cs new file mode 100644 index 0000000..fc35928 --- /dev/null +++ b/src/Constants/Config.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; +using System.Linq; + +using Serilog; +using Serilog.Core; + +using VNLib.Tools.Build.Executor.Model; + +namespace VNLib.Tools.Build.Executor.Constants +{ + + internal static class Config + { + + //relative local directores to the project root + public const string BUILD_DIR_NAME = ".build"; + public const string LOG_DIR_NAME = ".build/log"; + public const string BUILD_CONFIG = "build.conf.json"; + public const string SCRATCH_DIR = ".build/scratch"; + public const string SUM_DIR = ".build/sums"; + public const string OUTPUT_DIR = ".build/output"; + public const string SLEET_DIR = ".build/feed"; + + /// <summary> + /// Gets the system wide <see cref="Logger"/> log writer instance + /// </summary> + public static Logger Log { get; } = GetLog(); + + const string Template = "{Message:lj}{NewLine}{Exception}"; + + private static Logger GetLog() + { + string[] args = Environment.GetCommandLineArgs(); + + LoggerConfiguration conf = new(); + + if (args.Contains("-v") || args.Contains("--verbose")) + { + //Check for verbose logging level + conf.MinimumLevel.Verbose(); + } + else if (args.Contains("-d") || args.Contains("--debug")) + { + //Check for debug + conf.MinimumLevel.Debug(); + } + else + { + //Default information level + conf.MinimumLevel.Information(); + } + + //Create a console logger unless the silent flag is set + if (!args.Contains("-s")) + { + conf.WriteTo.Console(outputTemplate: Template); + } + + //Creat the new log file + string logFilePath = Path.Combine(LOG_DIR_NAME, $"{DateTimeOffset.Now.ToUnixTimeSeconds()}-log.txt"); + + //Setup the log file output + conf.WriteTo.File(logFilePath, outputTemplate: Template); + + return conf.CreateLogger(); + } + + /// <summary> + /// Cleans up old build log files, so that only 100 log files remain in the log directory + /// </summary> + public static void TrimLogs(IDirectoryIndex dirIndex, int maxLogs) + { + try + { + //Get all log files in the log directory and cleanup any files after the max log count + FileInfo[] toDelete = dirIndex.LogDir.EnumerateFiles("*.txt", SearchOption.TopDirectoryOnly) + .OrderByDescending(static f => f.LastWriteTimeUtc) + .Skip(maxLogs) + .ToArray(); + + foreach (FileInfo file in toDelete) + { + file.Delete(); + } + + Log.Debug("Cleaned {file} log files", toDelete.Length); + } + catch (Exception ex) + { + Log.Warning(ex, "Failed to cleanup log files"); + } + } + } +}
\ No newline at end of file diff --git a/src/Constants/ConfigManager.cs b/src/Constants/ConfigManager.cs new file mode 100644 index 0000000..3a0b295 --- /dev/null +++ b/src/Constants/ConfigManager.cs @@ -0,0 +1,41 @@ +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; + +using Semver; + +using VNLib.Tools.Build.Executor.Model; + +namespace VNLib.Tools.Build.Executor.Constants +{ + public class ConfigManager(SemVersionStyles semver) + { + public async Task<BuildConfig> GetOrCreateConfig(IDirectoryIndex index, bool overwrite) + { + //Get the config file path + string configFilePath = Path.Combine(index.BuildDir.FullName, Config.BUILD_CONFIG); + + BuildConfig? data = new() + { + SemverStyle = semver + }; + + //If the file doesnt exist or we want to overwrite it + if (!File.Exists(configFilePath) || overwrite) + { + //Create a new config file + await using FileStream fs = File.Create(configFilePath); + await JsonSerializer.SerializeAsync(fs, data); + } + else + { + await using FileStream fs = File.OpenRead(configFilePath); + data = await JsonSerializer.DeserializeAsync<BuildConfig>(fs); + } + + data!.Index = index; + + return data; + } + } +}
\ No newline at end of file diff --git a/src/Constants/ConsoleCancelToken.cs b/src/Constants/ConsoleCancelToken.cs new file mode 100644 index 0000000..cc25feb --- /dev/null +++ b/src/Constants/ConsoleCancelToken.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading; + +namespace VNLib.Tools.Build.Executor.Constants +{ + internal sealed class ConsoleCancelToken : IDisposable + { + private readonly CancellationTokenSource _cts = new(); + + public CancellationToken Token => _cts.Token; + + public ConsoleCancelToken() => Console.CancelKeyPress += OnCancel; + + private void OnCancel(object? sender, ConsoleCancelEventArgs e) + { + _cts.Cancel(); + e.Cancel = true; + } + + public void Dispose() + { + //Unsubscribe from event + Console.CancelKeyPress -= OnCancel; + _cts.Dispose(); + + GC.SuppressFinalize(this); + } + } +}
\ No newline at end of file diff --git a/src/Constants/Utils.cs b/src/Constants/Utils.cs new file mode 100644 index 0000000..6047c35 --- /dev/null +++ b/src/Constants/Utils.cs @@ -0,0 +1,151 @@ +using System; +using System.Threading; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +using static VNLib.Tools.Build.Executor.Constants.Config; + +namespace VNLib.Tools.Build.Executor.Constants +{ + + internal static class Utils + { + + /// <summary> + /// Runs a process by its name/exe file path, and writes its stdout/stderr to + /// the default build log + /// </summary> + /// <param name="process">The name of the process to run</param> + /// <param name="args">CLI arguments to pass to the process</param> + /// <returns>The process exit code</returns> + public static async Task<int> RunProcessAsync(string process, string? workingDir, string[] args, IReadOnlyDictionary<string, string>? env = null) + { + //Init new console cancellation token + using ConsoleCancelToken ctToken = new(); + + ProcessStartInfo psi = new(process) + { + //Redirect streams + RedirectStandardError = true, + RedirectStandardOutput = true, + CreateNoWindow = true, + //Create a child process, not shell + UseShellExecute = false, + WorkingDirectory = workingDir ?? string.Empty, + }; + + if (env != null) + { + //Add all env variables to process + foreach (KeyValuePair<string, string> kv in env) + { + psi.Environment.Add(kv.Key, kv.Value); + } + } + + //Add arguments + foreach (string arg in args) + { + psi.ArgumentList.Add(arg); + } + + using Process proc = new(); + proc.StartInfo = psi; + + //Start the process + proc.Start(); + + Log.Debug("Starting process {proc}, with args {args}", proc.ProcessName, args); + Console.WriteLine(); + + //Log std out + Task stdout = LogStdOutAsync(proc, ctToken.Token); + Task stdErr = LogStdErrAsync(proc, ctToken.Token); + + //Wait for the process to exit + Task wfe = proc.WaitForExitAsync(ctToken.Token); + + //Wait for stderr/out/proc to exit + await Task.WhenAll(stdout, stdErr, wfe); + + Console.WriteLine(); + Log.Debug("[CHILD]:{id}:{p} exited w/ code {code}", proc.ProcessName, proc.Id, proc.ExitCode); + + //Return status code + return proc.ExitCode; + } + + private static async Task LogStdOutAsync(Process psi, CancellationToken cancellation) + { + try + { + string procName = psi.ProcessName; + int id = psi.Id; + + do + { + //Read lines from the process + string? line = await psi.StandardOutput.ReadLineAsync(cancellation); + + if (line == null) + { + break; + } + + //Print to log file + Console.WriteLine(line); + } while (!psi.HasExited); + } + catch (Exception ex) + { + Log.Error(ex, "An exception was raised while reading the process standard output"); + } + } + + private static async Task LogStdErrAsync(Process psi, CancellationToken cancellation) + { + try + { + string procName = psi.ProcessName; + int id = psi.Id; + + do + { + //Read lines from the process + string? line = await psi.StandardError.ReadLineAsync(cancellation); + + if (line == null) + { + break; + } + + //Print to log file + Console.WriteLine(line); + } while (!psi.HasExited); + } + catch (Exception ex) + { + Log.Error(ex, "An exception was raised while reading the process standard output"); + } + } + + + /// <summary> + /// Throws a <see cref="BuildStepFailedException"/> if the value + /// of <paramref name="status"/> is false + /// </summary> + /// <param name="status">If false throws exception</param> + /// <param name="message">The message to display</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfStepFailed(bool status, string message, string artifactName) + { + if (!status) + { + throw new BuildStepFailedException(message, artifactName); + } + } + + } +}
\ No newline at end of file |