mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Simple unnecessary build prevention
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
namespace BuildTool;
|
namespace BuildTool;
|
||||||
|
|
||||||
class BuildInfo
|
struct BuildInfo
|
||||||
{
|
{
|
||||||
public WorkingDirectoryHistory WorkingDirectory;
|
public WorkingDirectoryHistory WorkingDirectory;
|
||||||
public bool ForceRebuild;
|
public bool ForceRebuild;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.CommandLine;
|
using System.CommandLine;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
using SimpleExec;
|
using SimpleExec;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using BuildTool.Modules;
|
using BuildTool.Modules;
|
||||||
@@ -247,7 +248,11 @@ class Program
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Module> acutalModuleOrder = allModulesOrdered.Intersect(modulesToBuild).ToList();
|
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))}");
|
Console.WriteLine($"Building modules: {string.Join(", ", acutalModuleOrder.Select(m => m.Name))}");
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
@@ -263,21 +268,33 @@ class Program
|
|||||||
AnsiConsole.MarkupLine(message);
|
AnsiConsole.MarkupLine(message);
|
||||||
Console.WriteLine(new string('-', message.Length));
|
Console.WriteLine(new string('-', message.Length));
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
buildInfo.WorkingDirectory.NavigateToIndex(0);
|
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);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await module.Run(buildInfo);
|
await module.Run(moduleBuildInfo);
|
||||||
|
|
||||||
AnsiConsole.MarkupLine($"\n[green]Module [bold italic]{module.Name}[/] completed successfully.[/]\n");
|
AnsiConsole.MarkupLine($"\n[green]Module [bold italic]{module.Name}[/] completed successfully.[/]\n");
|
||||||
|
|
||||||
|
module.UpdateCacheInfo(moduleBuildInfo, moduleCache, true);
|
||||||
}
|
}
|
||||||
catch (ExitCodeException e)
|
catch (ExitCodeException e)
|
||||||
{
|
{
|
||||||
AnsiConsole.MarkupLine($"\n[bold red]Error:[/] Module [bold italic]{module.Name}[/] failed with exit code: [red]{e.ExitCode}[/]\n");
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
moduleCache.Save(cacheFilePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+174
-1
@@ -1,4 +1,8 @@
|
|||||||
namespace BuildTool;
|
using System.Diagnostics;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BuildTool;
|
||||||
|
|
||||||
abstract class Module
|
abstract class Module
|
||||||
{
|
{
|
||||||
@@ -8,4 +12,173 @@ abstract class Module
|
|||||||
public abstract List<Type> DependencyTypes { get; }
|
public abstract List<Type> DependencyTypes { get; }
|
||||||
|
|
||||||
public abstract Task<bool> Run(BuildInfo buildInfo);
|
public abstract Task<bool> Run(BuildInfo buildInfo);
|
||||||
|
|
||||||
|
protected virtual List<WatchedSource> GetWatchedSources(BuildInfo buildInfo) => [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether the requested build configurations actually need to be rebuilt.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buildInfo"></param>
|
||||||
|
/// <param name="moduleBuildCache"></param>
|
||||||
|
/// <returns>The updated build information.</returns>
|
||||||
|
public BuildInfo GetBuildInfo(BuildInfo buildInfo, ModuleCache moduleBuildCache)
|
||||||
|
{
|
||||||
|
BuildInfo moduleBuildInfo = buildInfo;
|
||||||
|
|
||||||
|
if (buildInfo.BuildDebug)
|
||||||
|
{
|
||||||
|
ModuleInfo cacheInfo = moduleBuildCache.GetModuleInfo(Name, BuildConfig.Debug);
|
||||||
|
|
||||||
|
moduleBuildInfo.BuildDebug = HasSourceChanged(moduleBuildInfo, cacheInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildInfo.BuildRelease)
|
||||||
|
{
|
||||||
|
ModuleInfo cacheInfo = moduleBuildCache.GetModuleInfo(Name, BuildConfig.Release);
|
||||||
|
|
||||||
|
moduleBuildInfo.BuildRelease = HasSourceChanged(moduleBuildInfo, cacheInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return moduleBuildInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasSourceChanged(BuildInfo buildInfo, ModuleInfo cacheInfo)
|
||||||
|
{
|
||||||
|
bool needsRebuild = false;
|
||||||
|
|
||||||
|
if (buildInfo.ForceRebuild)
|
||||||
|
needsRebuild = true;
|
||||||
|
|
||||||
|
// If we never successfully built before, we need to build now.
|
||||||
|
if (cacheInfo.LastSuccessfulBuild == DateTime.MinValue)
|
||||||
|
needsRebuild = true;
|
||||||
|
|
||||||
|
List<WatchedSource> watchedSources = GetWatchedSources(buildInfo);
|
||||||
|
|
||||||
|
if (watchedSources.Count == 0)
|
||||||
|
needsRebuild = true;
|
||||||
|
|
||||||
|
foreach (WatchedSource source in watchedSources)
|
||||||
|
{
|
||||||
|
bool cacheEntryFound = cacheInfo.TryGetCachedHash(source.Path, out SourceHash cachedHash);
|
||||||
|
|
||||||
|
SourceHash newHash;
|
||||||
|
|
||||||
|
if (Directory.Exists(source.Path))
|
||||||
|
{
|
||||||
|
Debug.Assert(source.Mode == WatchMode.Metadata, "Directory watching is only supported in Metadata mode.");
|
||||||
|
|
||||||
|
DirectoryInfo dirInfo = new (source.Path);
|
||||||
|
|
||||||
|
if (dirInfo.LastWriteTimeUtc > cacheInfo.LastSuccessfulBuild)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Needs rebuild");
|
||||||
|
needsRebuild = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we don't save anything directory specific in the has, we can skip this check if we already know that we are going to rebuild.
|
||||||
|
if (!needsRebuild)
|
||||||
|
{
|
||||||
|
foreach (FileSystemInfo dirEntry in dirInfo.EnumerateFileSystemInfos("**",
|
||||||
|
enumerationOptions: new EnumerationOptions()
|
||||||
|
{ MatchType = MatchType.Win32, RecurseSubdirectories = source.Recursive }))
|
||||||
|
{
|
||||||
|
//string relativePath = Path.GetRelativePath(dirInfo.FullName, dirEntry.FullName);
|
||||||
|
//if (source.ExcludedDirectories.Contains(relativePath, StringComparer.OrdinalIgnoreCase))
|
||||||
|
// continue;
|
||||||
|
|
||||||
|
Console.WriteLine("Checking entry: " + dirEntry.FullName);
|
||||||
|
if (dirEntry.LastWriteTimeUtc > cacheInfo.LastSuccessfulBuild)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Needs rebuild");
|
||||||
|
needsRebuild = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We really just need any timestamp, to know that the directory existed at some point.
|
||||||
|
newHash = SourceHash.CreateTimeStamp(DateTime.UtcNow, 1);
|
||||||
|
}
|
||||||
|
else if (File.Exists(source.Path))
|
||||||
|
{
|
||||||
|
FileInfo fileInfo = new (source.Path);
|
||||||
|
|
||||||
|
if (source.Mode == WatchMode.Metadata)
|
||||||
|
{
|
||||||
|
newHash = SourceHash.CreateTimeStamp(fileInfo.LastWriteTimeUtc, fileInfo.Length);
|
||||||
|
|
||||||
|
if (newHash != cachedHash)
|
||||||
|
needsRebuild = true;
|
||||||
|
}
|
||||||
|
else if (source.Mode == WatchMode.Content)
|
||||||
|
{
|
||||||
|
string hash = ComputeFileHash(fileInfo.FullName);
|
||||||
|
newHash = SourceHash.CreateHash(hash, fileInfo.Length);
|
||||||
|
|
||||||
|
if (newHash != cachedHash)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Needs rebuild");
|
||||||
|
needsRebuild = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Unsupported file watch mode: " + source.Mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (source.Mode == WatchMode.Metadata)
|
||||||
|
{
|
||||||
|
newHash = SourceHash.CreateTimeStamp(DateTime.MinValue, -1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newHash = SourceHash.CreateHash(string.Empty, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cacheEntryFound || newHash != cachedHash)
|
||||||
|
{
|
||||||
|
// If we didn't have a cache entry before then this is the first time building.
|
||||||
|
// If we had a cache entry before we need to check whether it didn't exist before.
|
||||||
|
needsRebuild = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheInfo.SetNewHash(source.Path, newHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return needsRebuild;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ComputeFileHash(string filePath)
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(filePath);
|
||||||
|
using var sha256 = SHA256.Create();
|
||||||
|
|
||||||
|
byte[] hash = sha256.ComputeHash(stream);
|
||||||
|
return Convert.ToHexString(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateCacheInfo(BuildInfo buildInfo, ModuleCache moduleCache, bool success)
|
||||||
|
{
|
||||||
|
if (buildInfo.BuildDebug)
|
||||||
|
{
|
||||||
|
ModuleInfo cacheInfo = moduleCache.GetModuleInfo(Name, BuildConfig.Debug);
|
||||||
|
cacheInfo.LastSuccessfulBuild = success ? DateTime.UtcNow : DateTime.MinValue;
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
cacheInfo.ResetHashes();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildInfo.BuildRelease)
|
||||||
|
{
|
||||||
|
ModuleInfo cacheInfo = moduleCache.GetModuleInfo(Name, BuildConfig.Release);
|
||||||
|
cacheInfo.LastSuccessfulBuild = success ? DateTime.UtcNow : DateTime.MinValue;
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
cacheInfo.ResetHashes();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace BuildTool;
|
||||||
|
|
||||||
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
|
[JsonSerializable(typeof(Dictionary<string, ModuleInfo>))]
|
||||||
|
internal partial class SourceGenerationContext : JsonSerializerContext { }
|
||||||
|
|
||||||
|
internal class ModuleCache
|
||||||
|
{
|
||||||
|
private Dictionary<string, ModuleInfo> _modules = new();
|
||||||
|
|
||||||
|
public static ModuleCache Load(string filePath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
return new ModuleCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
using FileStream stream = File.OpenRead(filePath);
|
||||||
|
Dictionary<string, ModuleInfo>? modules =
|
||||||
|
JsonSerializer.Deserialize(stream, SourceGenerationContext.Default.DictionaryStringModuleInfo);
|
||||||
|
|
||||||
|
return new ModuleCache()
|
||||||
|
{
|
||||||
|
_modules = modules ?? new Dictionary<string, ModuleInfo>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(string filePath)
|
||||||
|
{
|
||||||
|
using FileStream stream = File.Create(filePath);
|
||||||
|
JsonSerializer.Serialize(stream, _modules, SourceGenerationContext.Default.DictionaryStringModuleInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetModuleKey(string moduleName, BuildConfig configuration) =>
|
||||||
|
$"{moduleName}: {configuration}";
|
||||||
|
|
||||||
|
public void SetModuleInfo(ModuleInfo moduleInfo)
|
||||||
|
{
|
||||||
|
_modules[GetModuleKey(moduleInfo.Name, moduleInfo.Configuration)] = moduleInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModuleInfo GetModuleInfo(string moduleName, BuildConfig configuration)
|
||||||
|
{
|
||||||
|
if (_modules.TryGetValue(GetModuleKey(moduleName, configuration), out ModuleInfo? moduleInfo))
|
||||||
|
{
|
||||||
|
return moduleInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleInfo info = new()
|
||||||
|
{
|
||||||
|
Name = moduleName,
|
||||||
|
Configuration = configuration,
|
||||||
|
LastSuccessfulBuild = DateTime.MinValue
|
||||||
|
};
|
||||||
|
|
||||||
|
SetModuleInfo(info);
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BuildConfig
|
||||||
|
{
|
||||||
|
Debug,
|
||||||
|
Release
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ModuleInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public BuildConfig Configuration { get; set; }
|
||||||
|
|
||||||
|
public DateTime LastSuccessfulBuild { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private Dictionary<string, SourceHash> _cachedSourceHashes = new();
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private Dictionary<string, SourceHash> _newSourceHashes = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes <see cref="_newSourceHashes"/> and deserializes into <see cref="_cachedSourceHashes"/>.
|
||||||
|
/// </summary>
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("hashes")]
|
||||||
|
internal Dictionary<string, SourceHash> Hashes
|
||||||
|
{
|
||||||
|
get => _newSourceHashes;
|
||||||
|
set => _cachedSourceHashes = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetCachedHash(string fileName, out SourceHash cachedHash)
|
||||||
|
{
|
||||||
|
return _cachedSourceHashes.TryGetValue(fileName, out cachedHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetNewHash(string fileName, SourceHash hash)
|
||||||
|
{
|
||||||
|
_newSourceHashes[fileName] = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetHashes()
|
||||||
|
{
|
||||||
|
_cachedSourceHashes = _newSourceHashes;
|
||||||
|
_newSourceHashes = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HashType
|
||||||
|
{
|
||||||
|
Hash,
|
||||||
|
Date
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SourceHash : IEquatable<SourceHash>
|
||||||
|
{
|
||||||
|
public HashType Type { get; init; }
|
||||||
|
public string? Hash { get; init; }
|
||||||
|
public long Length { get; init; }
|
||||||
|
|
||||||
|
public static SourceHash CreateHash(string hash, long length)
|
||||||
|
{
|
||||||
|
return new SourceHash()
|
||||||
|
{
|
||||||
|
Type = HashType.Hash,
|
||||||
|
Hash = hash,
|
||||||
|
Length = length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SourceHash CreateTimeStamp(DateTime timeStamp, long length)
|
||||||
|
{
|
||||||
|
return new SourceHash()
|
||||||
|
{
|
||||||
|
Type = HashType.Date,
|
||||||
|
Hash = timeStamp.ToString("o") // ISO 8601 format
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(SourceHash left, SourceHash right) => left.Equals(right);
|
||||||
|
public static bool operator !=(SourceHash left, SourceHash right) => !left.Equals(right);
|
||||||
|
|
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||||
|
{
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(SourceHash other)
|
||||||
|
{
|
||||||
|
return Type == other.Type && Hash == other.Hash && Length == other.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine((int)Type, Hash, Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ class ScriptCoreModule : Module
|
|||||||
|
|
||||||
public override async Task<bool> Run(BuildInfo buildInfo)
|
public override async Task<bool> Run(BuildInfo buildInfo)
|
||||||
{
|
{
|
||||||
string projectFile = Path.Join(buildInfo.WorkingDirectory.WorkspaceRoot, "ScriptCore/ScriptCore.csproj");
|
string projectFile = Path.Join(buildInfo.WorkingDirectory.WorkspaceRoot, "ScriptCore", "ScriptCore.csproj");
|
||||||
|
|
||||||
if (buildInfo.BuildDebug)
|
if (buildInfo.BuildDebug)
|
||||||
{
|
{
|
||||||
@@ -26,4 +26,25 @@ class ScriptCoreModule : Module
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override List<WatchedSource> GetWatchedSources(BuildInfo buildInfo)
|
||||||
|
{
|
||||||
|
List<WatchedSource> sources = new();
|
||||||
|
|
||||||
|
// Watch ScriptGlue-Definitions file by content
|
||||||
|
sources.Add(new WatchedSource("generated/ScriptGlue.json", WatchMode.Content));
|
||||||
|
|
||||||
|
// Watch source files by metadata
|
||||||
|
sources.Add(new WatchedSource("ScriptCore/", WatchMode.Metadata)
|
||||||
|
{
|
||||||
|
Recursive = true,
|
||||||
|
ExcludedDirectories =
|
||||||
|
[
|
||||||
|
"bin",
|
||||||
|
"obj"
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,10 @@
|
|||||||
"BuildTool": {
|
"BuildTool": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "build All --debug --workspace \"$(ProjectDir)/../../\""
|
"commandLineArgs": "build All --debug --workspace \"$(ProjectDir)/../../\""
|
||||||
|
},
|
||||||
|
"Build ScriptCore": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "build ScriptCore --debug -n --workspace \"$(ProjectDir)/../../\""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
namespace BuildTool;
|
||||||
|
|
||||||
|
public enum WatchMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the files/directories have changed by comparing their metadata (e.g. last modified time).
|
||||||
|
/// </summary>
|
||||||
|
Metadata,
|
||||||
|
/// <summary>
|
||||||
|
/// For files only, check if the content of the file has changed by comparing a hash of the file contents.
|
||||||
|
/// </summary>
|
||||||
|
Content
|
||||||
|
}
|
||||||
|
|
||||||
|
internal record WatchedSource(string Path, WatchMode Mode)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Path to a file or directory to be checked for changes.
|
||||||
|
/// </summary>
|
||||||
|
public string Path { get; init; } = Path;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether to check for changes in the file's metadata (e.g. last modified time) or content (e.g. hash of the file contents).
|
||||||
|
/// </summary>
|
||||||
|
public WatchMode Mode { get; init; } = Mode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Only if Path points to a directory, a list of subdirectories to exclude from change checking.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> ExcludedDirectories { get; init; } = new();
|
||||||
|
/// <summary>
|
||||||
|
/// Only if Path points to a directory, whether to check for changes in subdirectories as well.
|
||||||
|
/// </summary>
|
||||||
|
public bool Recursive { get; init; } = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Run the BuildTool project as release, pass all arguments to it.
|
||||||
|
dotnet run --project bin/BuildTool/BuildTool.csproj --configuration Release -- $args
|
||||||
Reference in New Issue
Block a user