diff --git a/GlitchyEngine/src/ImGui/ImGuiLayer.bf b/GlitchyEngine/src/ImGui/ImGuiLayer.bf index 51dbde4..58a7714 100644 --- a/GlitchyEngine/src/ImGui/ImGuiLayer.bf +++ b/GlitchyEngine/src/ImGui/ImGuiLayer.bf @@ -1,3 +1,5 @@ +#if IMGUI + using System; using ImGui; using GlitchyEngine.Events; @@ -256,3 +258,5 @@ namespace GlitchyEngine.ImGui } } } + +#endif diff --git a/GlitchyEngineHelper/BeefProj.toml b/GlitchyEngineHelper/BeefProj.toml index 8a3950f..11983f4 100644 --- a/GlitchyEngineHelper/BeefProj.toml +++ b/GlitchyEngineHelper/BeefProj.toml @@ -9,11 +9,9 @@ StartupObject = "GlitchyEngineHelper.Program" [Configs.Debug.Win64] BuildCommandsOnCompile = "IfFilesChanged" BuildCommandsOnRun = "IfFilesChanged" -LibPaths = ["$(ProjectDir)/out/build/x64-debug/GlitchyEngineHelper.lib", "$(ProjectDir)/vendor/mono/lib/Windows/Debug/libmono-static-sgen.lib", "$(ProjectDir)/vendor/mono/lib/Windows/Debug/libmonoutils.lib", "$(ProjectDir)/vendor/mono/lib/Windows/Debug/libmonoruntime-sgen.lib", "ws2_32.lib", "Winmm.lib", "OleAut32.lib", "Bcrypt.lib"] -PreBuildCmds = ["$(ProjectDir)/../bin/vscmake.bat x64 $(ProjectDir) x64-debug", "powershell $(ProjectDir)/Build_Mono.ps1"] +LibPaths = ["$(ProjectDir)/out/build/x64-debug/GlitchyEngineHelper.lib", "ws2_32.lib", "Winmm.lib", "OleAut32.lib", "Bcrypt.lib"] [Configs.Release.Win64] BuildCommandsOnCompile = "IfFilesChanged" BuildCommandsOnRun = "IfFilesChanged" -LibPaths = ["$(ProjectDir)/out/build/x64-release/GlitchyEngineHelper.lib"] -PreBuildCmds = ["$(ProjectDir)/../bin/vscmake.bat x64 $(ProjectDir) x64-release", "powershell $(ProjectDir)/Build_Mono.ps1"] +LibPaths = ["$(ProjectDir)/out/build/x64-debug/GlitchyEngineHelper.lib", "ws2_32.lib", "Winmm.lib", "OleAut32.lib", "Bcrypt.lib"] diff --git a/bin/BuildTool.exe b/bin/BuildTool.exe new file mode 100644 index 0000000..9b9d46d Binary files /dev/null and b/bin/BuildTool.exe differ diff --git a/bin/BuildTool/.gitignore b/bin/BuildTool/.gitignore new file mode 100644 index 0000000..0c523fa --- /dev/null +++ b/bin/BuildTool/.gitignore @@ -0,0 +1,421 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + + + +## VS CODE +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser diff --git a/bin/BuildTool/BuildTool.cs b/bin/BuildTool/BuildTool.cs new file mode 100644 index 0000000..f1d9259 --- /dev/null +++ b/bin/BuildTool/BuildTool.cs @@ -0,0 +1,415 @@ +using System.CommandLine; +using System.Diagnostics; +using System.Runtime.InteropServices.ComTypes; +using SimpleExec; +using static SimpleExec.Command; +using Command = System.CommandLine.Command; + +namespace BuildTool; + +class BuildInfo +{ + public WorkingDirectoryHistory WorkingDirectory; + public bool ForceRebuild; + public bool BuildDebug; + public bool BuildRelease; + + public BuildInfo(WorkingDirectoryHistory workingDirectory, bool forceRebuild, bool buildDebug, bool buildRelease) + { + WorkingDirectory = workingDirectory; + ForceRebuild = forceRebuild; + BuildDebug = buildDebug; + BuildRelease = buildRelease; + } +} + +class SetupNethostModule : Module +{ + public static readonly string ModuleName = "SetupNethost"; + + public override string Name => ModuleName; + + public override List DependencyTypes => []; + + public override async Task Run(BuildInfo buildInfo) + { + buildInfo.WorkingDirectory.NavigateFromWorkspaceRoot("GlitchyEngine/vendor/NetHostBeef"); + + string targetPath = buildInfo.WorkingDirectory.GetRelativePath("./NetHostBeef/nethost/"); + + if (buildInfo.ForceRebuild || !Directory.Exists(targetPath)) + { + if (Directory.Exists(targetPath)) + { + Console.WriteLine("Removing existing nethost directory..."); + Directory.Delete(targetPath, true); + Console.WriteLine("Done."); + } + + Console.WriteLine("Donwloading nethost..."); + await RunAsync("powershell", "./downloadNethost.ps1"); + Console.WriteLine("Done."); + } + else + { + Console.WriteLine("Nethost already exists. Use --force-rebuild to force download."); + } + + return true; + } +} + +class ScriptCoreModule : Module +{ + public static readonly string ModuleName = "ScriptCore"; + + public override string Name => ModuleName; + + public override List DependencyTypes => [typeof(SetupNethostModule)]; + + public override async Task Run(BuildInfo buildInfo) + { + string projectFile = Path.Join(buildInfo.WorkingDirectory.WorkspaceRoot, "ScriptCore/ScriptCore.csproj"); + + if (buildInfo.BuildDebug) + { + await RunAsync("dotnet", $"build \"{projectFile}\" -c Debug"); + } + + if (buildInfo.BuildRelease) + { + await RunAsync("dotnet", $"build \"{projectFile}\" -c Release"); + } + + return true; + } +} + +class GlitchyEngineHelperModule : Module +{ + public static readonly string ModuleName = "GlitchyEngineHelper"; + + public override string Name => ModuleName; + + public override List DependencyTypes => []; + + public override async Task Run(BuildInfo buildInfo) + { + // $(ProjectDir)/../bin/vscmake.bat x64 $(ProjectDir) x64-debug + // $(ProjectDir)/../bin/vscmake.bat x64 $(ProjectDir) x64-release + + buildInfo.WorkingDirectory.NavigateFromWorkspaceRoot("GlitchyEngineHelper"); + + if (buildInfo.BuildDebug) + { + await RunAsync("cmake", "--preset x64-debug"); + } + + if (buildInfo.BuildRelease) + { + await RunAsync("cmake", "--preset x64-release"); + } + + return true; + } +} + +class BeefWorkspaceModule : Module +{ + public static readonly string ModuleName = "BeefWorkspace"; + + public override string Name => ModuleName; + + public override List DependencyTypes => [typeof(ScriptCoreModule), typeof(GlitchyEngineHelperModule)]; + + public override async Task Run(BuildInfo buildInfo) + { + if (buildInfo.BuildDebug) + { + await RunAsync("beefbuild", $"-workspace={buildInfo.WorkingDirectory.WorkspaceRoot} -config=debug"); + } + + if (buildInfo.BuildRelease) + { + await RunAsync("beefbuild", $"-workspace={buildInfo.WorkingDirectory.WorkspaceRoot} -config=release"); + } + + return true; + } +} + +abstract class Module +{ + public abstract string Name { get; } + + public List Dependencies { get; } = new(); + public abstract List DependencyTypes { get; } + + public abstract Task Run(BuildInfo buildInfo); +} + +class Program +{ + /// + /// Given a list of modules, returns a list with the order in which they need to be build. + /// + /// The list of modules we want to build. + /// The list will receive the modules in the order in which they need to be build. + /// if the modules have been ordered; , if the graph could not be ordered. + private static bool ComputeBuildOrder(List requestedModules, List outOrderedModules) + { + Dictionary indegrees = new(requestedModules.Count); + Dictionary> dependees = new(requestedModules.Count); + + Queue queue = new Queue(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? dependeeList)) + { + dependees[dependency] = dependeeList = new List(); + } + + dependeeList.Add(module); + } + } + } + + while (queue.TryDequeue(out Module? currentModule)) + { + outOrderedModules.Add(currentModule); + + if (dependees.TryGetValue(currentModule, out List? 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 Main(string[] args) + { + List modules = [new BeefWorkspaceModule(), new GlitchyEngineHelperModule(), new ScriptCoreModule(), new SetupNethostModule()]; + + // 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 orderedModules = new List(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 debugOption = new("--debug", "-d") + { + Description = "Defines that the specified modules should be compiled as debug builds." + }; + Option releaseOption = new("--release", "-r") + { + Description = "Defines that the specified modules should be compiled as release builds." + }; + Option rebuildOption = new("--force-rebuild", "-f") + { + Description = "Defines that the specified modules should be rebuild." + }; + Option noDependenciesOption = new("--no-dependencies", "-n") + { + Description = "Defines that only the specified modules are build, but not their dependencies (unless they are also specified)." + }; + + Option 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() 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 modulesArgument = new("modules") + { + Description = "The modules to build.", + DefaultValueFactory = _ => [BeefWorkspaceModule.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 allModulesOrdered, string[] moduleNamesToBuild, DirectoryInfo workspaceRoot, bool forceRebuild, bool noDependencies, bool debug, bool release) + { + HashSet modulesToBuild = new HashSet(allModulesOrdered.Count); + + if (moduleNamesToBuild.Length == 0) + { + Console.WriteLine("Building all modules."); + + modulesToBuild.UnionWith(allModulesOrdered); + } + else + { + Queue modulesToAdd = new Queue(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 acutalModuleOrder = allModulesOrdered.Intersect(modulesToBuild).ToList(); + + 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) + { + Console.WriteLine($"Building module: {module.Name}"); + + buildInfo.WorkingDirectory.NavigateToIndex(0); + + try + { + await module.Run(buildInfo); + } + catch (ExitCodeException e) + { + Console.WriteLine("Building module failed with exit code: " + e.ExitCode); + + // TODO: allow abort once a step failed. + } + } + } +} diff --git a/bin/BuildTool/BuildTool.csproj b/bin/BuildTool/BuildTool.csproj new file mode 100644 index 0000000..94c8277 --- /dev/null +++ b/bin/BuildTool/BuildTool.csproj @@ -0,0 +1,31 @@ + + + + Exe + net10.0 + enable + enable + true + true + build-7f7a4354050726fe6f1b5cfd0a8592d0ee71bab853fc1c5c128d53981653347c + BuildTool.Program + true + true + true + true + link + + $(SolutionDir).. + + + + + + + + + + + diff --git a/bin/BuildTool/WorkingDirectoryHistory.cs b/bin/BuildTool/WorkingDirectoryHistory.cs new file mode 100644 index 0000000..dbd96a7 --- /dev/null +++ b/bin/BuildTool/WorkingDirectoryHistory.cs @@ -0,0 +1,127 @@ +namespace BuildTool; + +class WorkingDirectoryHistory +{ + private List _history = new(); + private int _currentIndex = -1; + + public int CurrentIndex => _currentIndex; + public int HistoryLength => _history.Count; + + public IReadOnlyCollection History => _history.AsReadOnly(); + + public string CurrentDirectoryPath => _currentIndex >= 0 ? _history[_currentIndex] : ""; + public string WorkspaceRoot => _history[0]; + + public bool CanGoBack => _currentIndex > 0; + public bool CanGoForward => (_currentIndex + 1) < _history.Count; + + public WorkingDirectoryHistory(string rootPath) + { + Navigate(rootPath); + } + + public void ApplyCurrentDirectory() + { + Directory.SetCurrentDirectory(CurrentDirectoryPath); + } + + public void Navigate(string nextPath) + { + if (!Path.IsPathRooted(nextPath)) + { + nextPath = Path.Combine(CurrentDirectoryPath, nextPath); + InternalNavigate(nextPath); + } + + InternalNavigate(nextPath); + } + + public void NavigateFromWorkspaceRoot(string nextPath) + { + if (Path.IsPathRooted(nextPath)) + { + throw new ArgumentException("Cannot navigate from a root path other than its working directory."); + } + + nextPath = Path.Combine(WorkspaceRoot, nextPath); + InternalNavigate(nextPath); + } + + private void InternalNavigate(string nextPath) + { + nextPath = Path.GetFullPath(nextPath); + + _currentIndex++; + _history.Insert(_currentIndex, nextPath); + TrimHistory(); + ApplyCurrentDirectory(); + } + + private void TrimHistory() + { + _history.RemoveRange(_currentIndex + 1, _history.Count - _currentIndex - 1); + } + + public void Replace(string nextPath) + { + if (_currentIndex == -1) + { + _currentIndex = 0; + _history.Insert(_currentIndex, nextPath); + } + else + { + _history[_currentIndex] = nextPath; + } + + TrimHistory(); + ApplyCurrentDirectory(); + } + + public bool Back() + { + if (CanGoBack) + { + _currentIndex--; + ApplyCurrentDirectory(); + return true; + } + return false; + } + + public bool Forward() + { + if (CanGoForward) + { + _currentIndex++; + ApplyCurrentDirectory(); + return true; + } + return false; + } + + public bool NavigateToIndex(int index) + { + if (index < 0) + return false; + + if (index >= _history.Count) + return false; + + _currentIndex = index; + ApplyCurrentDirectory(); + + return true; + } + + public string GetRelativePath(string path) + { + return Path.GetFullPath(Path.Combine(CurrentDirectoryPath, path)); + } + + public string GetPathRelativeToRoot(string path) + { + return Path.GetFullPath(Path.Combine(WorkspaceRoot, path)); + } +} \ No newline at end of file