From 6773eb9ce6a184472a0b3eaa8ccd8d958962300b Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Tue, 17 Dec 2024 14:07:17 +0100 Subject: [PATCH 01/17] Started on splitting up GitHub release into smaller parts --- .../ExplicitTests/GitHubReleaseCookTests.cs | 10 +-- .../Services/ComposerOrderingTests.cs | 10 +-- .../Composers/GitHubReleaseComposer.cs | 33 +++------ .../Bake/Cooking/Composers/ReleaseComposer.cs | 70 +++++++++++++++++++ Source/Bake/Cooking/Cooks/.gitignore | 1 + .../Cooking/Cooks/GitHub/GitHubReleaseCook.cs | 8 +-- .../Bake/Cooking/Cooks/Release/ReleaseCook.cs | 37 ++++++++++ .../Gathers/DynamicDestinationGather.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 4 ++ Source/Bake/Names.cs | 10 ++- Source/Bake/Services/ComposerOrdering.cs | 3 - Source/Bake/ValueObjects/.gitignore | 1 + .../Bake/ValueObjects/Artifacts/Artifact.cs | 2 +- .../ValueObjects/Artifacts/ArtifactType.cs | 1 + .../Artifacts/HelmChartArtifact.cs | 4 -- .../ValueObjects/Artifacts/ReleaseArtifact.cs | 57 +++++++++++++++ .../Recipes/GitHub/GitHubReleaseRecipe.cs | 17 ++--- .../Recipes/Release/ReleaseRecipe.cs | 54 ++++++++++++++ 18 files changed, 258 insertions(+), 66 deletions(-) create mode 100644 Source/Bake/Cooking/Composers/ReleaseComposer.cs create mode 100644 Source/Bake/Cooking/Cooks/.gitignore create mode 100644 Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs create mode 100644 Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs create mode 100644 Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs diff --git a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs index b41521d7..d1bca98c 100644 --- a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs +++ b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs @@ -49,16 +49,8 @@ public async Task CreateRelease() "testtest", new Uri("https://github.com/rasmus/testtest"), new Uri("https://api.guthub.com/")), - SemVer.Random, "a108d8a38b4ac154172cb7eeea8530e316ead798", - new ReleaseNotes(SemVer.Random, "This is a test"), - new Artifact[] - { - new ExecutableArtifact( - "test_linux", - Path.Combine(WorkingDirectory, "README.md"), - new Platform(ExecutableOperatingSystem.Linux, ExecutableArchitecture.Intel64)) - }); + new ReleaseArtifact(SemVer.Random, new ReleaseNotes(SemVer.Random, string.Empty))); // Arrange var result = await Sut.CookAsync( diff --git a/Source/Bake.Tests/UnitTests/Services/ComposerOrderingTests.cs b/Source/Bake.Tests/UnitTests/Services/ComposerOrderingTests.cs index 01023f16..adf0b40f 100644 --- a/Source/Bake.Tests/UnitTests/Services/ComposerOrderingTests.cs +++ b/Source/Bake.Tests/UnitTests/Services/ComposerOrderingTests.cs @@ -33,7 +33,7 @@ namespace Bake.Tests.UnitTests.Services { public class ComposerOrderingTests : TestFor { - private static readonly IReadOnlyCollection EmptyArtifactTypes = new ArtifactType[] { }; + private static readonly IReadOnlyCollection EmptyArtifactTypes = []; [Test] public void BasicOrdering() @@ -42,7 +42,7 @@ public void BasicOrdering() var composers = new[] { DummyProducer("E", ArtifactType.Executable), - Dummy("R", ArtifactType.Executable, ArtifactType.Release), + Dummy("R", ArtifactType.Executable, ArtifactType.GitHubRelease), DummyProducer("E", ArtifactType.Executable), }; @@ -102,12 +102,12 @@ private static IReadOnlyCollection GetNames(IEnumerable compo private static IComposer Dummy(string name, ArtifactType consume, ArtifactType produce) => new DummyComposer( name, - new[] { consume }, - new[] { produce }); + [consume], + [produce]); private class DummyComposer : IComposer { - private static readonly Task> EmptyRecipes = Task.FromResult>(new Recipe[] { }); + private static readonly Task> EmptyRecipes = Task.FromResult>([]); public string Name { get; } public IReadOnlyCollection Produces { get; } diff --git a/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs b/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs index ffa7e498..8fd3613f 100644 --- a/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs +++ b/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs @@ -33,17 +33,8 @@ public class GitHubReleaseComposer : Composer { private readonly IConventionInterpreter _conventionInterpreter; - public override IReadOnlyCollection Consumes { get; } = new[] - { - ArtifactType.NuGet, - ArtifactType.Executable, - ArtifactType.DocumentationSite, - ArtifactType.Container - }; - public override IReadOnlyCollection Produces { get; } = new[] - { - ArtifactType.Release, - }; + public override IReadOnlyCollection Consumes { get; } = [ArtifactType.Release]; + public override IReadOnlyCollection Produces { get; } = [ArtifactType.GitHubRelease]; public GitHubReleaseComposer( IConventionInterpreter conventionInterpreter) @@ -75,26 +66,20 @@ public override Task> ComposeAsync( return Task.FromResult(EmptyRecipes); } - var artifacts = Enumerable.Empty() - .Concat(context.GetArtifacts()) - .Concat(context.GetArtifacts()) - .Concat(context.GetArtifacts()) - .ToArray(); + var release = context.GetArtifacts().SingleOrDefault(); - if (!artifacts.Any()) + if (release == null) { return Task.FromResult(EmptyRecipes); } - return Task.FromResult>(new[] - { - new GitHubReleaseRecipe( + return Task.FromResult>( + [ + new GitHubReleaseRecipe( context.Ingredients.GitHub, - context.Ingredients.Version, context.Ingredients.Git.Sha, - context.Ingredients.ReleaseNotes!, - artifacts) - }); + release) + ]); } } } diff --git a/Source/Bake/Cooking/Composers/ReleaseComposer.cs b/Source/Bake/Cooking/Composers/ReleaseComposer.cs new file mode 100644 index 00000000..045ed2dd --- /dev/null +++ b/Source/Bake/Cooking/Composers/ReleaseComposer.cs @@ -0,0 +1,70 @@ +// MIT License +// +// Copyright (c) 2021-2024 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using Bake.Exceptions; +using Bake.ValueObjects.Artifacts; +using Bake.ValueObjects.Recipes; +using Bake.ValueObjects.Recipes.Release; + +namespace Bake.Cooking.Composers +{ + public class ReleaseComposer : Composer + { + public override IReadOnlyCollection Consumes { get; } = + [ + ArtifactType.NuGet, + ArtifactType.Executable, + ArtifactType.DocumentationSite, + ArtifactType.Container + ]; + + public override IReadOnlyCollection Produces { get; } = [ArtifactType.Release]; + + public override Task> ComposeAsync( + IContext context, CancellationToken cancellationToken) + { + if (context.Ingredients.Git == null) + { + throw new BuildFailedException("Missing git or GitHub information!"); + } + + var artifacts = Enumerable.Empty() + .Concat(context.GetArtifacts()) + .Concat(context.GetArtifacts()) + .Concat(context.GetArtifacts()) + .ToArray(); + + if (!artifacts.Any()) + { + return Task.FromResult(EmptyRecipes); + } + + return Task.FromResult>( + [ + new ReleaseRecipe( + context.Ingredients.Version, + context.Ingredients.ReleaseNotes!, + artifacts) + ]); + } + } +} diff --git a/Source/Bake/Cooking/Cooks/.gitignore b/Source/Bake/Cooking/Cooks/.gitignore new file mode 100644 index 00000000..3a2535e1 --- /dev/null +++ b/Source/Bake/Cooking/Cooks/.gitignore @@ -0,0 +1 @@ +!Release diff --git a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs index 626ce153..497e9e3f 100644 --- a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs +++ b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs @@ -79,11 +79,11 @@ protected override async Task CookAsync( var stringBuilder = new StringBuilder(); - if (recipe.ReleaseNotes != null) + if (recipe.Release.ReleaseNotes != null) { stringBuilder .AppendLine("### Release notes") - .AppendLine(recipe.ReleaseNotes.Notes) + .AppendLine(recipe.Release.ReleaseNotes.Notes) .AppendLine(); } @@ -163,8 +163,8 @@ protected override async Task CookAsync( } } - var release = new Release( - recipe.Version, + var release = new ValueObjects.Release( + recipe.Release.Version, recipe.Sha, stringBuilder.ToString(), releaseFiles); diff --git a/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs new file mode 100644 index 00000000..ec301f0b --- /dev/null +++ b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs @@ -0,0 +1,37 @@ +// MIT License +// +// Copyright (c) 2021-2024 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using Bake.ValueObjects.Recipes.Release; + +namespace Bake.Cooking.Cooks.Release +{ + public class ReleaseCook : Cook + { + protected override Task CookAsync( + IContext context, + ReleaseRecipe recipe, + CancellationToken cancellationToken) + { + return Task.FromResult(true); + } + } +} diff --git a/Source/Bake/Cooking/Ingredients/Gathers/DynamicDestinationGather.cs b/Source/Bake/Cooking/Ingredients/Gathers/DynamicDestinationGather.cs index 8362732e..16335a51 100644 --- a/Source/Bake/Cooking/Ingredients/Gathers/DynamicDestinationGather.cs +++ b/Source/Bake/Cooking/Ingredients/Gathers/DynamicDestinationGather.cs @@ -76,7 +76,7 @@ public async Task GatherAsync( await ExtractContainerDestinationAsync(ingredients, dynamicDestination); break; - case ArtifactType.Release: + case ArtifactType.GitHubRelease: await ExtractReleaseDestinationAsync(ingredients, dynamicDestination); break; diff --git a/Source/Bake/Extensions/ServiceCollectionExtensions.cs b/Source/Bake/Extensions/ServiceCollectionExtensions.cs index f3cce426..047f263b 100644 --- a/Source/Bake/Extensions/ServiceCollectionExtensions.cs +++ b/Source/Bake/Extensions/ServiceCollectionExtensions.cs @@ -38,6 +38,7 @@ using Bake.Cooking.Cooks.OctopusDeploy; using Bake.Cooking.Cooks.Pip; using Bake.Cooking.Cooks.Python; +using Bake.Cooking.Cooks.Release; using Bake.Cooking.Ingredients.Gathers; using Bake.Core; using Bake.Services; @@ -112,6 +113,7 @@ public static IServiceCollection AddBake( .AddTransient() .AddTransient() .AddTransient() + .AddTransient() // Cooks - .NET .AddTransient() @@ -140,6 +142,8 @@ public static IServiceCollection AddBake( .AddTransient() // Cooks - GitHub .AddTransient() + // Cooks - Releases + .AddTransient() // NodeJS / NPM .AddTransient() .AddTransient() diff --git a/Source/Bake/Names.cs b/Source/Bake/Names.cs index 1d1ddaa3..72fbfbab 100644 --- a/Source/Bake/Names.cs +++ b/Source/Bake/Names.cs @@ -61,6 +61,7 @@ public static class Artifacts public const string DirectoryArtifact = "directory-artifact"; public const string DocumentationSiteArtifact = "documentation-site-artifact"; public const string HelmChartArtifact = "helm-chart-artifact"; + public const string ReleaseArtifact = "release-artifact"; } public static class ArtifactTypes @@ -80,7 +81,7 @@ public static class ArtifactTypes [ArtifactType.Dockerfile] = Dockerfile, [ArtifactType.DotNetPublishedDirectory] = DotNetPublishedDirectory, [ArtifactType.NuGet] = NuGet, - [ArtifactType.Release] = Release, + [ArtifactType.GitHubRelease] = Release, [ArtifactType.Executable] = Executable, [ArtifactType.DocumentationSite] = DocumentationSite, }; @@ -124,7 +125,7 @@ public static class Python public static class GitHub { - public const string Release = "github-release"; + public const string GitHubRelease = "github-release"; } public static class Helm @@ -133,6 +134,11 @@ public static class Helm public const string Package = "helm-package"; } + public static class Releases + { + public const string Release = "release"; + } + public static class MkDocs { public const string Release = "mkdocs-build"; diff --git a/Source/Bake/Services/ComposerOrdering.cs b/Source/Bake/Services/ComposerOrdering.cs index 1661d4c6..b3b40360 100644 --- a/Source/Bake/Services/ComposerOrdering.cs +++ b/Source/Bake/Services/ComposerOrdering.cs @@ -20,9 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System; -using System.Collections.Generic; -using System.Linq; using Bake.Cooking; using Bake.Extensions; using Microsoft.Extensions.Logging; diff --git a/Source/Bake/ValueObjects/.gitignore b/Source/Bake/ValueObjects/.gitignore index 6a890271..19a13fb1 100644 --- a/Source/Bake/ValueObjects/.gitignore +++ b/Source/Bake/ValueObjects/.gitignore @@ -1 +1,2 @@ !Artifacts +!Release diff --git a/Source/Bake/ValueObjects/Artifacts/Artifact.cs b/Source/Bake/ValueObjects/Artifacts/Artifact.cs index 4958879c..6dc9fd0c 100644 --- a/Source/Bake/ValueObjects/Artifacts/Artifact.cs +++ b/Source/Bake/ValueObjects/Artifacts/Artifact.cs @@ -24,7 +24,7 @@ namespace Bake.ValueObjects.Artifacts { public abstract class Artifact : ValueObject { - public static IReadOnlyCollection Empty { get; } = new Artifact[] { }; + public static IReadOnlyCollection Empty { get; } = []; public abstract IAsyncEnumerable ValidateAsync( CancellationToken cancellationToken); diff --git a/Source/Bake/ValueObjects/Artifacts/ArtifactType.cs b/Source/Bake/ValueObjects/Artifacts/ArtifactType.cs index fd961a0e..60826fab 100644 --- a/Source/Bake/ValueObjects/Artifacts/ArtifactType.cs +++ b/Source/Bake/ValueObjects/Artifacts/ArtifactType.cs @@ -33,6 +33,7 @@ public enum ArtifactType HelmChart, Container, DocumentationSite, + GitHubRelease, Release, } } diff --git a/Source/Bake/ValueObjects/Artifacts/HelmChartArtifact.cs b/Source/Bake/ValueObjects/Artifacts/HelmChartArtifact.cs index 3e1a814b..b12c130c 100644 --- a/Source/Bake/ValueObjects/Artifacts/HelmChartArtifact.cs +++ b/Source/Bake/ValueObjects/Artifacts/HelmChartArtifact.cs @@ -20,10 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System; -using System.Collections.Generic; -using System.IO; - namespace Bake.ValueObjects.Artifacts { [Artifact(Names.Artifacts.HelmChartArtifact)] diff --git a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs new file mode 100644 index 00000000..0c8c9cc6 --- /dev/null +++ b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs @@ -0,0 +1,57 @@ +// MIT License +// +// Copyright (c) 2021-2024 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using Bake.Core; +using YamlDotNet.Serialization; + +namespace Bake.ValueObjects.Artifacts +{ + [Artifact(Names.Artifacts.ReleaseArtifact)] + public class ReleaseArtifact : Artifact + { + [YamlMember] + public SemVer Version { get; [Obsolete] set; } = null!; + + [YamlMember] + public ReleaseNotes? ReleaseNotes { get; [Obsolete] set; } + +#pragma warning disable CS0612 // Type or member is obsolete + public ReleaseArtifact( + SemVer version, + ReleaseNotes? releaseNotes) + { + Version = version; + ReleaseNotes = releaseNotes; + } +#pragma warning restore CS0612 // Type or member is obsolete + + public override IAsyncEnumerable ValidateAsync(CancellationToken cancellationToken) + { + return AsyncEnumerable.Empty(); + } + + public override IEnumerable PrettyNames() + { + yield return "release"; + } + } +} diff --git a/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs b/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs index 184b1863..135a8f29 100644 --- a/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs +++ b/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs @@ -20,44 +20,35 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using Bake.Core; using Bake.ValueObjects.Artifacts; using YamlDotNet.Serialization; namespace Bake.ValueObjects.Recipes.GitHub { - [Recipe(Names.Recipes.GitHub.Release)] + [Recipe(Names.Recipes.GitHub.GitHubRelease)] public class GitHubReleaseRecipe : Recipe { [YamlMember] public GitHubInformation GitHubInformation { get; [Obsolete] set; } = null!; - [YamlMember] - public SemVer Version { get; [Obsolete] set; } = null!; - [YamlMember] public string Sha { get; [Obsolete] set; } = null!; [YamlMember] - public ReleaseNotes? ReleaseNotes { get; [Obsolete] set; } + public ReleaseArtifact Release { get; [Obsolete] set; } = null!; [Obsolete] public GitHubReleaseRecipe() { } public GitHubReleaseRecipe( GitHubInformation gitHubInformation, - SemVer version, string sha, - ReleaseNotes? releaseNotes, - Artifact[] artifacts) - : base(artifacts) + ReleaseArtifact release) { #pragma warning disable CS0612 // Type or member is obsolete GitHubInformation = gitHubInformation; - Version = version; Sha = sha; - ReleaseNotes = releaseNotes; - Artifacts = artifacts; + Release = release; #pragma warning restore CS0612 // Type or member is obsolete } } diff --git a/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs new file mode 100644 index 00000000..7a9ba9f0 --- /dev/null +++ b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs @@ -0,0 +1,54 @@ +// MIT License +// +// Copyright (c) 2021-2024 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using Bake.Core; +using Bake.ValueObjects.Artifacts; +using YamlDotNet.Serialization; + +namespace Bake.ValueObjects.Recipes.Release +{ + [Recipe(Names.Recipes.Releases.Release)] + public class ReleaseRecipe : Recipe + { + [YamlMember] + public SemVer Version { get; [Obsolete] set; } = null!; + + [YamlMember] + public ReleaseNotes? ReleaseNotes { get; [Obsolete] set; } + + [Obsolete] + public ReleaseRecipe() { } + + public ReleaseRecipe( + SemVer version, + ReleaseNotes? releaseNotes, + Artifact[] artifacts) + : base(artifacts) + { +#pragma warning disable CS0612 // Type or member is obsolete + Version = version; + ReleaseNotes = releaseNotes; + Artifacts = artifacts; +#pragma warning restore CS0612 // Type or member is obsolete + } + } +} From b23ee7bb07afded329b1e81b04e06ff309ac19b4 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Tue, 17 Dec 2024 14:29:29 +0100 Subject: [PATCH 02/17] More split --- .../ExplicitTests/GitHubReleaseCookTests.cs | 2 +- .../Bake/Cooking/Composers/ReleaseComposer.cs | 20 ++++--- .../Cooking/Cooks/GitHub/GitHubReleaseCook.cs | 14 ++--- .../OctopusDeployPackagePushCook.cs | 2 +- Source/Bake/Core/File.cs | 4 -- Source/Bake/Core/FileSystem.cs | 2 +- Source/Bake/Core/IFileSystem.cs | 2 +- Source/Bake/Names.cs | 1 + Source/Bake/Services/GitHub.cs | 10 ++-- Source/Bake/Services/Uploader.cs | 2 +- .../Artifacts/ArtifactAttribute.cs | 2 - .../ValueObjects/Artifacts/ReleaseArtifact.cs | 14 ++++- .../Artifacts/ReleaseFileArtifact.cs | 60 +++++++++++++++++++ .../{ReleaseFile.cs => LegacyReleaseFile.cs} | 4 +- Source/Bake/ValueObjects/Release.cs | 4 +- 15 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 Source/Bake/ValueObjects/Artifacts/ReleaseFileArtifact.cs rename Source/Bake/ValueObjects/{ReleaseFile.cs => LegacyReleaseFile.cs} (96%) diff --git a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs index d1bca98c..849ea379 100644 --- a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs +++ b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs @@ -50,7 +50,7 @@ public async Task CreateRelease() new Uri("https://github.com/rasmus/testtest"), new Uri("https://api.guthub.com/")), "a108d8a38b4ac154172cb7eeea8530e316ead798", - new ReleaseArtifact(SemVer.Random, new ReleaseNotes(SemVer.Random, string.Empty))); + new ReleaseArtifact(SemVer.Random, new ReleaseNotes(SemVer.Random, string.Empty), [])); // Arrange var result = await Sut.CookAsync( diff --git a/Source/Bake/Cooking/Composers/ReleaseComposer.cs b/Source/Bake/Cooking/Composers/ReleaseComposer.cs index 045ed2dd..c9e152da 100644 --- a/Source/Bake/Cooking/Composers/ReleaseComposer.cs +++ b/Source/Bake/Cooking/Composers/ReleaseComposer.cs @@ -20,10 +20,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using Bake.Exceptions; using Bake.ValueObjects.Artifacts; using Bake.ValueObjects.Recipes; using Bake.ValueObjects.Recipes.Release; +using Microsoft.Extensions.Logging; namespace Bake.Cooking.Composers { @@ -39,22 +39,28 @@ public class ReleaseComposer : Composer public override IReadOnlyCollection Produces { get; } = [ArtifactType.Release]; - public override Task> ComposeAsync( - IContext context, CancellationToken cancellationToken) + private readonly ILogger _logger; + + public ReleaseComposer( + ILogger logger) { - if (context.Ingredients.Git == null) - { - throw new BuildFailedException("Missing git or GitHub information!"); - } + _logger = logger; + } + public override Task> ComposeAsync( + IContext context, + CancellationToken cancellationToken) + { var artifacts = Enumerable.Empty() .Concat(context.GetArtifacts()) .Concat(context.GetArtifacts()) .Concat(context.GetArtifacts()) + .Concat(context.GetArtifacts()) .ToArray(); if (!artifacts.Any()) { + _logger.LogWarning("No artifacts found for release, skipping release creation!"); return Task.FromResult(EmptyRecipes); } diff --git a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs index 497e9e3f..0c3432e8 100644 --- a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs +++ b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs @@ -74,7 +74,7 @@ protected override async Task CookAsync( Path.Combine(context.Ingredients.WorkingDirectory, "RELEASE_NOTES.md"), } .Where(System.IO.File.Exists) - .Select(p => _fileSystem.Open(p)) + .Select(_fileSystem.Get) .ToArray(); var stringBuilder = new StringBuilder(); @@ -130,8 +130,8 @@ protected override async Task CookAsync( "documentation.zip"); Directory.CreateDirectory(Path.GetDirectoryName(documentationZipFilePath)!); ZipFile.CreateFromDirectory(documentationSite.Path, documentationZipFilePath); - var file = _fileSystem.Open(documentationZipFilePath); - releaseFiles.Add(new ReleaseFile( + var file = _fileSystem.Get(documentationZipFilePath); + releaseFiles.Add(new LegacyReleaseFile( file, $"documentation_v{context.Ingredients.Version}.zip", await file.GetHashAsync(HashAlgorithm.SHA256, cancellationToken))); @@ -177,7 +177,7 @@ await _gitHub.CreateReleaseAsync( return true; } - private async Task> CreateReleaseFilesAsync( + private async Task> CreateReleaseFilesAsync( IReadOnlyCollection additionalFiles, GitHubReleaseRecipe recipe, CancellationToken cancellationToken) @@ -186,20 +186,20 @@ private async Task> CreateReleaseFilesAsync( .OfType() .Select(async artifact => { - var file = _fileSystem.Open(artifact.Path); + var file = _fileSystem.Get(artifact.Path); var fileName = CalculateArtifactFileName(artifact); var compressedFile = await _fileSystem.CompressAsync( fileName, CompressionAlgorithm.ZIP, Enumerable.Empty() .Concat(additionalFiles) - .Concat(new[] {file,}) + .Concat([file]) .ToArray(), cancellationToken); var sha256 = await compressedFile.GetHashAsync( HashAlgorithm.SHA256, cancellationToken); - return new ReleaseFile( + return new LegacyReleaseFile( compressedFile, fileName, sha256); diff --git a/Source/Bake/Cooking/Cooks/OctopusDeploy/OctopusDeployPackagePushCook.cs b/Source/Bake/Cooking/Cooks/OctopusDeploy/OctopusDeployPackagePushCook.cs index a5f8e140..aaffa6e8 100644 --- a/Source/Bake/Cooking/Cooks/OctopusDeploy/OctopusDeployPackagePushCook.cs +++ b/Source/Bake/Cooking/Cooks/OctopusDeploy/OctopusDeployPackagePushCook.cs @@ -86,7 +86,7 @@ private async Task UploadAsync( CancellationToken cancellationToken) { url = new Uri(url, "/api/packages/raw?replace=false"); - var file = _fileSystem.Open(packagePath); + var file = _fileSystem.Get(packagePath); await using var stream = await file.OpenReadAsync(cancellationToken); using var request = new HttpRequestMessage(HttpMethod.Post, url) { diff --git a/Source/Bake/Core/File.cs b/Source/Bake/Core/File.cs index 0da7ee55..4cd0b724 100644 --- a/Source/Bake/Core/File.cs +++ b/Source/Bake/Core/File.cs @@ -20,11 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System; -using System.IO; using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; using HashAlgorithm = Bake.ValueObjects.HashAlgorithm; namespace Bake.Core diff --git a/Source/Bake/Core/FileSystem.cs b/Source/Bake/Core/FileSystem.cs index eaba8470..3b62b2a4 100644 --- a/Source/Bake/Core/FileSystem.cs +++ b/Source/Bake/Core/FileSystem.cs @@ -152,7 +152,7 @@ public async Task ReadAllTextAsync( return await streamReader.ReadToEndAsync(); } - public IFile Open(string filePath) + public IFile Get(string filePath) { return new File(filePath); } diff --git a/Source/Bake/Core/IFileSystem.cs b/Source/Bake/Core/IFileSystem.cs index e2c6a450..d171ae7c 100644 --- a/Source/Bake/Core/IFileSystem.cs +++ b/Source/Bake/Core/IFileSystem.cs @@ -40,7 +40,7 @@ Task ReadAllTextAsync( string filePath, CancellationToken cancellationToken); - IFile Open(string filePath); + IFile Get(string filePath); Task CompressAsync( string fileName, diff --git a/Source/Bake/Names.cs b/Source/Bake/Names.cs index 72fbfbab..e922b1a2 100644 --- a/Source/Bake/Names.cs +++ b/Source/Bake/Names.cs @@ -62,6 +62,7 @@ public static class Artifacts public const string DocumentationSiteArtifact = "documentation-site-artifact"; public const string HelmChartArtifact = "helm-chart-artifact"; public const string ReleaseArtifact = "release-artifact"; + public const string ReleaseFileArtifact = "release-file-artifact"; } public static class ArtifactTypes diff --git a/Source/Bake/Services/GitHub.cs b/Source/Bake/Services/GitHub.cs index b74b7b55..dde15609 100644 --- a/Source/Bake/Services/GitHub.cs +++ b/Source/Bake/Services/GitHub.cs @@ -316,7 +316,7 @@ public async Task> GetPullRequestsAsync( } private async Task UploadFileAsync( - ReleaseFile releaseFile, + LegacyReleaseFile legacyReleaseFile, Octokit.Release gitHubRelease, IGitHubClient gitHubClient, CancellationToken cancellationToken) @@ -324,10 +324,10 @@ private async Task UploadFileAsync( var stopwatch = Stopwatch.StartNew(); _logger.LogDebug( "Uploading releaseFile {FileName} to GitHub release {ReleaseUrl}", - releaseFile.Source.FileName, + legacyReleaseFile.Source.FileName, gitHubRelease.Url); - await using var stream = await releaseFile.Source.OpenReadAsync(cancellationToken); + await using var stream = await legacyReleaseFile.Source.OpenReadAsync(cancellationToken); try { @@ -336,7 +336,7 @@ await gitHubClient.Repository.Release.UploadAsset( new ReleaseAssetUpload { ContentType = "application/octet-stream", - FileName = releaseFile.Destination, + FileName = legacyReleaseFile.Destination, RawData = stream, }, cancellationToken); @@ -349,7 +349,7 @@ await gitHubClient.Repository.Release.UploadAsset( _logger.LogInformation( "Done uploading releaseFile {FileName} to GitHub release {ReleaseUrl} after {TotalSeconds} seconds", - releaseFile.Source.FileName, + legacyReleaseFile.Source.FileName, gitHubRelease.Url, stopwatch.Elapsed.TotalSeconds); } diff --git a/Source/Bake/Services/Uploader.cs b/Source/Bake/Services/Uploader.cs index f34b5e2d..1fea38b0 100644 --- a/Source/Bake/Services/Uploader.cs +++ b/Source/Bake/Services/Uploader.cs @@ -67,7 +67,7 @@ public async Task UploadAsync( Uri url, CancellationToken cancellationToken) { - var file = _fileSystem.Open(filePath); + var file = _fileSystem.Get(filePath); var fileName = Path.GetFileName(filePath); var fileExtension = Path.GetExtension(filePath).Trim('.'); var mediaType = MediaTypes.TryGetValue(fileExtension, out var t) ? t : DefaultMediaType; diff --git a/Source/Bake/ValueObjects/Artifacts/ArtifactAttribute.cs b/Source/Bake/ValueObjects/Artifacts/ArtifactAttribute.cs index 6e2f9514..aea1271d 100644 --- a/Source/Bake/ValueObjects/Artifacts/ArtifactAttribute.cs +++ b/Source/Bake/ValueObjects/Artifacts/ArtifactAttribute.cs @@ -20,8 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System; - namespace Bake.ValueObjects.Artifacts { public class ArtifactAttribute : Attribute, IYamlTag diff --git a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs index 0c8c9cc6..150be6ee 100644 --- a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs +++ b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs @@ -34,15 +34,23 @@ public class ReleaseArtifact : Artifact [YamlMember] public ReleaseNotes? ReleaseNotes { get; [Obsolete] set; } -#pragma warning disable CS0612 // Type or member is obsolete + [YamlMember] + public ReleaseFileArtifact[] Files { get; [Obsolete] set; } = null!; + + [Obsolete] + public ReleaseArtifact() { } + public ReleaseArtifact( SemVer version, - ReleaseNotes? releaseNotes) + ReleaseNotes? releaseNotes, + ReleaseFileArtifact[] files) { +#pragma warning disable CS0612 // Type or member is obsolete Version = version; ReleaseNotes = releaseNotes; - } + Files = files; #pragma warning restore CS0612 // Type or member is obsolete + } public override IAsyncEnumerable ValidateAsync(CancellationToken cancellationToken) { diff --git a/Source/Bake/ValueObjects/Artifacts/ReleaseFileArtifact.cs b/Source/Bake/ValueObjects/Artifacts/ReleaseFileArtifact.cs new file mode 100644 index 00000000..cc958c8d --- /dev/null +++ b/Source/Bake/ValueObjects/Artifacts/ReleaseFileArtifact.cs @@ -0,0 +1,60 @@ +// MIT License +// +// Copyright (c) 2021-2024 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using YamlDotNet.Serialization; + +namespace Bake.ValueObjects.Artifacts +{ + [Artifact(Names.Artifacts.ReleaseFileArtifact)] + public class ReleaseFileArtifact : Artifact + { + [YamlMember] + public string Name { get; [Obsolete] set; } = null!; + + [YamlMember] + public Artifact[] Sources { get; [Obsolete] set; } = null!; + + [Obsolete] + public ReleaseFileArtifact(){} + + public ReleaseFileArtifact( + string name, + Artifact[] sources) + { +#pragma warning disable CS0612 // Type or member is obsolete + Name = name; + Sources = sources; +#pragma warning restore CS0612 // Type or member is obsolete + + } + + public override IAsyncEnumerable ValidateAsync(CancellationToken cancellationToken) + { + return AsyncEnumerable.Empty(); + } + + public override IEnumerable PrettyNames() + { + yield return Name; + } + } +} diff --git a/Source/Bake/ValueObjects/ReleaseFile.cs b/Source/Bake/ValueObjects/LegacyReleaseFile.cs similarity index 96% rename from Source/Bake/ValueObjects/ReleaseFile.cs rename to Source/Bake/ValueObjects/LegacyReleaseFile.cs index c53eb24b..d057580a 100644 --- a/Source/Bake/ValueObjects/ReleaseFile.cs +++ b/Source/Bake/ValueObjects/LegacyReleaseFile.cs @@ -24,13 +24,13 @@ namespace Bake.ValueObjects; -public class ReleaseFile +public class LegacyReleaseFile { public IFile Source { get; } public string Destination { get; } public string Sha256 { get; } - public ReleaseFile( + public LegacyReleaseFile( IFile source, string destination, string sha256) diff --git a/Source/Bake/ValueObjects/Release.cs b/Source/Bake/ValueObjects/Release.cs index 23a24122..2d5f309e 100644 --- a/Source/Bake/ValueObjects/Release.cs +++ b/Source/Bake/ValueObjects/Release.cs @@ -28,13 +28,13 @@ namespace Bake.ValueObjects public class Release : Tag { public string Body { get; } - public IReadOnlyCollection Files { get; } + public IReadOnlyCollection Files { get; } public Release( SemVer version, string sha, string body, - IReadOnlyCollection files) + IReadOnlyCollection files) : base(version, sha) { Body = body; From 0a19d1ed76fbf1fa0ca3ebc23385203453c15ead Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 22 Dec 2024 15:11:23 +0100 Subject: [PATCH 03/17] Cleaning more up --- .../ExplicitTests/GitHubReleaseCookTests.cs | 2 +- Source/Bake/Context.cs | 2 - .../Bake/Cooking/Composers/ReleaseComposer.cs | 100 ++++++++++++++++-- .../Cooking/Cooks/GitHub/GitHubReleaseCook.cs | 55 +--------- Source/Bake/Names.cs | 1 - .../ValueObjects/Artifacts/ReleaseArtifact.cs | 19 +--- .../Artifacts/ReleaseFileArtifact.cs | 60 ----------- .../Recipes/Release/ReleaseRecipe.cs | 14 +-- Source/Bake/ValueObjects/Release.cs | 1 - 9 files changed, 98 insertions(+), 156 deletions(-) delete mode 100644 Source/Bake/ValueObjects/Artifacts/ReleaseFileArtifact.cs diff --git a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs index 849ea379..c1d47c65 100644 --- a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs +++ b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs @@ -50,7 +50,7 @@ public async Task CreateRelease() new Uri("https://github.com/rasmus/testtest"), new Uri("https://api.guthub.com/")), "a108d8a38b4ac154172cb7eeea8530e316ead798", - new ReleaseArtifact(SemVer.Random, new ReleaseNotes(SemVer.Random, string.Empty), [])); + new ReleaseArtifact(string.Empty)); // Arrange var result = await Sut.CookAsync( diff --git a/Source/Bake/Context.cs b/Source/Bake/Context.cs index d87970cd..692631e1 100644 --- a/Source/Bake/Context.cs +++ b/Source/Bake/Context.cs @@ -20,8 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System.Collections.Generic; -using System.Linq; using Bake.ValueObjects; using Bake.ValueObjects.Artifacts; diff --git a/Source/Bake/Cooking/Composers/ReleaseComposer.cs b/Source/Bake/Cooking/Composers/ReleaseComposer.cs index c9e152da..3494d86d 100644 --- a/Source/Bake/Cooking/Composers/ReleaseComposer.cs +++ b/Source/Bake/Cooking/Composers/ReleaseComposer.cs @@ -20,6 +20,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using System.Text; +using Bake.ValueObjects; using Bake.ValueObjects.Artifacts; using Bake.ValueObjects.Recipes; using Bake.ValueObjects.Recipes.Release; @@ -31,10 +33,11 @@ public class ReleaseComposer : Composer { public override IReadOnlyCollection Consumes { get; } = [ - ArtifactType.NuGet, - ArtifactType.Executable, + ArtifactType.Container, ArtifactType.DocumentationSite, - ArtifactType.Container + ArtifactType.Executable, + ArtifactType.HelmChart, + ArtifactType.NuGet, ]; public override IReadOnlyCollection Produces { get; } = [ArtifactType.Release]; @@ -52,9 +55,10 @@ public override Task> ComposeAsync( CancellationToken cancellationToken) { var artifacts = Enumerable.Empty() - .Concat(context.GetArtifacts()) - .Concat(context.GetArtifacts()) .Concat(context.GetArtifacts()) + .Concat(context.GetArtifacts()) + .Concat(context.GetArtifacts()) + .Concat(context.GetArtifacts()) .Concat(context.GetArtifacts()) .ToArray(); @@ -64,13 +68,91 @@ public override Task> ComposeAsync( return Task.FromResult(EmptyRecipes); } + var releaseText = new StringBuilder(); + AddReleaseNotes(context, releaseText); + AddChangeLog(context, releaseText); + AddArtifactDescriptions(context, artifacts, releaseText); + AddGitHubChangeLink(context, releaseText); + return Task.FromResult>( [ - new ReleaseRecipe( - context.Ingredients.Version, - context.Ingredients.ReleaseNotes!, - artifacts) + new ReleaseRecipe(new ReleaseArtifact(releaseText.ToString())) ]); } + + private static void AddReleaseNotes(IContext context, StringBuilder releaseText) + { + if (context.Ingredients.ReleaseNotes != null) + { + releaseText + .AppendLine("### Release notes") + .AppendLine(context.Ingredients.ReleaseNotes.Notes) + .AppendLine(); + } + } + + private static void AddArtifactDescriptions( + IContext _, + Artifact[] artifacts, + StringBuilder releaseText) + { + foreach (var g in artifacts.GroupBy(a => a.GetType())) + { + switch (g.Key) + { + case { } t when t == typeof(ContainerArtifact): + { + releaseText.AppendLine("### Containers"); + foreach (var artifact in g) + { + var containerArtifact = (ContainerArtifact) artifact; + releaseText.AppendLine($"* `{containerArtifact.Name}`"); + foreach (var tag in containerArtifact.Tags) + { + releaseText.AppendLine($" * `{tag}`"); + } + } + } + break; + } + + } + } + + private static void AddChangeLog(IContext context, StringBuilder releaseText) + { + if (context.Ingredients.Changelog == null || !context.Ingredients.Changelog.Changes.Any()) + { + return; + } + + foreach (var a in new[] + { + new {changeType = ChangeType.Other, title = "Changes"}, + new {changeType = ChangeType.Dependency, title = "Updated dependencies"}, + }) + { + releaseText + .AppendLine($"#### {a.title}") + .AppendLine(); + + foreach (var change in context.Ingredients.Changelog.Changes[a.changeType]) + { + releaseText.AppendLine($"* {change.Text}"); + } + releaseText.AppendLine(); + } + + releaseText.AppendLine(); + } + + private static void AddGitHubChangeLink(IContext context, StringBuilder releaseText) + { + if (context.Ingredients is {GitHub: not null, Changelog: not null}) + { + releaseText.AppendLine( + $"Full Changelog: {context.Ingredients.GitHub.Url.AbsoluteUri.TrimEnd('/')}/compare/{context.Ingredients.Changelog.PreviousReleaseTag.Sha}...{context.Ingredients.Git!.Sha}"); + } + } } } diff --git a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs index 0c3432e8..a67c5812 100644 --- a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs +++ b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs @@ -79,43 +79,6 @@ protected override async Task CookAsync( var stringBuilder = new StringBuilder(); - if (recipe.Release.ReleaseNotes != null) - { - stringBuilder - .AppendLine("### Release notes") - .AppendLine(recipe.Release.ReleaseNotes.Notes) - .AppendLine(); - } - - if (context.Ingredients.Changelog != null && context.Ingredients.Changelog.Changes.Any()) - { - foreach (var a in new[] - { - new {changeType = ChangeType.Other, title = "Changes"}, - new {changeType = ChangeType.Dependency, title = "Updated dependencies"}, - }) - { - stringBuilder - .AppendLine($"#### {a.title}") - .AppendLine(); - - foreach (var change in context.Ingredients.Changelog.Changes[a.changeType]) - { - stringBuilder.AppendLine($"* {change.Text}"); - } - - stringBuilder.AppendLine(); - } - - stringBuilder.AppendLine(); - - if (context.Ingredients.GitHub != null) - { - stringBuilder.AppendLine( - $"Full Changelog: {context.Ingredients.GitHub.Url.AbsoluteUri.TrimEnd('/')}/compare/{context.Ingredients.Changelog.PreviousReleaseTag.Sha}...{context.Ingredients.Git!.Sha}"); - } - } - var releaseFiles = (await CreateReleaseFilesAsync(additionalFiles, recipe, cancellationToken)).ToList(); var documentationSite = recipe.Artifacts @@ -137,22 +100,6 @@ protected override async Task CookAsync( await file.GetHashAsync(HashAlgorithm.SHA256, cancellationToken))); } - var containerArtifacts = recipe.Artifacts - .OfType() - .ToArray(); - if (containerArtifacts.Any()) - { - stringBuilder.AppendLine("### Containers"); - foreach (var containerArtifact in containerArtifacts) - { - stringBuilder.AppendLine($"* `{containerArtifact.Name}`"); - foreach (var tag in containerArtifact.Tags) - { - stringBuilder.AppendLine($" * `{tag}`"); - } - } - } - if (releaseFiles.Any()) { stringBuilder.AppendLine("### Files"); @@ -164,7 +111,7 @@ protected override async Task CookAsync( } var release = new ValueObjects.Release( - recipe.Release.Version, + context.Ingredients.Version, recipe.Sha, stringBuilder.ToString(), releaseFiles); diff --git a/Source/Bake/Names.cs b/Source/Bake/Names.cs index e922b1a2..72fbfbab 100644 --- a/Source/Bake/Names.cs +++ b/Source/Bake/Names.cs @@ -62,7 +62,6 @@ public static class Artifacts public const string DocumentationSiteArtifact = "documentation-site-artifact"; public const string HelmChartArtifact = "helm-chart-artifact"; public const string ReleaseArtifact = "release-artifact"; - public const string ReleaseFileArtifact = "release-file-artifact"; } public static class ArtifactTypes diff --git a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs index 150be6ee..ee71b300 100644 --- a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs +++ b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs @@ -20,7 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using Bake.Core; using YamlDotNet.Serialization; namespace Bake.ValueObjects.Artifacts @@ -29,26 +28,16 @@ namespace Bake.ValueObjects.Artifacts public class ReleaseArtifact : Artifact { [YamlMember] - public SemVer Version { get; [Obsolete] set; } = null!; - - [YamlMember] - public ReleaseNotes? ReleaseNotes { get; [Obsolete] set; } - - [YamlMember] - public ReleaseFileArtifact[] Files { get; [Obsolete] set; } = null!; + public string Text { get; [Obsolete] set; } = null!; [Obsolete] public ReleaseArtifact() { } public ReleaseArtifact( - SemVer version, - ReleaseNotes? releaseNotes, - ReleaseFileArtifact[] files) + string text) { #pragma warning disable CS0612 // Type or member is obsolete - Version = version; - ReleaseNotes = releaseNotes; - Files = files; + Text = text; #pragma warning restore CS0612 // Type or member is obsolete } @@ -59,7 +48,7 @@ public override IAsyncEnumerable ValidateAsync(CancellationToken cancell public override IEnumerable PrettyNames() { - yield return "release"; + yield return Names.Artifacts.ReleaseArtifact; } } } diff --git a/Source/Bake/ValueObjects/Artifacts/ReleaseFileArtifact.cs b/Source/Bake/ValueObjects/Artifacts/ReleaseFileArtifact.cs deleted file mode 100644 index cc958c8d..00000000 --- a/Source/Bake/ValueObjects/Artifacts/ReleaseFileArtifact.cs +++ /dev/null @@ -1,60 +0,0 @@ -// MIT License -// -// Copyright (c) 2021-2024 Rasmus Mikkelsen -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using YamlDotNet.Serialization; - -namespace Bake.ValueObjects.Artifacts -{ - [Artifact(Names.Artifacts.ReleaseFileArtifact)] - public class ReleaseFileArtifact : Artifact - { - [YamlMember] - public string Name { get; [Obsolete] set; } = null!; - - [YamlMember] - public Artifact[] Sources { get; [Obsolete] set; } = null!; - - [Obsolete] - public ReleaseFileArtifact(){} - - public ReleaseFileArtifact( - string name, - Artifact[] sources) - { -#pragma warning disable CS0612 // Type or member is obsolete - Name = name; - Sources = sources; -#pragma warning restore CS0612 // Type or member is obsolete - - } - - public override IAsyncEnumerable ValidateAsync(CancellationToken cancellationToken) - { - return AsyncEnumerable.Empty(); - } - - public override IEnumerable PrettyNames() - { - yield return Name; - } - } -} diff --git a/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs index 7a9ba9f0..69aa6f64 100644 --- a/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs +++ b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs @@ -20,33 +20,21 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using Bake.Core; using Bake.ValueObjects.Artifacts; -using YamlDotNet.Serialization; namespace Bake.ValueObjects.Recipes.Release { [Recipe(Names.Recipes.Releases.Release)] public class ReleaseRecipe : Recipe { - [YamlMember] - public SemVer Version { get; [Obsolete] set; } = null!; - - [YamlMember] - public ReleaseNotes? ReleaseNotes { get; [Obsolete] set; } - [Obsolete] public ReleaseRecipe() { } public ReleaseRecipe( - SemVer version, - ReleaseNotes? releaseNotes, - Artifact[] artifacts) + params Artifact[] artifacts) : base(artifacts) { #pragma warning disable CS0612 // Type or member is obsolete - Version = version; - ReleaseNotes = releaseNotes; Artifacts = artifacts; #pragma warning restore CS0612 // Type or member is obsolete } diff --git a/Source/Bake/ValueObjects/Release.cs b/Source/Bake/ValueObjects/Release.cs index 2d5f309e..e9b145bc 100644 --- a/Source/Bake/ValueObjects/Release.cs +++ b/Source/Bake/ValueObjects/Release.cs @@ -20,7 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System.Collections.Generic; using Bake.Core; namespace Bake.ValueObjects From 8f0741fe2400a9ea7ff5748b31f1ff609ba8ba99 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 22 Dec 2024 16:02:33 +0100 Subject: [PATCH 04/17] Started on creating release files --- .../ExplicitTests/GitHubReleaseCookTests.cs | 2 +- .../Bake/Cooking/Composers/ReleaseComposer.cs | 103 +++++++++++++++++- .../Cooking/Cooks/GitHub/GitHubReleaseCook.cs | 33 +----- .../Bake/Cooking/Cooks/Release/ReleaseCook.cs | 58 +++++++++- Source/Bake/Core/Defaults.cs | 2 + Source/Bake/Core/FileSystem.cs | 46 +++++++- Source/Bake/Core/IDefaults.cs | 1 + Source/Bake/Core/IFileSystem.cs | 10 ++ Source/Bake/ValueObjects/.gitignore | 1 + .../ValueObjects/Artifacts/ReleaseArtifact.cs | 14 ++- .../Recipes/Release/ReleaseRecipe.cs | 7 ++ .../Bake/ValueObjects/Releases/ReleaseFile.cs | 41 +++++++ 12 files changed, 277 insertions(+), 41 deletions(-) create mode 100644 Source/Bake/ValueObjects/Releases/ReleaseFile.cs diff --git a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs index c1d47c65..5a69d437 100644 --- a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs +++ b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs @@ -50,7 +50,7 @@ public async Task CreateRelease() new Uri("https://github.com/rasmus/testtest"), new Uri("https://api.guthub.com/")), "a108d8a38b4ac154172cb7eeea8530e316ead798", - new ReleaseArtifact(string.Empty)); + new ReleaseArtifact(string.Empty, [])); // Arrange var result = await Sut.CookAsync( diff --git a/Source/Bake/Cooking/Composers/ReleaseComposer.cs b/Source/Bake/Cooking/Composers/ReleaseComposer.cs index 3494d86d..97711595 100644 --- a/Source/Bake/Cooking/Composers/ReleaseComposer.cs +++ b/Source/Bake/Cooking/Composers/ReleaseComposer.cs @@ -20,17 +20,33 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using System.Collections.Concurrent; using System.Text; +using Bake.Core; using Bake.ValueObjects; using Bake.ValueObjects.Artifacts; using Bake.ValueObjects.Recipes; using Bake.ValueObjects.Recipes.Release; +using Bake.ValueObjects.Releases; using Microsoft.Extensions.Logging; +using File = System.IO.File; namespace Bake.Cooking.Composers { public class ReleaseComposer : Composer { + private static readonly IReadOnlyDictionary NamingOs = new ConcurrentDictionary + { + [ExecutableOperatingSystem.Linux] = "linux", + [ExecutableOperatingSystem.MacOSX] = "macosx", + [ExecutableOperatingSystem.Windows] = "windows" + }; + private static readonly IReadOnlyDictionary NamingArch = new ConcurrentDictionary + { + [ExecutableArchitecture.Intel32] = "x86", + [ExecutableArchitecture.Intel64] = "x86_64", + }; + public override IReadOnlyCollection Consumes { get; } = [ ArtifactType.Container, @@ -43,11 +59,14 @@ public class ReleaseComposer : Composer public override IReadOnlyCollection Produces { get; } = [ArtifactType.Release]; private readonly ILogger _logger; + private readonly IDefaults _defaults; public ReleaseComposer( - ILogger logger) + ILogger logger, + IDefaults defaults) { _logger = logger; + _defaults = defaults; } public override Task> ComposeAsync( @@ -74,12 +93,79 @@ public override Task> ComposeAsync( AddArtifactDescriptions(context, artifacts, releaseText); AddGitHubChangeLink(context, releaseText); + var releaseFiles = BuildReleaseFiles(context, artifacts); + return Task.FromResult>( [ - new ReleaseRecipe(new ReleaseArtifact(releaseText.ToString())) + new ReleaseRecipe( + releaseFiles.ToArray(), + new ReleaseArtifact( + releaseText.ToString(), + releaseFiles.Select(f => f.Destination).ToArray())) ]); } + private List BuildReleaseFiles( + IContext context, + Artifact[] inputArtifacts) + { + var additionalSourceFiles = new[] + { + Path.Combine(context.Ingredients.WorkingDirectory, "README.md"), + Path.Combine(context.Ingredients.WorkingDirectory, "LICENSE"), + Path.Combine(context.Ingredients.WorkingDirectory, "RELEASE_NOTES.md"), + } + .Where(File.Exists) + .ToArray(); + + var releaseFiles = new List(); + + foreach (var g in inputArtifacts.GroupBy(a => a.GetType())) + { + switch (g.Key) + { + case { } t when t == typeof(DocumentationSiteArtifact): + { + foreach (var artifact in g) + { + var documentationSiteArtifact = (DocumentationSiteArtifact)artifact; + var fileName = $"documentation_v{context.Ingredients.Version}.zip"; + releaseFiles.Add(new ReleaseFile( + fileName, + AppendFiles(documentationSiteArtifact.Path), + Path.Combine(_defaults.BakeReleaseOutputDirectory, fileName))); + } + } + break; + + case { } t when t == typeof(ExecutableArtifact): + { + foreach (var artifact in g) + { + var executableArtifact = (ExecutableArtifact) artifact; + var fileName = CalculateArtifactFileName(executableArtifact); + releaseFiles.Add(new ReleaseFile( + fileName, + AppendFiles(executableArtifact.Path), + Path.Combine(_defaults.BakeReleaseOutputDirectory, fileName))); + } + } + break; + } + } + + return releaseFiles; + + string[] AppendFiles(params string[] paths) + { + return Enumerable.Empty() + .Concat(additionalSourceFiles) + .Concat(paths) + .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) + .ToArray(); + } + } + private static void AddReleaseNotes(IContext context, StringBuilder releaseText) { if (context.Ingredients.ReleaseNotes != null) @@ -115,7 +201,6 @@ private static void AddArtifactDescriptions( } break; } - } } @@ -154,5 +239,17 @@ private static void AddGitHubChangeLink(IContext context, StringBuilder releaseT $"Full Changelog: {context.Ingredients.GitHub.Url.AbsoluteUri.TrimEnd('/')}/compare/{context.Ingredients.Changelog.PreviousReleaseTag.Sha}...{context.Ingredients.Git!.Sha}"); } } + + private static string CalculateArtifactFileName(ExecutableArtifact artifact) + { + var parts = new[] + { + artifact.Name, + NamingOs[artifact.Platform.Os], + NamingArch[artifact.Platform.Arch] + }; + + return $"{string.Join("_", parts)}.zip"; + } } } diff --git a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs index a67c5812..1449b494 100644 --- a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs +++ b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs @@ -77,43 +77,12 @@ protected override async Task CookAsync( .Select(_fileSystem.Get) .ToArray(); - var stringBuilder = new StringBuilder(); - var releaseFiles = (await CreateReleaseFilesAsync(additionalFiles, recipe, cancellationToken)).ToList(); - var documentationSite = recipe.Artifacts - .OfType() - .FirstOrDefault(); - if (documentationSite != null) - { - _logger.LogInformation("Documentation site built, packing it into a release file"); - var documentationZipFilePath = Path.Combine( - Path.GetTempPath(), - Guid.NewGuid().ToString("N"), - "documentation.zip"); - Directory.CreateDirectory(Path.GetDirectoryName(documentationZipFilePath)!); - ZipFile.CreateFromDirectory(documentationSite.Path, documentationZipFilePath); - var file = _fileSystem.Get(documentationZipFilePath); - releaseFiles.Add(new LegacyReleaseFile( - file, - $"documentation_v{context.Ingredients.Version}.zip", - await file.GetHashAsync(HashAlgorithm.SHA256, cancellationToken))); - } - - if (releaseFiles.Any()) - { - stringBuilder.AppendLine("### Files"); - foreach (var releaseFile in releaseFiles) - { - stringBuilder.AppendLine($"* `{releaseFile.Destination}`"); - stringBuilder.AppendLine($" * SHA256: `{releaseFile.Sha256}`"); - } - } - var release = new ValueObjects.Release( context.Ingredients.Version, recipe.Sha, - stringBuilder.ToString(), + string.Empty, releaseFiles); await _gitHub.CreateReleaseAsync( diff --git a/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs index ec301f0b..58abb737 100644 --- a/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs +++ b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs @@ -20,18 +20,72 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using Bake.Core; using Bake.ValueObjects.Recipes.Release; +using Bake.ValueObjects.Releases; +using Microsoft.Extensions.Logging; +using System.IO.Compression; +using File = System.IO.File; namespace Bake.Cooking.Cooks.Release { public class ReleaseCook : Cook { - protected override Task CookAsync( + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + public ReleaseCook( + ILogger logger, + IFileSystem fileSystem) + { + _logger = logger; + _fileSystem = fileSystem; + } + + protected override async Task CookAsync( IContext context, ReleaseRecipe recipe, CancellationToken cancellationToken) { - return Task.FromResult(true); + foreach (var releaseFile in recipe.Files) + { + if (!await CompressReleaseFilesAsync(releaseFile, cancellationToken)) + { + return false; + } + } + + return true; + } + + private async Task CompressReleaseFilesAsync(ReleaseFile releaseFile, CancellationToken cancellationToken) + { + var tmpDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); + _logger.LogInformation("Creating temporary directory at {TmpDirectory}", tmpDirectory); + foreach (var source in releaseFile.Sources) + { + if (File.Exists(source)) + { + var fileName = Path.GetFileName(source); + var destination = Path.Combine(tmpDirectory, fileName); + _logger.LogInformation("Copying file from {Source} to {Destination}", source, destination); + await _fileSystem.CopyFileAsync(source, destination, cancellationToken); + } + else if (Directory.Exists(source)) + { + _logger.LogInformation("Copying directory from {Source} to {Destination}", source, tmpDirectory); + await _fileSystem.CopyDirectoryAsync(source, tmpDirectory, cancellationToken); + } + else + { + _logger.LogError("The source {Source} does not exist", source); + return false; + } + } + + ZipFile.CreateFromDirectory(tmpDirectory, releaseFile.Destination); + + return true; } } } diff --git a/Source/Bake/Core/Defaults.cs b/Source/Bake/Core/Defaults.cs index 656ecb85..c5335a7c 100644 --- a/Source/Bake/Core/Defaults.cs +++ b/Source/Bake/Core/Defaults.cs @@ -42,6 +42,7 @@ public class Defaults : IDefaults public string DotNetRollForward { get; private set; } = "LatestMajor"; public TimeSpan BakeIngredientsGatherTimeout { get; private set; } = TimeSpan.FromMinutes(5); public TimeSpan BakeComposeTimeout { get; private set; } = TimeSpan.FromMinutes(5); + public string BakeReleaseOutputDirectory { get; private set; } = Path.Combine(Path.GetTempPath(), "bake-release"); public Defaults( IEnvironmentVariables environmentVariables) @@ -66,6 +67,7 @@ public async Task InitializeAsync( DotNetRollForward = GetString(e, "dotnet_roll_forward", DotNetRollForward); BakeIngredientsGatherTimeout = TimeSpan.FromSeconds(GetDouble(e, "bake_ingredients_gather_timeout_seconds", BakeIngredientsGatherTimeout.TotalSeconds)); BakeComposeTimeout = TimeSpan.FromSeconds(GetDouble(e, "bake_compose_timeout_seconds", BakeComposeTimeout.TotalSeconds)); + BakeReleaseOutputDirectory = GetString(e, "bake_release_output_directory", BakeReleaseOutputDirectory); } private static bool GetBool( diff --git a/Source/Bake/Core/FileSystem.cs b/Source/Bake/Core/FileSystem.cs index 3b62b2a4..7e8c1831 100644 --- a/Source/Bake/Core/FileSystem.cs +++ b/Source/Bake/Core/FileSystem.cs @@ -85,6 +85,50 @@ public async Task> FindFilesAsync( return validPaths; } + public async Task CopyFileAsync( + string sourcePath, + string destinationPath, + CancellationToken cancellationToken) + { + var destinationParentDirectory = Path.GetDirectoryName(destinationPath); + if (string.IsNullOrEmpty(destinationParentDirectory)) + { + throw new ArgumentException($"Cannot determine parent directory of {destinationPath}"); + } + if (!Directory.Exists(destinationPath)) + { + Directory.CreateDirectory(destinationParentDirectory!); + } + + await using var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); + await using var destinationStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); + await sourceStream.CopyToAsync(destinationStream, 81920, cancellationToken); + } + + public async Task CopyDirectoryAsync( + string sourcePath, + string destinationPath, + CancellationToken cancellationToken) + { + var directoryInfo = new DirectoryInfo(sourcePath); + var directoryInfos = directoryInfo.GetDirectories(); + + Directory.CreateDirectory(destinationPath); + + var files = directoryInfo.GetFiles(); + foreach (var file in files) + { + var tempPath = Path.Combine(destinationPath, file.Name); + await CopyFileAsync(file.FullName, tempPath, cancellationToken); + } + + foreach (var subDirectory in directoryInfos) + { + var tempPath = Path.Combine(destinationPath, subDirectory.Name); + await CopyDirectoryAsync(subDirectory.FullName, tempPath, cancellationToken); + } + } + public async Task CompressAsync( string fileName, CompressionAlgorithm algorithm, @@ -96,7 +140,7 @@ public async Task CompressAsync( throw new ArgumentOutOfRangeException(nameof(algorithm)); } - if (!files.Any()) + if (files.Count == 0) { throw new ArgumentNullException(nameof(files)); } diff --git a/Source/Bake/Core/IDefaults.cs b/Source/Bake/Core/IDefaults.cs index 4662c514..fb61c9b1 100644 --- a/Source/Bake/Core/IDefaults.cs +++ b/Source/Bake/Core/IDefaults.cs @@ -41,6 +41,7 @@ public interface IDefaults TimeSpan BakeIngredientsGatherTimeout { get; } TimeSpan BakeComposeTimeout { get; } + string BakeReleaseOutputDirectory { get; } Task InitializeAsync( CancellationToken cancellationToken); diff --git a/Source/Bake/Core/IFileSystem.cs b/Source/Bake/Core/IFileSystem.cs index d171ae7c..0a613f16 100644 --- a/Source/Bake/Core/IFileSystem.cs +++ b/Source/Bake/Core/IFileSystem.cs @@ -49,5 +49,15 @@ Task CompressAsync( CancellationToken cancellationToken); bool FileExists(string filePath); + + Task CopyFileAsync( + string sourcePath, + string destinationPath, + CancellationToken cancellationToken); + + Task CopyDirectoryAsync( + string sourcePath, + string destinationPath, + CancellationToken cancellationToken); } } diff --git a/Source/Bake/ValueObjects/.gitignore b/Source/Bake/ValueObjects/.gitignore index 19a13fb1..5f6ad16e 100644 --- a/Source/Bake/ValueObjects/.gitignore +++ b/Source/Bake/ValueObjects/.gitignore @@ -1,2 +1,3 @@ !Artifacts !Release +!Releases diff --git a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs index ee71b300..8328ed7d 100644 --- a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs +++ b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs @@ -30,20 +30,30 @@ public class ReleaseArtifact : Artifact [YamlMember] public string Text { get; [Obsolete] set; } = null!; + [YamlMember] + public string[] Files { get; [Obsolete] set; } = null!; + [Obsolete] public ReleaseArtifact() { } public ReleaseArtifact( - string text) + string text, + string[] files) { #pragma warning disable CS0612 // Type or member is obsolete Text = text; + Files = files; #pragma warning restore CS0612 // Type or member is obsolete } public override IAsyncEnumerable ValidateAsync(CancellationToken cancellationToken) { - return AsyncEnumerable.Empty(); + var missingFiles = Files + .Where(file => !File.Exists(file)) + .Select(f => $"File '{f}' is missing!") + .ToArray(); + + return missingFiles.ToAsyncEnumerable(); } public override IEnumerable PrettyNames() diff --git a/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs index 69aa6f64..6cb41925 100644 --- a/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs +++ b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs @@ -21,20 +21,27 @@ // SOFTWARE. using Bake.ValueObjects.Artifacts; +using Bake.ValueObjects.Releases; +using YamlDotNet.Serialization; namespace Bake.ValueObjects.Recipes.Release { [Recipe(Names.Recipes.Releases.Release)] public class ReleaseRecipe : Recipe { + [YamlMember] + public ReleaseFile[] Files { get; [Obsolete] set; } = null!; + [Obsolete] public ReleaseRecipe() { } public ReleaseRecipe( + ReleaseFile[] files, params Artifact[] artifacts) : base(artifacts) { #pragma warning disable CS0612 // Type or member is obsolete + Files = files; Artifacts = artifacts; #pragma warning restore CS0612 // Type or member is obsolete } diff --git a/Source/Bake/ValueObjects/Releases/ReleaseFile.cs b/Source/Bake/ValueObjects/Releases/ReleaseFile.cs new file mode 100644 index 00000000..8ef9ccf3 --- /dev/null +++ b/Source/Bake/ValueObjects/Releases/ReleaseFile.cs @@ -0,0 +1,41 @@ +// MIT License +// +// Copyright (c) 2021-2024 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace Bake.ValueObjects.Releases +{ + public class ReleaseFile + { + public string Name { get; } + public string[] Sources { get; } + public string Destination { get; } + + public ReleaseFile( + string name, + string[] sources, + string destination) + { + Name = name; + Sources = sources; + Destination = destination; + } + } +} From 5e60a23acaa48c865dad07dbc5621bf35329adfd Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 22 Dec 2024 17:28:52 +0100 Subject: [PATCH 05/17] Started on testing releases --- Source/Bake.Tests/Helpers/BakeTest.cs | 11 ---- Source/Bake.Tests/Helpers/TestFor.cs | 13 ++-- Source/Bake.Tests/Helpers/TestIt.cs | 6 ++ Source/Bake.Tests/Helpers/TestService.cs | 41 ++++++++++++ .../Cooking/Cooks/ReleaseCookTests.cs | 64 +++++++++++++++++++ .../Bake/Cooking/Composers/ReleaseComposer.cs | 14 ++-- .../Recipes/Release/ReleaseRecipe.cs | 5 ++ 7 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 Source/Bake.Tests/Helpers/TestService.cs create mode 100644 Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs diff --git a/Source/Bake.Tests/Helpers/BakeTest.cs b/Source/Bake.Tests/Helpers/BakeTest.cs index 92f47e23..35499b7b 100644 --- a/Source/Bake.Tests/Helpers/BakeTest.cs +++ b/Source/Bake.Tests/Helpers/BakeTest.cs @@ -33,11 +33,8 @@ namespace Bake.Tests.Helpers { public abstract class BakeTest : TestProject { - private CancellationTokenSource? _timeout; - private List _releases = null!; protected IReadOnlyCollection Releases => _releases; - protected CancellationToken Timeout => _timeout!.Token; protected BakeTest(string projectName) : base(projectName) { @@ -46,17 +43,9 @@ protected BakeTest(string projectName) : base(projectName) [SetUp] public void SetUpBakeTest() { - _timeout = new CancellationTokenSource(TimeSpan.FromMinutes(5)); _releases = new List(); } - [TearDown] - public void TearDownBakeTest() - { - _timeout?.Dispose(); - _timeout = null; - } - protected Task ExecuteAsync( params string[] args) { diff --git a/Source/Bake.Tests/Helpers/TestFor.cs b/Source/Bake.Tests/Helpers/TestFor.cs index 5d1f3e6f..91a78ebf 100644 --- a/Source/Bake.Tests/Helpers/TestFor.cs +++ b/Source/Bake.Tests/Helpers/TestFor.cs @@ -32,10 +32,11 @@ namespace Bake.Tests.Helpers { public class TestFor : TestIt { + protected IServiceProvider ServiceProvider { get; private set; } = null!; + protected T Sut => _lazySut.Value; + private Lazy _lazySut = null!; - private ServiceProvider _serviceProvider = null!; private Logger _logger = null!; - protected T Sut => _lazySut.Value; [SetUp] public void SetUpTestFor() @@ -45,18 +46,18 @@ public void SetUpTestFor() .MinimumLevel.Verbose() .WriteTo.Sink(new LogSink(A())) .CreateLogger(); - _serviceProvider = Configure(new ServiceCollection()) + ServiceProvider = Configure(new ServiceCollection()) .AddLogging(b => b.AddSerilog(_logger)) .BuildServiceProvider(); - Inject(_serviceProvider.GetRequiredService>()); - Inject(_serviceProvider); + Inject(ServiceProvider.GetRequiredService>()); + Inject(ServiceProvider); } [TearDown] public void TearDownTestFor() { - _serviceProvider.Dispose(); + ((IDisposable)ServiceProvider).Dispose(); _logger.Dispose(); } diff --git a/Source/Bake.Tests/Helpers/TestIt.cs b/Source/Bake.Tests/Helpers/TestIt.cs index 753f362b..cc9196f1 100644 --- a/Source/Bake.Tests/Helpers/TestIt.cs +++ b/Source/Bake.Tests/Helpers/TestIt.cs @@ -31,13 +31,16 @@ namespace Bake.Tests.Helpers { public abstract class TestIt { + private CancellationTokenSource? _timeout; private List _filesToDelete = null!; protected IFixture Fixture { get; private set; } = null!; + protected CancellationToken Timeout => _timeout!.Token; [SetUp] public void SetUpTestIt() { + _timeout = new CancellationTokenSource(TimeSpan.FromMinutes(5)); _filesToDelete = new List(); Fixture = new Fixture().Customize(new AutoNSubstituteCustomization()); @@ -46,6 +49,9 @@ public void SetUpTestIt() [TearDown] public void TearDownTestIt() { + _timeout?.Dispose(); + _timeout = null; + foreach (var file in _filesToDelete) { if (File.Exists(file)) diff --git a/Source/Bake.Tests/Helpers/TestService.cs b/Source/Bake.Tests/Helpers/TestService.cs new file mode 100644 index 00000000..60b5f276 --- /dev/null +++ b/Source/Bake.Tests/Helpers/TestService.cs @@ -0,0 +1,41 @@ +// MIT License +// +// Copyright (c) 2021-2024 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using Microsoft.Extensions.DependencyInjection; + +namespace Bake.Tests.Helpers +{ + public abstract class TestService : TestFor + where T : class + { + protected override T CreateSut() + { + return ServiceProvider.GetRequiredService(); + } + + protected override IServiceCollection Configure(IServiceCollection serviceCollection) + { + return base.Configure(serviceCollection) + .AddTransient(); + } + } +} diff --git a/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs new file mode 100644 index 00000000..2276e280 --- /dev/null +++ b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs @@ -0,0 +1,64 @@ +// MIT License +// +// Copyright (c) 2021-2024 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using Bake.Cooking.Cooks.Release; +using Bake.Core; +using Bake.Tests.Helpers; +using Bake.ValueObjects.Recipes.Release; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; + +namespace Bake.Tests.UnitTests.Cooking.Cooks +{ + public class ReleaseCookTests : TestService + { + [Test] + public async Task Empty() + { + // Arrange + var context = NewContext(); + + // Act + var success = await Sut.CookAsync( + context, + new ReleaseRecipe( + string.Empty, + []), + Timeout); + + // Assert + success.Should().BeTrue(); + } + + private static Context NewContext() + { + return Context.New(ValueObjects.Ingredients.New(SemVer.Random, Path.GetTempPath())); + } + + protected override IServiceCollection Configure(IServiceCollection serviceCollection) + { + return base.Configure(serviceCollection) + .AddTransient(); + } + } +} diff --git a/Source/Bake/Cooking/Composers/ReleaseComposer.cs b/Source/Bake/Cooking/Composers/ReleaseComposer.cs index 97711595..4ed3a9b6 100644 --- a/Source/Bake/Cooking/Composers/ReleaseComposer.cs +++ b/Source/Bake/Cooking/Composers/ReleaseComposer.cs @@ -87,20 +87,22 @@ public override Task> ComposeAsync( return Task.FromResult(EmptyRecipes); } - var releaseText = new StringBuilder(); - AddReleaseNotes(context, releaseText); - AddChangeLog(context, releaseText); - AddArtifactDescriptions(context, artifacts, releaseText); - AddGitHubChangeLink(context, releaseText); + var releaseTextBuilder = new StringBuilder(); + AddReleaseNotes(context, releaseTextBuilder); + AddChangeLog(context, releaseTextBuilder); + AddArtifactDescriptions(context, artifacts, releaseTextBuilder); + AddGitHubChangeLink(context, releaseTextBuilder); var releaseFiles = BuildReleaseFiles(context, artifacts); + var releaseText = releaseTextBuilder.ToString(); return Task.FromResult>( [ new ReleaseRecipe( + releaseText, releaseFiles.ToArray(), new ReleaseArtifact( - releaseText.ToString(), + releaseText, releaseFiles.Select(f => f.Destination).ToArray())) ]); } diff --git a/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs index 6cb41925..e4c1f1e4 100644 --- a/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs +++ b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs @@ -29,6 +29,9 @@ namespace Bake.ValueObjects.Recipes.Release [Recipe(Names.Recipes.Releases.Release)] public class ReleaseRecipe : Recipe { + [YamlMember] + public string Text { get; [Obsolete] set; } = null!; + [YamlMember] public ReleaseFile[] Files { get; [Obsolete] set; } = null!; @@ -36,11 +39,13 @@ public class ReleaseRecipe : Recipe public ReleaseRecipe() { } public ReleaseRecipe( + string text, ReleaseFile[] files, params Artifact[] artifacts) : base(artifacts) { #pragma warning disable CS0612 // Type or member is obsolete + Text = text; Files = files; Artifacts = artifacts; #pragma warning restore CS0612 // Type or member is obsolete From 87b93738b31a5dd5bece1e8a811b0b79d26b5897 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 22 Dec 2024 17:40:03 +0100 Subject: [PATCH 06/17] Now testing adding files to release --- Source/Bake.Tests/Helpers/TestIt.cs | 9 +++- .../Cooking/Cooks/ReleaseCookTests.cs | 47 +++++++++++++++++++ .../Bake/Cooking/Cooks/Release/ReleaseCook.cs | 4 ++ Source/Bake/Extensions/LongExtensions.cs | 1 - 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/Source/Bake.Tests/Helpers/TestIt.cs b/Source/Bake.Tests/Helpers/TestIt.cs index cc9196f1..6e6941d1 100644 --- a/Source/Bake.Tests/Helpers/TestIt.cs +++ b/Source/Bake.Tests/Helpers/TestIt.cs @@ -102,7 +102,7 @@ protected async Task ReadEmbeddedAsync( var resourceName = resourceNames.Single(n => n.EndsWith(fileEnding, StringComparison.OrdinalIgnoreCase)); await using var stream = assembly.GetManifestResourceStream(resourceName); using var streamReader = new StreamReader(stream!); - return await streamReader.ReadToEndAsync(); + return await streamReader.ReadToEndAsync(Timeout); } protected string Lines( @@ -111,12 +111,17 @@ protected string Lines( return string.Join(Environment.NewLine, lines); } + protected void DeleteAfter(string filePath) + { + _filesToDelete.Add(filePath); + } + protected async Task WriteEmbeddedAsync( string fileEnding) { var content = await ReadEmbeddedAsync(fileEnding); var path = Path.GetTempFileName(); - await System.IO.File.WriteAllTextAsync(path, content); + await File.WriteAllTextAsync(path, content, Timeout); _filesToDelete.Add(path); return path; } diff --git a/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs index 2276e280..bafbda06 100644 --- a/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs +++ b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs @@ -24,9 +24,11 @@ using Bake.Core; using Bake.Tests.Helpers; using Bake.ValueObjects.Recipes.Release; +using Bake.ValueObjects.Releases; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using File = System.IO.File; namespace Bake.Tests.UnitTests.Cooking.Cooks { @@ -50,11 +52,56 @@ public async Task Empty() success.Should().BeTrue(); } + [Test] + public async Task Files() + { + // Arrange + var context = NewContext(); + + // Act + var success = await Sut.CookAsync( + context, + new ReleaseRecipe( + string.Empty, + [ + NewReleaseFile() + ]), + Timeout); + + // Assert + success.Should().BeTrue(); + } + private static Context NewContext() { return Context.New(ValueObjects.Ingredients.New(SemVer.Random, Path.GetTempPath())); } + private ReleaseFile NewReleaseFile(int fileCount = 3) + { + var fileName = $"{Guid.NewGuid():N}.zip"; + var destinationPath = Path.Combine(Path.GetTempPath(), fileName); + DeleteAfter(destinationPath); + + return new ReleaseFile( + fileName, + Enumerable.Range(0, fileCount).Select(_ => NewFile()).ToArray(), + destinationPath); + } + + private string NewFile() + { + var filePath = Path.Combine( + Path.GetTempPath(), + $"{Guid.NewGuid():N}.txt"); + + File.WriteAllText(filePath, "Hello there!"); + + DeleteAfter(filePath); + + return filePath; + } + protected override IServiceCollection Configure(IServiceCollection serviceCollection) { return base.Configure(serviceCollection) diff --git a/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs index 58abb737..9273230b 100644 --- a/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs +++ b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs @@ -25,6 +25,7 @@ using Bake.ValueObjects.Releases; using Microsoft.Extensions.Logging; using System.IO.Compression; +using Bake.Extensions; using File = System.IO.File; namespace Bake.Cooking.Cooks.Release @@ -83,7 +84,10 @@ private async Task CompressReleaseFilesAsync(ReleaseFile releaseFile, Canc } } + _logger.LogInformation("Creating ZIP file at {Destination}", releaseFile.Destination); ZipFile.CreateFromDirectory(tmpDirectory, releaseFile.Destination); + var fileInfo = new FileInfo(releaseFile.Destination); + _logger.LogInformation("Created ZIP file {Destination} with size {Size}", releaseFile.Destination, fileInfo.Length.BytesToString()); return true; } diff --git a/Source/Bake/Extensions/LongExtensions.cs b/Source/Bake/Extensions/LongExtensions.cs index 31acc310..6454c43b 100644 --- a/Source/Bake/Extensions/LongExtensions.cs +++ b/Source/Bake/Extensions/LongExtensions.cs @@ -20,7 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System; using System.Globalization; namespace Bake.Extensions From 42ababd2791d0524103e26f7921ac422b6290ccb Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 22 Dec 2024 18:13:47 +0100 Subject: [PATCH 07/17] Getting the GitHub part in shape as well --- .../ExplicitTests/GitHubReleaseCookTests.cs | 4 +- Source/Bake.Tests/Helpers/BakeTest.cs | 14 ++-- .../Cooking/Cooks/ReleaseCookTests.cs | 10 ++- .../Composers/GitHubReleaseComposer.cs | 9 +-- .../Cooking/Cooks/GitHub/GitHubReleaseCook.cs | 81 ++----------------- Source/Bake/Core/File.cs | 19 ++++- Source/Bake/Services/GitHub.cs | 37 +++++---- Source/Bake/Services/IGitHub.cs | 2 +- .../{Release.cs => GitHubRelease.cs} | 8 +- ...acyReleaseFile.cs => GitHubReleaseFile.cs} | 13 ++- .../Recipes/GitHub/GitHubReleaseRecipe.cs | 12 ++- 11 files changed, 79 insertions(+), 130 deletions(-) rename Source/Bake/ValueObjects/{Release.cs => GitHubRelease.cs} (88%) rename Source/Bake/ValueObjects/{LegacyReleaseFile.cs => GitHubReleaseFile.cs} (84%) diff --git a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs index 5a69d437..a19c2188 100644 --- a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs +++ b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs @@ -25,7 +25,6 @@ using Bake.Services; using Bake.Tests.Helpers; using Bake.ValueObjects; -using Bake.ValueObjects.Artifacts; using Bake.ValueObjects.Recipes.GitHub; using FluentAssertions; using Microsoft.Extensions.Configuration; @@ -44,13 +43,14 @@ public async Task CreateRelease() { // Arrange var recipe = new GitHubReleaseRecipe( + string.Empty, new GitHubInformation( "rasmus", "testtest", new Uri("https://github.com/rasmus/testtest"), new Uri("https://api.guthub.com/")), "a108d8a38b4ac154172cb7eeea8530e316ead798", - new ReleaseArtifact(string.Empty, [])); + []); // Arrange var result = await Sut.CookAsync( diff --git a/Source/Bake.Tests/Helpers/BakeTest.cs b/Source/Bake.Tests/Helpers/BakeTest.cs index 35499b7b..9b86ca6e 100644 --- a/Source/Bake.Tests/Helpers/BakeTest.cs +++ b/Source/Bake.Tests/Helpers/BakeTest.cs @@ -33,8 +33,8 @@ namespace Bake.Tests.Helpers { public abstract class BakeTest : TestProject { - private List _releases = null!; - protected IReadOnlyCollection Releases => _releases; + private List _releases = null!; + protected IReadOnlyCollection Releases => _releases; protected BakeTest(string projectName) : base(projectName) { @@ -43,7 +43,7 @@ protected BakeTest(string projectName) : base(projectName) [SetUp] public void SetUpBakeTest() { - _releases = new List(); + _releases = new List(); } protected Task ExecuteAsync( @@ -96,20 +96,20 @@ private static void ReplaceService(IServiceCollection serviceCollection, T in private class TestGitHub : IGitHub { - private readonly List _releases; + private readonly List _releases; public TestGitHub( - List releases) + List releases) { _releases = releases; } public Task CreateReleaseAsync( - Release release, + GitHubRelease gitHubRelease, GitHubInformation gitHubInformation, CancellationToken cancellationToken) { - _releases.Add(release); + _releases.Add(gitHubRelease); return Task.CompletedTask; } diff --git a/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs index bafbda06..aaada9f0 100644 --- a/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs +++ b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs @@ -89,10 +89,16 @@ private ReleaseFile NewReleaseFile(int fileCount = 3) destinationPath); } - private string NewFile() + private string NewFile(params string[] path) { + var parentDirectory = path.Aggregate(Path.GetTempPath(), Path.Combine); + if (!Directory.Exists(parentDirectory)) + { + Directory.CreateDirectory(parentDirectory); + } + var filePath = Path.Combine( - Path.GetTempPath(), + parentDirectory, $"{Guid.NewGuid():N}.txt"); File.WriteAllText(filePath, "Hello there!"); diff --git a/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs b/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs index 8fd3613f..fe18a404 100644 --- a/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs +++ b/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs @@ -60,14 +60,12 @@ public override Task> ComposeAsync( var gitHubDestination = context.Ingredients.Destinations .OfType() .SingleOrDefault(); - if (gitHubDestination == null) { return Task.FromResult(EmptyRecipes); } var release = context.GetArtifacts().SingleOrDefault(); - if (release == null) { return Task.FromResult(EmptyRecipes); @@ -76,9 +74,10 @@ public override Task> ComposeAsync( return Task.FromResult>( [ new GitHubReleaseRecipe( - context.Ingredients.GitHub, - context.Ingredients.Git.Sha, - release) + release.Text, + context.Ingredients.GitHub, + context.Ingredients.Git.Sha, + release.Files) ]); } } diff --git a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs index 1449b494..d96f3148 100644 --- a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs +++ b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs @@ -20,15 +20,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System.Collections.Concurrent; -using System.IO.Compression; -using System.Text; using Bake.Core; using Bake.Services; using Bake.ValueObjects; -using Bake.ValueObjects.Artifacts; using Bake.ValueObjects.Recipes.GitHub; -using Microsoft.Extensions.Logging; // ReSharper disable StringLiteralTypo @@ -36,28 +31,13 @@ namespace Bake.Cooking.Cooks.GitHub { public class GitHubReleaseCook : Cook { - private static readonly IReadOnlyDictionary NamingOs = new ConcurrentDictionary - { - [ExecutableOperatingSystem.Linux] = "linux", - [ExecutableOperatingSystem.MacOSX] = "macosx", - [ExecutableOperatingSystem.Windows] = "windows" - }; - private static readonly IReadOnlyDictionary NamingArch = new ConcurrentDictionary - { - [ExecutableArchitecture.Intel32] = "x86", - [ExecutableArchitecture.Intel64] = "x86_64", - }; - - private readonly ILogger _logger; private readonly IGitHub _gitHub; private readonly IFileSystem _fileSystem; public GitHubReleaseCook( - ILogger logger, IGitHub gitHub, IFileSystem fileSystem) { - _logger = logger; _gitHub = gitHub; _fileSystem = fileSystem; } @@ -67,71 +47,24 @@ protected override async Task CookAsync( GitHubReleaseRecipe recipe, CancellationToken cancellationToken) { - var additionalFiles = new[] - { - Path.Combine(context.Ingredients.WorkingDirectory, "README.md"), - Path.Combine(context.Ingredients.WorkingDirectory, "LICENSE"), - Path.Combine(context.Ingredients.WorkingDirectory, "RELEASE_NOTES.md"), - } - .Where(System.IO.File.Exists) - .Select(_fileSystem.Get) + var releaseFiles = recipe.Files + .Select(f => new GitHubReleaseFile( + _fileSystem.Get(f), + Path.GetFileName(f))) .ToArray(); - var releaseFiles = (await CreateReleaseFilesAsync(additionalFiles, recipe, cancellationToken)).ToList(); - - var release = new ValueObjects.Release( + var gitHubRelease = new GitHubRelease( context.Ingredients.Version, recipe.Sha, - string.Empty, + recipe.Text, releaseFiles); await _gitHub.CreateReleaseAsync( - release, + gitHubRelease, recipe.GitHubInformation, cancellationToken); return true; } - - private async Task> CreateReleaseFilesAsync( - IReadOnlyCollection additionalFiles, - GitHubReleaseRecipe recipe, - CancellationToken cancellationToken) - { - return await Task.WhenAll(recipe.Artifacts - .OfType() - .Select(async artifact => - { - var file = _fileSystem.Get(artifact.Path); - var fileName = CalculateArtifactFileName(artifact); - var compressedFile = await _fileSystem.CompressAsync( - fileName, - CompressionAlgorithm.ZIP, - Enumerable.Empty() - .Concat(additionalFiles) - .Concat([file]) - .ToArray(), - cancellationToken); - var sha256 = await compressedFile.GetHashAsync( - HashAlgorithm.SHA256, - cancellationToken); - return new LegacyReleaseFile( - compressedFile, - fileName, - sha256); - })); - } - - private static string CalculateArtifactFileName(ExecutableArtifact artifact) - { - var parts = new[] - { - artifact.Name, - NamingOs[artifact.Platform.Os], - NamingArch[artifact.Platform.Arch] - }; - - return $"{string.Join("_", parts)}.zip"; - } } } diff --git a/Source/Bake/Core/File.cs b/Source/Bake/Core/File.cs index 4cd0b724..4a67dffb 100644 --- a/Source/Bake/Core/File.cs +++ b/Source/Bake/Core/File.cs @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using System.Collections.Concurrent; using System.Security.Cryptography; using HashAlgorithm = Bake.ValueObjects.HashAlgorithm; @@ -27,6 +28,8 @@ namespace Bake.Core { public class File : IFile { + private static readonly ConcurrentDictionary>> CachedHashes = new(); + public string Path { get; } public string FileName => System.IO.Path.GetFileName(Path); public long Size => new FileInfo(Path).Length; @@ -68,16 +71,24 @@ public async Task GetHashAsync( throw new ArgumentOutOfRangeException(nameof(hashAlgorithm)); } - await using var stream = await OpenReadAsync(cancellationToken); + return await CachedHashes.GetOrAdd( + $"{hashAlgorithm}:{Path}", + _ => new Lazy>( + async () => + { + await using var stream = await OpenReadAsync(cancellationToken); - using var sha256 = SHA256.Create(); - var checksum = await sha256.ComputeHashAsync(stream, cancellationToken); - return BitConverter.ToString(checksum).Replace("-", string.Empty); + using var sha256 = SHA256.Create(); + var checksum = await sha256.ComputeHashAsync(stream, cancellationToken); + return BitConverter.ToString(checksum).Replace("-", string.Empty); + }, + LazyThreadSafetyMode.ExecutionAndPublication)).Value; } public void Dispose() { System.IO.File.Delete(Path); + GC.SuppressFinalize(this); } } } diff --git a/Source/Bake/Services/GitHub.cs b/Source/Bake/Services/GitHub.cs index dde15609..0cdc3dc2 100644 --- a/Source/Bake/Services/GitHub.cs +++ b/Source/Bake/Services/GitHub.cs @@ -30,7 +30,6 @@ using Author = Bake.ValueObjects.Author; using Commit = Bake.ValueObjects.Commit; using PullRequest = Bake.ValueObjects.PullRequest; -using Release = Bake.ValueObjects.Release; namespace Bake.Services { @@ -57,7 +56,7 @@ public GitHub( } public async Task CreateReleaseAsync( - Release release, + GitHubRelease gitHubRelease, GitHubInformation gitHubInformation, CancellationToken cancellationToken) { @@ -68,33 +67,33 @@ public async Task CreateReleaseAsync( "Could not create a GitHub release due to missing credentials"); } - var tag = $"v{release.Version}"; + var tag = $"v{gitHubRelease.Version}"; - var gitHubRelease = await gitHubClient.Repository.Release.Create( + var octoKitRelease = await gitHubClient.Repository.Release.Create( gitHubInformation.Owner, gitHubInformation.Repository, new NewRelease(tag) { - Prerelease = release.Version.IsPrerelease, - TargetCommitish = release.Sha, - Body = release.Body, + Prerelease = gitHubRelease.Version.IsPrerelease, + TargetCommitish = gitHubRelease.Sha, + Body = gitHubRelease.Body, Draft = true, - Name = $"v{release.Version}", + Name = $"v{gitHubRelease.Version}", }); - if (release.Files.Count != 0) + if (gitHubRelease.Files.Count != 0) { - var uploadTasks = release.Files - .Select(f => UploadFileAsync(f, gitHubRelease, gitHubClient, cancellationToken)); + var uploadTasks = gitHubRelease.Files + .Select(f => UploadFileAsync(f, octoKitRelease, gitHubClient, cancellationToken)); await Task.WhenAll(uploadTasks); } - var gitHubReleaseUpdate = gitHubRelease.ToUpdate(); + var gitHubReleaseUpdate = octoKitRelease.ToUpdate(); gitHubReleaseUpdate.Draft = false; await gitHubClient.Repository.Release.Edit( gitHubInformation.Owner, gitHubInformation.Repository, - gitHubRelease.Id, + octoKitRelease.Id, gitHubReleaseUpdate); } @@ -316,18 +315,18 @@ public async Task> GetPullRequestsAsync( } private async Task UploadFileAsync( - LegacyReleaseFile legacyReleaseFile, - Octokit.Release gitHubRelease, + GitHubReleaseFile gitHubReleaseFile, + Release gitHubRelease, IGitHubClient gitHubClient, CancellationToken cancellationToken) { var stopwatch = Stopwatch.StartNew(); _logger.LogDebug( "Uploading releaseFile {FileName} to GitHub release {ReleaseUrl}", - legacyReleaseFile.Source.FileName, + gitHubReleaseFile.Source.FileName, gitHubRelease.Url); - await using var stream = await legacyReleaseFile.Source.OpenReadAsync(cancellationToken); + await using var stream = await gitHubReleaseFile.Source.OpenReadAsync(cancellationToken); try { @@ -336,7 +335,7 @@ await gitHubClient.Repository.Release.UploadAsset( new ReleaseAssetUpload { ContentType = "application/octet-stream", - FileName = legacyReleaseFile.Destination, + FileName = gitHubReleaseFile.ReleaseFileName, RawData = stream, }, cancellationToken); @@ -349,7 +348,7 @@ await gitHubClient.Repository.Release.UploadAsset( _logger.LogInformation( "Done uploading releaseFile {FileName} to GitHub release {ReleaseUrl} after {TotalSeconds} seconds", - legacyReleaseFile.Source.FileName, + gitHubReleaseFile.Source.FileName, gitHubRelease.Url, stopwatch.Elapsed.TotalSeconds); } diff --git a/Source/Bake/Services/IGitHub.cs b/Source/Bake/Services/IGitHub.cs index dc7f1b12..fb420d83 100644 --- a/Source/Bake/Services/IGitHub.cs +++ b/Source/Bake/Services/IGitHub.cs @@ -27,7 +27,7 @@ namespace Bake.Services public interface IGitHub { Task CreateReleaseAsync( - Release release, + GitHubRelease gitHubRelease, GitHubInformation gitHubInformation, CancellationToken cancellationToken); diff --git a/Source/Bake/ValueObjects/Release.cs b/Source/Bake/ValueObjects/GitHubRelease.cs similarity index 88% rename from Source/Bake/ValueObjects/Release.cs rename to Source/Bake/ValueObjects/GitHubRelease.cs index e9b145bc..07cd389c 100644 --- a/Source/Bake/ValueObjects/Release.cs +++ b/Source/Bake/ValueObjects/GitHubRelease.cs @@ -24,16 +24,16 @@ namespace Bake.ValueObjects { - public class Release : Tag + public class GitHubRelease : Tag { public string Body { get; } - public IReadOnlyCollection Files { get; } + public IReadOnlyCollection Files { get; } - public Release( + public GitHubRelease( SemVer version, string sha, string body, - IReadOnlyCollection files) + IReadOnlyCollection files) : base(version, sha) { Body = body; diff --git a/Source/Bake/ValueObjects/LegacyReleaseFile.cs b/Source/Bake/ValueObjects/GitHubReleaseFile.cs similarity index 84% rename from Source/Bake/ValueObjects/LegacyReleaseFile.cs rename to Source/Bake/ValueObjects/GitHubReleaseFile.cs index d057580a..09db4b52 100644 --- a/Source/Bake/ValueObjects/LegacyReleaseFile.cs +++ b/Source/Bake/ValueObjects/GitHubReleaseFile.cs @@ -24,19 +24,16 @@ namespace Bake.ValueObjects; -public class LegacyReleaseFile +public class GitHubReleaseFile { public IFile Source { get; } - public string Destination { get; } - public string Sha256 { get; } + public string ReleaseFileName { get; } - public LegacyReleaseFile( + public GitHubReleaseFile( IFile source, - string destination, - string sha256) + string releaseFileName) { Source = source; - Destination = destination; - Sha256 = sha256; + ReleaseFileName = releaseFileName; } } diff --git a/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs b/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs index 135a8f29..b584e2a8 100644 --- a/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs +++ b/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs @@ -20,7 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using Bake.ValueObjects.Artifacts; using YamlDotNet.Serialization; namespace Bake.ValueObjects.Recipes.GitHub @@ -28,6 +27,9 @@ namespace Bake.ValueObjects.Recipes.GitHub [Recipe(Names.Recipes.GitHub.GitHubRelease)] public class GitHubReleaseRecipe : Recipe { + [YamlMember] + public string Text { get; [Obsolete] set; } = null!; + [YamlMember] public GitHubInformation GitHubInformation { get; [Obsolete] set; } = null!; @@ -35,20 +37,22 @@ public class GitHubReleaseRecipe : Recipe public string Sha { get; [Obsolete] set; } = null!; [YamlMember] - public ReleaseArtifact Release { get; [Obsolete] set; } = null!; + public string[] Files { get; [Obsolete] set; } = null!; [Obsolete] public GitHubReleaseRecipe() { } public GitHubReleaseRecipe( + string text, GitHubInformation gitHubInformation, string sha, - ReleaseArtifact release) + string[] files) { #pragma warning disable CS0612 // Type or member is obsolete + Text = text; GitHubInformation = gitHubInformation; Sha = sha; - Release = release; + Files = files; #pragma warning restore CS0612 // Type or member is obsolete } } From d50b3ab0b230bd3870ba24a01b43d4b0e10b8a6a Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 22 Dec 2024 18:15:40 +0100 Subject: [PATCH 08/17] Skip bake.local names in the release description --- Source/Bake/Cooking/Composers/ReleaseComposer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Bake/Cooking/Composers/ReleaseComposer.cs b/Source/Bake/Cooking/Composers/ReleaseComposer.cs index 4ed3a9b6..22465d31 100644 --- a/Source/Bake/Cooking/Composers/ReleaseComposer.cs +++ b/Source/Bake/Cooking/Composers/ReleaseComposer.cs @@ -194,6 +194,11 @@ private static void AddArtifactDescriptions( foreach (var artifact in g) { var containerArtifact = (ContainerArtifact) artifact; + if (containerArtifact.Name.StartsWith("bake.local", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + releaseText.AppendLine($"* `{containerArtifact.Name}`"); foreach (var tag in containerArtifact.Tags) { From edef4ecb7eceb3d0673e33f86eeee616604520b6 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Mon, 23 Dec 2024 07:32:09 +0100 Subject: [PATCH 09/17] Create destination directory --- Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs index 9273230b..fede4754 100644 --- a/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs +++ b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs @@ -84,6 +84,12 @@ private async Task CompressReleaseFilesAsync(ReleaseFile releaseFile, Canc } } + var destinationDirectory = Path.GetDirectoryName(releaseFile.Destination); + if (!string.IsNullOrEmpty(destinationDirectory) && !Directory.Exists(destinationDirectory)) + { + Directory.CreateDirectory(destinationDirectory); + } + _logger.LogInformation("Creating ZIP file at {Destination}", releaseFile.Destination); ZipFile.CreateFromDirectory(tmpDirectory, releaseFile.Destination); var fileInfo = new FileInfo(releaseFile.Destination); From 21f959be3c7ca9ee9e0a1f04e7905d59207e6be8 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Mon, 23 Dec 2024 07:50:58 +0100 Subject: [PATCH 10/17] Allow pushing to test nuget store --- Source/Bake.Tests/Files/nuget-config.xml | 6 ++++++ Source/Bake.Tests/Helpers/TestProject.cs | 11 +++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 Source/Bake.Tests/Files/nuget-config.xml diff --git a/Source/Bake.Tests/Files/nuget-config.xml b/Source/Bake.Tests/Files/nuget-config.xml new file mode 100644 index 00000000..7e956965 --- /dev/null +++ b/Source/Bake.Tests/Files/nuget-config.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/Source/Bake.Tests/Helpers/TestProject.cs b/Source/Bake.Tests/Helpers/TestProject.cs index 5f548932..fad726b0 100644 --- a/Source/Bake.Tests/Helpers/TestProject.cs +++ b/Source/Bake.Tests/Helpers/TestProject.cs @@ -46,7 +46,7 @@ protected TestProject( } [SetUp] - public void SetUpTestProject() + public async Task SetUpTestProject() { _folder = Folder.New; @@ -54,12 +54,19 @@ public void SetUpTestProject() { Sha = GitHelper.Create(_folder.Path); + var destination = Path.Join(_folder.Path, ProjectName); + DirectoryCopy( Path.Combine( ProjectHelper.GetRoot(), "TestProjects", ProjectName), - Path.Join(_folder.Path, ProjectName)); + destination); + + var nugetConfig = await ReadEmbeddedAsync("nuget-config.xml"); + await System.IO.File.WriteAllTextAsync( + Path.Combine(destination, "nuget.config"), + nugetConfig); } _previousCurrentDirectory = Directory.GetCurrentDirectory(); From 77e9979ec299679aa37be75a4b39882d08f47767 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Mon, 23 Dec 2024 07:52:40 +0100 Subject: [PATCH 11/17] Better name for release artifacts --- Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs index 8328ed7d..bab9396d 100644 --- a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs +++ b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs @@ -58,7 +58,7 @@ public override IAsyncEnumerable ValidateAsync(CancellationToken cancell public override IEnumerable PrettyNames() { - yield return Names.Artifacts.ReleaseArtifact; + return Files; } } } From d961bd9fec96d4315e14847b7350d9d29b19c2e7 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Mon, 23 Dec 2024 08:08:10 +0100 Subject: [PATCH 12/17] Add a random name to the release directory --- Source/Bake/Core/Defaults.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Bake/Core/Defaults.cs b/Source/Bake/Core/Defaults.cs index c5335a7c..0d2793cb 100644 --- a/Source/Bake/Core/Defaults.cs +++ b/Source/Bake/Core/Defaults.cs @@ -42,7 +42,7 @@ public class Defaults : IDefaults public string DotNetRollForward { get; private set; } = "LatestMajor"; public TimeSpan BakeIngredientsGatherTimeout { get; private set; } = TimeSpan.FromMinutes(5); public TimeSpan BakeComposeTimeout { get; private set; } = TimeSpan.FromMinutes(5); - public string BakeReleaseOutputDirectory { get; private set; } = Path.Combine(Path.GetTempPath(), "bake-release"); + public string BakeReleaseOutputDirectory { get; private set; } = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"), "bake-release"); public Defaults( IEnvironmentVariables environmentVariables) From dc1eb918d134857e14573435bd23f8b4ef06ca6b Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Mon, 23 Dec 2024 08:17:49 +0100 Subject: [PATCH 13/17] Be able to read and write ReleaseFile --- .../Bake/ValueObjects/Releases/ReleaseFile.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Source/Bake/ValueObjects/Releases/ReleaseFile.cs b/Source/Bake/ValueObjects/Releases/ReleaseFile.cs index 8ef9ccf3..d32b5009 100644 --- a/Source/Bake/ValueObjects/Releases/ReleaseFile.cs +++ b/Source/Bake/ValueObjects/Releases/ReleaseFile.cs @@ -20,22 +20,34 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using YamlDotNet.Serialization; + namespace Bake.ValueObjects.Releases { public class ReleaseFile { - public string Name { get; } - public string[] Sources { get; } - public string Destination { get; } + [YamlMember] + public string Name { get; [Obsolete] set; } = null!; + + [YamlMember] + public string[] Sources { get; [Obsolete] set; } = null!; + + [YamlMember] + public string Destination { get; [Obsolete] set; } = null!; + + [Obsolete] + public ReleaseFile() { } public ReleaseFile( string name, string[] sources, string destination) { +#pragma warning disable CS0612 // Type or member is obsolete Name = name; Sources = sources; Destination = destination; +#pragma warning restore CS0612 // Type or member is obsolete } } } From 61ca42d52c2fb242b7541e85261b3edf672defa7 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Mon, 23 Dec 2024 08:25:46 +0100 Subject: [PATCH 14/17] Add name --- Source/Bake/Names.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Bake/Names.cs b/Source/Bake/Names.cs index 68a6d8e1..1372b2df 100644 --- a/Source/Bake/Names.cs +++ b/Source/Bake/Names.cs @@ -70,6 +70,7 @@ public static class Artifacts [NuGetArtifact] = "nuget packages", [DocumentationSiteArtifact] = "documentation sites", [HelmChartArtifact] = "helm charts", + [ReleaseArtifact] = "released files", }; } From ba44e69ee6c42f4fb8566f49c2933dca5f930d5d Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Mon, 6 Jan 2025 20:36:10 +0100 Subject: [PATCH 15/17] Add missing arch --- Source/Bake/Cooking/Composers/ReleaseComposer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Bake/Cooking/Composers/ReleaseComposer.cs b/Source/Bake/Cooking/Composers/ReleaseComposer.cs index 22465d31..e40a1a9f 100644 --- a/Source/Bake/Cooking/Composers/ReleaseComposer.cs +++ b/Source/Bake/Cooking/Composers/ReleaseComposer.cs @@ -44,7 +44,9 @@ public class ReleaseComposer : Composer private static readonly IReadOnlyDictionary NamingArch = new ConcurrentDictionary { [ExecutableArchitecture.Intel32] = "x86", - [ExecutableArchitecture.Intel64] = "x86_64", + [ExecutableArchitecture.Intel64] = "x64", + [ExecutableArchitecture.Arm32] = "arm32", + [ExecutableArchitecture.Arm64] = "arm64", }; public override IReadOnlyCollection Consumes { get; } = From c035119df57841b6212bd6bbc3d5f36b45325706 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Mon, 6 Jan 2025 20:40:16 +0100 Subject: [PATCH 16/17] Update file headers --- Source/Bake.Tests/Helpers/TestService.cs | 4 ++-- Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs | 4 ++-- Source/Bake/Cooking/Composers/ReleaseComposer.cs | 4 ++-- Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs | 4 ++-- Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs | 4 ++-- Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs | 2 +- Source/Bake/ValueObjects/Releases/ReleaseFile.cs | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Source/Bake.Tests/Helpers/TestService.cs b/Source/Bake.Tests/Helpers/TestService.cs index 60b5f276..a9645100 100644 --- a/Source/Bake.Tests/Helpers/TestService.cs +++ b/Source/Bake.Tests/Helpers/TestService.cs @@ -1,6 +1,6 @@ -// MIT License +// MIT License // -// Copyright (c) 2021-2024 Rasmus Mikkelsen +// Copyright (c) 2021-2025 Rasmus Mikkelsen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs index aaada9f0..27b8f963 100644 --- a/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs +++ b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs @@ -1,6 +1,6 @@ -// MIT License +// MIT License // -// Copyright (c) 2021-2024 Rasmus Mikkelsen +// Copyright (c) 2021-2025 Rasmus Mikkelsen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/Source/Bake/Cooking/Composers/ReleaseComposer.cs b/Source/Bake/Cooking/Composers/ReleaseComposer.cs index e40a1a9f..2ceb557d 100644 --- a/Source/Bake/Cooking/Composers/ReleaseComposer.cs +++ b/Source/Bake/Cooking/Composers/ReleaseComposer.cs @@ -1,6 +1,6 @@ -// MIT License +// MIT License // -// Copyright (c) 2021-2024 Rasmus Mikkelsen +// Copyright (c) 2021-2025 Rasmus Mikkelsen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs index fede4754..0df30d9a 100644 --- a/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs +++ b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs @@ -1,6 +1,6 @@ -// MIT License +// MIT License // -// Copyright (c) 2021-2024 Rasmus Mikkelsen +// Copyright (c) 2021-2025 Rasmus Mikkelsen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs index bab9396d..df061ab0 100644 --- a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs +++ b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs @@ -1,6 +1,6 @@ -// MIT License +// MIT License // -// Copyright (c) 2021-2024 Rasmus Mikkelsen +// Copyright (c) 2021-2025 Rasmus Mikkelsen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs index e4c1f1e4..78acdd1f 100644 --- a/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs +++ b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs @@ -1,6 +1,6 @@ // MIT License // -// Copyright (c) 2021-2024 Rasmus Mikkelsen +// Copyright (c) 2021-2025 Rasmus Mikkelsen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/Source/Bake/ValueObjects/Releases/ReleaseFile.cs b/Source/Bake/ValueObjects/Releases/ReleaseFile.cs index d32b5009..98e679ca 100644 --- a/Source/Bake/ValueObjects/Releases/ReleaseFile.cs +++ b/Source/Bake/ValueObjects/Releases/ReleaseFile.cs @@ -1,6 +1,6 @@ -// MIT License +// MIT License // -// Copyright (c) 2021-2024 Rasmus Mikkelsen +// Copyright (c) 2021-2025 Rasmus Mikkelsen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal From 6484395851b88d4660d3751b505a8b470b1f540d Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Mon, 6 Jan 2025 21:16:44 +0100 Subject: [PATCH 17/17] Test release cook --- .../Cooking/Cooks/ReleaseCookTests.cs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs index 27b8f963..d1e96cb4 100644 --- a/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs +++ b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs @@ -72,11 +72,77 @@ public async Task Files() success.Should().BeTrue(); } + [Test] + public async Task Directories() + { + // Arrange + var context = NewContext(); + + // Act + var success = await Sut.CookAsync( + context, + new ReleaseRecipe( + string.Empty, + [ + NewReleaseDirectory() + ]), + Timeout); + + // Assert + success.Should().BeTrue(); + } + + [Test] + public async Task Mixed() + { + // Arrange + var context = NewContext(); + + // Act + var success = await Sut.CookAsync( + context, + new ReleaseRecipe( + string.Empty, + [ + NewMixedRelease(), + NewReleaseDirectory(), + NewReleaseFile(), + ]), + Timeout); + + // Assert + success.Should().BeTrue(); + } + private static Context NewContext() { return Context.New(ValueObjects.Ingredients.New(SemVer.Random, Path.GetTempPath())); } + private ReleaseFile NewMixedRelease() + { + var fileName = $"{Guid.NewGuid():N}.zip"; + var destinationPath = Path.Combine(Path.GetTempPath(), fileName); + DeleteAfter(destinationPath); + + return new ReleaseFile( + fileName, + [NewDirectory(), NewFile(), NewFile(), NewDirectory()], + destinationPath); + } + + private ReleaseFile NewReleaseDirectory() + { + var fileName = $"{Guid.NewGuid():N}.zip"; + var destinationPath = Path.Combine(Path.GetTempPath(), fileName); + DeleteAfter(destinationPath); + + return new ReleaseFile( + fileName, + [NewDirectory()], + destinationPath); + } + private ReleaseFile NewReleaseFile(int fileCount = 3) { var fileName = $"{Guid.NewGuid():N}.zip"; @@ -89,6 +155,21 @@ private ReleaseFile NewReleaseFile(int fileCount = 3) destinationPath); } + private string NewDirectory(params string[] path) + { + var name = Guid.NewGuid().ToString("N"); + path = path.Concat([name]).ToArray(); + var directory = path.Aggregate(Path.GetTempPath(), Path.Combine); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + _ = Enumerable.Range(0, 3).Select(_ => NewFile(directory)).ToArray(); + + return directory; + } + private string NewFile(params string[] path) { var parentDirectory = path.Aggregate(Path.GetTempPath(), Path.Combine);