mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
- Changed ModuleInfo to class, save Length for Timestamp-Hashes - Obey recursive-flag for WatchedSources - Verbosely skip up-to-date modules
306 lines
12 KiB
C#
306 lines
12 KiB
C#
using System.CommandLine;
|
|
using System.Diagnostics;
|
|
using SimpleExec;
|
|
using Spectre.Console;
|
|
using BuildTool.Modules;
|
|
|
|
using Command = System.CommandLine.Command;
|
|
|
|
namespace BuildTool;
|
|
|
|
class Program
|
|
{
|
|
/// <summary>
|
|
/// Given a list of modules, returns a list with the order in which they need to be build.
|
|
/// </summary>
|
|
/// <param name="requestedModules">The list of modules we want to build.</param>
|
|
/// <param name="outOrderedModules">The list will receive the modules in the order in which they need to be build.</param>
|
|
/// <returns><see langword="true"/> if the modules have been ordered; <see langword="false"/>, if the graph could not be ordered.</returns>
|
|
private static bool ComputeBuildOrder(List<Module> requestedModules, List<Module> outOrderedModules)
|
|
{
|
|
Dictionary<Module, int> indegrees = new(requestedModules.Count);
|
|
Dictionary<Module, List<Module>> dependees = new(requestedModules.Count);
|
|
|
|
Queue<Module> queue = new Queue<Module>(requestedModules.Count);
|
|
|
|
// Get in-degrees and put 0-degree nodes into queue
|
|
foreach (Module module in requestedModules)
|
|
{
|
|
int inDegree = module.Dependencies.Count;
|
|
indegrees[module] = inDegree;
|
|
|
|
if (inDegree == 0)
|
|
{
|
|
queue.Enqueue(module);
|
|
}
|
|
else
|
|
{
|
|
foreach (Module dependency in module.Dependencies)
|
|
{
|
|
if (!dependees.TryGetValue(dependency, out List<Module>? dependeeList))
|
|
{
|
|
dependees[dependency] = dependeeList = new List<Module>();
|
|
}
|
|
|
|
dependeeList.Add(module);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (queue.TryDequeue(out Module? currentModule))
|
|
{
|
|
outOrderedModules.Add(currentModule);
|
|
|
|
if (dependees.TryGetValue(currentModule, out List<Module>? dependeeList))
|
|
{
|
|
foreach (var dependee in dependeeList)
|
|
{
|
|
int dependeeIndegree = --indegrees[dependee];
|
|
|
|
Debug.Assert(dependeeIndegree >= 0);
|
|
|
|
if (dependeeIndegree == 0)
|
|
{
|
|
queue.Enqueue(dependee);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (outOrderedModules.Count < requestedModules.Count)
|
|
{
|
|
// We have a cycle and can't order the dependencies.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static async Task<int> Main(string[] args)
|
|
{
|
|
List<Module> modules = [new BeefWorkspaceModule(), new GlitchyEngineHelperModule(), new ScriptCoreModule(), new SetupNethostModule(), new Box2DModule(), new FreetypeStackModule(), new MSDFgenModule(), new AllModule()];
|
|
|
|
// Find dependencies
|
|
foreach (Module module in modules)
|
|
{
|
|
foreach (Type moduleDependencyType in module.DependencyTypes)
|
|
{
|
|
Module dependencyInstance = modules.Single(m => moduleDependencyType.IsInstanceOfType(m));
|
|
module.Dependencies.Add(dependencyInstance);
|
|
}
|
|
}
|
|
|
|
List<Module> orderedModules = new List<Module>(modules.Count);
|
|
if (!ComputeBuildOrder(modules, orderedModules))
|
|
{
|
|
throw new Exception($"Could not compute build order. Are there any cylces? Unsorted modules: {string.Join(", ", modules.Except(orderedModules).Select(m => m.Name))}");
|
|
}
|
|
|
|
Option<bool> debugOption = new("--debug", "-d")
|
|
{
|
|
Description = "Defines that the specified modules should be compiled as debug builds."
|
|
};
|
|
Option<bool> releaseOption = new("--release", "-r")
|
|
{
|
|
Description = "Defines that the specified modules should be compiled as release builds."
|
|
};
|
|
Option<bool> rebuildOption = new("--force-rebuild", "-f")
|
|
{
|
|
Description = "Defines that the specified modules should be rebuild."
|
|
};
|
|
Option<bool> noDependenciesOption = new("--no-dependencies", "-n")
|
|
{
|
|
Description = "Defines that only the specified modules are build, but not their dependencies (unless they are also specified)."
|
|
};
|
|
|
|
Option<DirectoryInfo> workspaceRootOption = new("--workspace", "-w")
|
|
{
|
|
Description = "The relative or absolute path to the root directory of the workspace. If unset, the current working directory will be used.",
|
|
DefaultValueFactory = _ => new DirectoryInfo(Environment.CurrentDirectory)
|
|
};
|
|
workspaceRootOption.Validators.Add(result =>
|
|
{
|
|
if (result.GetValueOrDefault<DirectoryInfo>() is not DirectoryInfo workspaceRoot)
|
|
{
|
|
result.AddError("The workspace directory doesn't exist.");
|
|
return;
|
|
}
|
|
|
|
if (!workspaceRoot.Exists)
|
|
{
|
|
result.AddError($"The workspace directory '{workspaceRoot.FullName}' doesn't exist.");
|
|
}
|
|
|
|
// Minimal sanity check: Is it actually a beef workspace? (Does BeefSpace.toml exist)
|
|
string beefSpaceFile = Path.Combine(workspaceRoot.FullName, "BeefSpace.toml");
|
|
if (!File.Exists(beefSpaceFile))
|
|
{
|
|
result.AddError($"The directory '{workspaceRoot.FullName}' doesn't seem to be a workspace. Either invoke it from the root of the workspace, or use the '--workspace' option.");
|
|
}
|
|
});
|
|
|
|
Argument<string[]> modulesArgument = new("modules")
|
|
{
|
|
Description = "The modules to build.",
|
|
// DefaultValueFactory = _ => [AllModule.ModuleName]
|
|
};
|
|
|
|
modulesArgument.Validators.Add(result =>
|
|
{
|
|
if (result.GetValue(modulesArgument) is string[] modulesToBuild)
|
|
{
|
|
foreach (string moduleToBuild in modulesToBuild)
|
|
{
|
|
if (!modules.Any(m => m.Name.Equals(moduleToBuild, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
result.AddError($"The module \"{moduleToBuild}\" doesn't exists.");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
modulesArgument.CompletionSources.Add(completionContext =>
|
|
{
|
|
string[] alreadySpecifiedModules = completionContext.ParseResult.GetValue(modulesArgument) ?? [];
|
|
|
|
return modules.Select(m => m.Name) // Get available Module names.
|
|
.Where(moduleName => moduleName.StartsWith(completionContext.WordToComplete)) // Only take the modules that start with the word to complete.
|
|
.Where(moduleName => !alreadySpecifiedModules.Contains(moduleName)); // Remove the modules that already are specified.
|
|
});
|
|
|
|
RootCommand rootCommand = new("Builds the engine and it's modules.");
|
|
|
|
Command buildCommand = new("build", "Builds the specified modules. At least either '--debug' or '--release' or both must be specified.");
|
|
rootCommand.Add(buildCommand);
|
|
|
|
buildCommand.Options.Add(debugOption);
|
|
buildCommand.Options.Add(releaseOption);
|
|
buildCommand.Options.Add(rebuildOption);
|
|
buildCommand.Options.Add(noDependenciesOption);
|
|
buildCommand.Options.Add(workspaceRootOption);
|
|
buildCommand.Arguments.Add(modulesArgument);
|
|
|
|
buildCommand.Validators.Add(result =>
|
|
{
|
|
bool buildDebug = result.GetValue(debugOption);
|
|
bool buildRelease = result.GetValue(releaseOption);
|
|
|
|
if (!buildDebug && !buildRelease)
|
|
{
|
|
result.AddError("Either \"--debug\" or \"--release\" option, or both, must be specified.");
|
|
}
|
|
});
|
|
|
|
buildCommand.SetAction(parseResult =>
|
|
BuildModules(
|
|
orderedModules,
|
|
parseResult.GetValue(modulesArgument)!,
|
|
parseResult.GetValue(workspaceRootOption)!,
|
|
parseResult.GetValue(rebuildOption),
|
|
parseResult.GetValue(noDependenciesOption),
|
|
parseResult.GetValue(debugOption),
|
|
parseResult.GetValue(releaseOption)));
|
|
|
|
Command listModulesCommand = new("list");
|
|
rootCommand.Add(listModulesCommand);
|
|
listModulesCommand.SetAction(result =>
|
|
{
|
|
Console.WriteLine("Following modules can be build:");
|
|
|
|
foreach (Module module in modules)
|
|
{
|
|
Console.WriteLine(module.Name);
|
|
}
|
|
|
|
Console.WriteLine("Build order:");
|
|
Console.WriteLine(string.Join(" -> ", orderedModules.Select(m => m.Name)));
|
|
});
|
|
|
|
ParseResult parseResult = rootCommand.Parse(args);
|
|
return await parseResult.InvokeAsync();
|
|
}
|
|
|
|
private static async Task BuildModules(List<Module> allModulesOrdered, string[] moduleNamesToBuild, DirectoryInfo workspaceRoot, bool forceRebuild, bool noDependencies, bool debug, bool release)
|
|
{
|
|
HashSet<Module> modulesToBuild = new HashSet<Module>(allModulesOrdered.Count);
|
|
|
|
if (moduleNamesToBuild.Length == 0)
|
|
{
|
|
Console.WriteLine("Building all modules.");
|
|
|
|
modulesToBuild.UnionWith(allModulesOrdered);
|
|
}
|
|
else
|
|
{
|
|
Queue<Module> modulesToAdd = new Queue<Module>(allModulesOrdered.Where(m =>
|
|
moduleNamesToBuild.Contains(m.Name, StringComparer.OrdinalIgnoreCase)));
|
|
|
|
while (modulesToAdd.TryDequeue(out Module? moduleToAdd))
|
|
{
|
|
if (modulesToBuild.Add(moduleToAdd) && !noDependencies)
|
|
{
|
|
foreach (Module dependency in moduleToAdd.Dependencies)
|
|
{
|
|
modulesToAdd.Enqueue(dependency);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
List<Module> acutalModuleOrder = allModulesOrdered.Intersect(modulesToBuild).ToList();
|
|
|
|
string cacheFilePath = Path.Combine(workspaceRoot.FullName, "build", ".build_tool.json");
|
|
|
|
ModuleCache moduleCache = ModuleCache.Load(cacheFilePath);
|
|
|
|
Console.WriteLine($"Building modules: {string.Join(", ", acutalModuleOrder.Select(m => m.Name))}");
|
|
await Task.Delay(1000);
|
|
|
|
Console.WriteLine($"Workspace: {workspaceRoot.FullName}");
|
|
BuildInfo buildInfo = new(new WorkingDirectoryHistory(workspaceRoot.FullName), forceRebuild, debug, release);
|
|
|
|
foreach (Module module in acutalModuleOrder)
|
|
{
|
|
string message = $"Building module: [bold italic blue]{module.Name}[/]";
|
|
|
|
Console.WriteLine();
|
|
Console.WriteLine(new string('-', message.Length));
|
|
AnsiConsole.MarkupLine(message);
|
|
Console.WriteLine(new string('-', message.Length));
|
|
Console.WriteLine();
|
|
|
|
buildInfo.WorkingDirectory.NavigateToIndex(0);
|
|
|
|
// create a copy of the build info updated with the module's watched sources and cache info (last build time etc.)
|
|
BuildInfo moduleBuildInfo = module.GetBuildInfo(buildInfo, moduleCache);
|
|
|
|
if (!moduleBuildInfo.BuildDebug && !moduleBuildInfo.BuildRelease)
|
|
{
|
|
AnsiConsole.MarkupLine($"\n[green]Module [bold italic]{module.Name}[/] is up to date, skipping.[/]\n");
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
await module.Run(moduleBuildInfo);
|
|
|
|
AnsiConsole.MarkupLine($"\n[green]Module [bold italic]{module.Name}[/] completed successfully.[/]\n");
|
|
|
|
module.UpdateCacheInfo(moduleBuildInfo, moduleCache, true);
|
|
}
|
|
catch (ExitCodeException e)
|
|
{
|
|
AnsiConsole.MarkupLine(
|
|
$"\n[bold red]Error:[/] Module [bold italic]{module.Name}[/] failed with exit code: [red]{e.ExitCode}[/]\n");
|
|
|
|
module.UpdateCacheInfo(moduleBuildInfo, moduleCache, false);
|
|
|
|
break;
|
|
}
|
|
finally
|
|
{
|
|
moduleCache.Save(cacheFilePath);
|
|
}
|
|
}
|
|
}
|
|
}
|