diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8d20e10 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,69 @@ +name: Releasing ModVerify + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + +# Builds and tests the solution. + test: + uses: ./.github/workflows/test.yml + + pack: + name: Pack + needs: [test] + runs-on: windows-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + - name: Setup .NET + uses: actions/setup-dotnet@v4 + - name: Create NetFramework Release + run: dotnet publish .\src\ModVerify.CliApp\ModVerify.CliApp.csproj --configuration Release -f net48 --output ./releases/net48 + - name: Create Net Core Release + run: dotnet publish .\src\ModVerify.CliApp\ModVerify.CliApp.csproj --configuration Release -f net8.0 --output ./releases/net8.0 + - name: Upload a Build Artifact + uses: actions/upload-artifact@v4 + with: + name: Binary Releases + path: ./releases + if-no-files-found: error + retention-days: 1 + + deploy: + name: Deploy + if: | + github.ref == 'refs/heads/main' && github.event_name == 'push' + needs: [pack] + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/download-artifact@v4 + with: + name: Binary Releases + path: ./releases + - name: Create NET Core zip + # Change into the artifacts directory to avoid including the directory itself in the zip archive + working-directory: ./releases/net8.0 + run: zip -r ./releases/ModVerify-Net8.zip . + - uses: dotnet/nbgv@v0.4.2 + id: nbgv + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + name: v${{ steps.nbgv.outputs.SemVer2 }} + tag_name: v${{ steps.nbgv.outputs.SemVer2 }} + token: ${{ secrets.GITHUB_TOKEN }} + generate_release_notes: true + files: | + ./releases/net48/ModVerify.CliApp.exe + ./releases/ModVerify-Net8.zip \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a34d7ac..20a36cc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,12 +1,12 @@ -name: Petroglyph Tools .NET Build & Test +name: ModVerify Build & Test on: workflow_call: workflow_dispatch: push: - branches: [ main ] + branches: [ develop ] pull_request: - branches: [ main ] + branches: [ develop ] jobs: build-test: diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..1dda2dd --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,9 @@ + + + + + all + 3.6.133 + + + \ No newline at end of file diff --git a/src/ModVerify.CliApp/FodyWeavers.xml b/src/ModVerify.CliApp/FodyWeavers.xml new file mode 100644 index 0000000..5029e70 --- /dev/null +++ b/src/ModVerify.CliApp/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerify.CliApp.csproj b/src/ModVerify.CliApp/ModVerify.CliApp.csproj index 82c06b5..66924d6 100644 --- a/src/ModVerify.CliApp/ModVerify.CliApp.csproj +++ b/src/ModVerify.CliApp/ModVerify.CliApp.csproj @@ -18,6 +18,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/ModVerify.CliApp/Program.cs b/src/ModVerify.CliApp/Program.cs index f3a46f1..b4157d8 100644 --- a/src/ModVerify.CliApp/Program.cs +++ b/src/ModVerify.CliApp/Program.cs @@ -80,7 +80,7 @@ static async Task Main(string[] args) } } - private static VerifyFocPipeline BuildPipeline(IPlayableObject playableObject, IGame fallbackGame) + private static VerifyGamePipeline BuildPipeline(IPlayableObject playableObject, IGame fallbackGame) { IList mods = Array.Empty(); if (playableObject is IMod mod) @@ -97,7 +97,9 @@ private static VerifyFocPipeline BuildPipeline(IPlayableObject playableObject, I playableObject.Game.Directory.FullName, fallbackGame.Directory.FullName); - return new VerifyFocPipeline(gameLocations, VerificationSettings.Default, _services); + var repo = _services.GetRequiredService().Create(GameEngineType.Foc, gameLocations); + + return new VerifyGamePipeline(repo, VerificationSettings.Default, _services); } private static IServiceProvider CreateAppServices() diff --git a/src/ModVerify/GameVerificationException.cs b/src/ModVerify/GameVerificationException.cs index 036dfa4..0844557 100644 --- a/src/ModVerify/GameVerificationException.cs +++ b/src/ModVerify/GameVerificationException.cs @@ -11,6 +11,10 @@ public sealed class GameVerificationException(IEnumerable private readonly string? _error = null; private readonly IEnumerable _failedSteps = failedSteps ?? throw new ArgumentNullException(nameof(failedSteps)); + public GameVerificationException(GameVerificationStep step) : this([step]) + { + } + /// public override string Message => Error; diff --git a/src/ModVerify/ModVerify.csproj b/src/ModVerify/ModVerify.csproj index a77fbcc..8febcd9 100644 --- a/src/ModVerify/ModVerify.csproj +++ b/src/ModVerify/ModVerify.csproj @@ -25,6 +25,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/ModVerify/Steps/GameVerificationStep.cs b/src/ModVerify/Steps/GameVerificationStep.cs index 6a7df65..295cd70 100644 --- a/src/ModVerify/Steps/GameVerificationStep.cs +++ b/src/ModVerify/Steps/GameVerificationStep.cs @@ -12,7 +12,11 @@ namespace AET.ModVerify.Steps; -public abstract class GameVerificationStep(CreateGameDatabaseStep createDatabaseStep, IGameRepository repository, IServiceProvider serviceProvider) +public abstract class GameVerificationStep( + CreateGameDatabaseStep createDatabaseStep, + IGameRepository repository, + VerificationSettings settings, + IServiceProvider serviceProvider) : PipelineStep(serviceProvider) { protected readonly IFileSystem FileSystem = serviceProvider.GetRequiredService(); @@ -22,6 +26,8 @@ public abstract class GameVerificationStep(CreateGameDatabaseStep createDatabase public IReadOnlyCollection VerifyErrors => _verifyErrors; + protected VerificationSettings Settings { get; } = settings; + protected GameDatabase Database { get; private set; } = null!; protected IGameRepository Repository => repository; @@ -34,7 +40,8 @@ protected sealed override void RunCore(CancellationToken token) { createDatabaseStep.Wait(); Database = createDatabaseStep.GameDatabase; - + + Logger?.LogInformation($"Running verifier '{Name}'..."); try { _errorLog = CreateVerificationLogFile(); @@ -42,6 +49,7 @@ protected sealed override void RunCore(CancellationToken token) } finally { + Logger?.LogInformation($"Finished verifier '{Name}'"); _errorLog.Dispose(); _errorLog = null!; } @@ -57,9 +65,11 @@ protected void AddError(VerificationError error) Logger?.LogTrace($"Error suppressed: '{error}'"); return; } - _verifyErrors.Add(error); _errorLog.WriteLine(error.Message); + + if (Settings.ThrowBehavior == VerifyThrowBehavior.FailFast) + throw new GameVerificationException(this); } protected virtual bool OnError(VerificationError error) diff --git a/src/ModVerify/Steps/VerifyReferencedModelsStep.cs b/src/ModVerify/Steps/VerifyReferencedModelsStep.cs index 293a8c3..5151e5b 100644 --- a/src/ModVerify/Steps/VerifyReferencedModelsStep.cs +++ b/src/ModVerify/Steps/VerifyReferencedModelsStep.cs @@ -17,8 +17,12 @@ namespace AET.ModVerify.Steps; -internal sealed class VerifyReferencedModelsStep(CreateGameDatabaseStep createDatabaseStep, IGameRepository repository, IServiceProvider serviceProvider) - : GameVerificationStep(createDatabaseStep, repository, serviceProvider) +internal sealed class VerifyReferencedModelsStep( + CreateGameDatabaseStep createDatabaseStep, + IGameRepository repository, + VerificationSettings settings, + IServiceProvider serviceProvider) + : GameVerificationStep(createDatabaseStep, repository, settings, serviceProvider) { public const string ModelNotFound = "ALO00"; public const string ModelBroken = "ALO01"; diff --git a/src/ModVerify/VerificationSettings.cs b/src/ModVerify/VerificationSettings.cs new file mode 100644 index 0000000..ab5e618 --- /dev/null +++ b/src/ModVerify/VerificationSettings.cs @@ -0,0 +1,11 @@ +namespace AET.ModVerify; + +public record VerificationSettings +{ + public static readonly VerificationSettings Default = new() + { + ThrowBehavior = VerifyThrowBehavior.None + }; + + public VerifyThrowBehavior ThrowBehavior { get; init; } +} \ No newline at end of file diff --git a/src/ModVerify/VerifyPipeline.cs b/src/ModVerify/VerifyGamePipeline.cs similarity index 71% rename from src/ModVerify/VerifyPipeline.cs rename to src/ModVerify/VerifyGamePipeline.cs index 86091cd..8b51723 100644 --- a/src/ModVerify/VerifyPipeline.cs +++ b/src/ModVerify/VerifyGamePipeline.cs @@ -11,36 +11,26 @@ namespace AET.ModVerify; - -public record VerificationSettings -{ - public static readonly VerificationSettings Default = new(); - - public bool ThrowOnError { get; init; } -} - - -public class VerifyFocPipeline : ParallelPipeline +public class VerifyGamePipeline : ParallelPipeline { private IList _verificationSteps = null!; - private readonly GameLocations _gameLocations; + private readonly IGameRepository _repository; private readonly VerificationSettings _settings; - public VerifyFocPipeline(GameLocations gameLocations, VerificationSettings settings, IServiceProvider serviceProvider) + public VerifyGamePipeline(IGameRepository gameRepository, VerificationSettings settings, IServiceProvider serviceProvider) : base(serviceProvider, 4, false) { - _gameLocations = gameLocations; + _repository = gameRepository; _settings = settings; } protected override Task> BuildSteps() { - var repository = new FocGameRepository(_gameLocations, ServiceProvider); + var buildIndexStep = new CreateGameDatabaseStep(_repository, ServiceProvider); - var buildIndexStep = new CreateGameDatabaseStep(repository, ServiceProvider); _verificationSteps = new List { - new VerifyReferencedModelsStep(buildIndexStep, repository, ServiceProvider), + new VerifyReferencedModelsStep(buildIndexStep, _repository, _settings, ServiceProvider), }; var allSteps = new List @@ -71,7 +61,7 @@ public override async Task RunAsync(CancellationToken token = default) } } - if (_settings.ThrowOnError && failedSteps.Count > 0) + if (_settings.ThrowBehavior == VerifyThrowBehavior.FinalThrow && failedSteps.Count > 0) throw new GameVerificationException(stepsWithVerificationErrors); } finally diff --git a/src/ModVerify/VerifyThrowBehavior.cs b/src/ModVerify/VerifyThrowBehavior.cs new file mode 100644 index 0000000..d1e9192 --- /dev/null +++ b/src/ModVerify/VerifyThrowBehavior.cs @@ -0,0 +1,8 @@ +namespace AET.ModVerify; + +public enum VerifyThrowBehavior +{ + None, + FinalThrow, + FailFast +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/GameRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/GameRepository.cs index 37915f5..f7b1c63 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/GameRepository.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/GameRepository.cs @@ -34,6 +34,8 @@ public class FocGameRepository : IGameRepository private readonly IVirtualMegArchive? _masterMegArchive; + public GameEngineType EngineType => GameEngineType.Foc; + public IRepository EffectsRepository { get; } public FocGameRepository(GameLocations gameLocations, IServiceProvider serviceProvider) diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/GameRepositoryFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/GameRepositoryFactory.cs new file mode 100644 index 0000000..02e24d2 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/GameRepositoryFactory.cs @@ -0,0 +1,13 @@ +using System; + +namespace PG.StarWarsGame.Engine.FileSystem; + +internal sealed class GameRepositoryFactory(IServiceProvider serviceProvider) : IGameRepositoryFactory +{ + public IGameRepository Create(GameEngineType engineType, GameLocations gameLocations) + { + if (engineType == GameEngineType.Eaw) + throw new NotImplementedException("Empire at War is currently not supported."); + return new FocGameRepository(gameLocations, serviceProvider); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/IGameRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/IGameRepository.cs index 788d0f2..8965189 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/IGameRepository.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/IGameRepository.cs @@ -2,7 +2,9 @@ public interface IGameRepository : IRepository { - IRepository EffectsRepository { get; } + public GameEngineType EngineType { get; } - bool FileExists(string filePath, string[] extensions, bool megFileOnly = false); + IRepository EffectsRepository { get; } + + bool FileExists(string filePath, string[] extensions, bool megFileOnly = false); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/IGameRepositoryFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/IGameRepositoryFactory.cs new file mode 100644 index 0000000..ed6600f --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/FileSystem/IGameRepositoryFactory.cs @@ -0,0 +1,6 @@ +namespace PG.StarWarsGame.Engine.FileSystem; + +public interface IGameRepositoryFactory +{ + IGameRepository Create(GameEngineType engineType, GameLocations gameLocations); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameDatabase.cs index a30a206..7abf073 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameDatabase.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameDatabase.cs @@ -5,6 +5,8 @@ namespace PG.StarWarsGame.Engine; public class GameDatabase { + public required GameEngineType EngineType { get; init; } + public required GameConstants GameConstants { get; init; } public required IList GameObjects { get; init; } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameEngineType.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameEngineType.cs new file mode 100644 index 0000000..962c389 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameEngineType.cs @@ -0,0 +1,16 @@ +namespace PG.StarWarsGame.Engine; + +/// +/// The type of engine can be either "Empire at War" of "Forces of Corruption" +/// +public enum GameEngineType +{ + /// + /// Empire at War + /// + Eaw = 0, + /// + /// Forces of Corruption + /// + Foc = 1 +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs index fd5364e..f1ff96c 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Engine.FileSystem; using PG.StarWarsGame.Engine.Language; using PG.StarWarsGame.Engine.Xml; @@ -8,6 +9,7 @@ public static class PetroglyphEngineServiceContribution { public static void ContributeServices(IServiceCollection serviceCollection) { + serviceCollection.AddSingleton(sp => new GameRepositoryFactory(sp)); serviceCollection.AddSingleton(sp => new GameLanguageManager(sp)); serviceCollection.AddSingleton(sp => new PetroglyphXmlFileParserFactory(sp)); } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/CreateGameDatabasePipeline.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/CreateGameDatabasePipeline.cs index 2da1ef1..53fb102 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/CreateGameDatabasePipeline.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/CreateGameDatabasePipeline.cs @@ -75,6 +75,7 @@ protected override async Task RunCoreAsync(CancellationToken token) GameDatabase = new GameDatabase { + EngineType = repository.EngineType, GameConstants = _parseGameConstants.Database, GameObjects = _parseGameObjects.Database }; diff --git a/version.json b/version.json new file mode 100644 index 0000000..251e6e2 --- /dev/null +++ b/version.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.0-alpha", + "publicReleaseRefSpec": [ + "^refs/heads/main$" + ], + "assemblyVersion" : { + "precision": "major" + }, + "cloudBuild": { + "buildNumber": { + "enabled": true + } + }, + "nugetPackageVersion": { + "semVer": 2 + } +} \ No newline at end of file