From 9737668321b1720684b7cbf1b7cb304ead34a08c Mon Sep 17 00:00:00 2001 From: Nils Andresen Date: Fri, 3 Jan 2025 20:40:34 +0100 Subject: [PATCH] (#387) Added support for Central Package Mangagement --- src/Directory.Build.props | 5 ++ src/Generators/Generators.csproj | 2 +- .../build/CakeInternalReferences.targets | 1 + .../build/RecommendedCakeVersion.targets | 1 + .../build/TargetFrameworkVersions.targets | 1 + src/Tasks.IntegrationTests/E2eTests.cs | 68 ++++++++++-------- .../Fixtures/E2eTestFixture.cs | 67 ++++++++++++++--- .../Tasks.IntegrationTests.csproj | 2 +- .../Fixtures/RecommendedCakeVersionFixture.cs | 30 +++++++- .../RecommendedCakeVersionTests.cs | 71 +++++++++++++++++++ src/Tasks.Tests/Tasks.Tests.csproj | 2 +- src/Tasks/CheckCakeInternalReferences.cs | 10 ++- src/Tasks/ITaskItemExtensions.cs | 47 ++++++++++++ src/Tasks/RecommendedCakeVersion.cs | 9 ++- src/Tasks/TargetFrameworkVersions.cs | 8 ++- src/Tasks/Tasks.csproj | 8 +-- 16 files changed, 281 insertions(+), 51 deletions(-) create mode 100644 src/Directory.Build.props create mode 100644 src/Tasks/ITaskItemExtensions.cs diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..66325ee --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,5 @@ + + + latest + + diff --git a/src/Generators/Generators.csproj b/src/Generators/Generators.csproj index 5ae7ae1..cd220f1 100644 --- a/src/Generators/Generators.csproj +++ b/src/Generators/Generators.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net9.0 enable enable diff --git a/src/Guidelines/build/CakeInternalReferences.targets b/src/Guidelines/build/CakeInternalReferences.targets index b666eed..aab2157 100644 --- a/src/Guidelines/build/CakeInternalReferences.targets +++ b/src/Guidelines/build/CakeInternalReferences.targets @@ -16,6 +16,7 @@ ProjectType="$(CakeContribGuidelinesProjectType)" CakeVersion="$(CakeContribGuidelinesOverrideTargetFrameworkCakeVersion)" References="@(PackageReference)" + PackageVersions="@(PackageVersion)" NoWarn="$(NoWarn)" WarningsAsErrors="$(WarningsAsErrors)" /> diff --git a/src/Guidelines/build/RecommendedCakeVersion.targets b/src/Guidelines/build/RecommendedCakeVersion.targets index 36b6d53..7cd6b61 100644 --- a/src/Guidelines/build/RecommendedCakeVersion.targets +++ b/src/Guidelines/build/RecommendedCakeVersion.targets @@ -22,6 +22,7 @@ ProjectFile="$(MSBuildProjectFullPath)" RecommendedVersion="$(RecommendedCakeVersion)" References="@(PackageReference)" + PackageVersions="@(PackageVersion)" Omitted="@(CakeContribGuidelinesOmitRecommendedCakeVersion)" ReferencesToCheck="@(CakeReferenceToCheck)" ProjectType="$(CakeContribGuidelinesProjectType)" diff --git a/src/Guidelines/build/TargetFrameworkVersions.targets b/src/Guidelines/build/TargetFrameworkVersions.targets index 605dc98..ddfd2f1 100644 --- a/src/Guidelines/build/TargetFrameworkVersions.targets +++ b/src/Guidelines/build/TargetFrameworkVersions.targets @@ -16,6 +16,7 @@ l.IndexOf("CCG0007", StringComparison.Ordinal) > -1); - result.ErrorLines.ShouldContain(l => l.IndexOf("netstandard2.0", StringComparison.Ordinal) > -1); + result.ErrorLines.ShouldContain(l => l.IndexOf(fixture.DefaultTargetFrameworkForModules, StringComparison.Ordinal) > -1); } [Fact] public void Missing_Suggested_Target_results_in_CCG0007_warning() { + var missingTfm = fixture.DefaultTargetFrameworksForAddins + .Split(";", StringSplitOptions.RemoveEmptyEntries) + .First(); + var allTfmButMissing = fixture.DefaultTargetFrameworksForAddins.Replace(missingTfm, string.Empty); + // given - fixture.WithTargetFrameworks("netstandard2.0"); + fixture.WithTargetFrameworks(allTfmButMissing); // when var result = fixture.Run(); // then - result.IsErrorExitCode.ShouldBeFalse(); - result.WarningLines.ShouldContain(l => l.IndexOf("CCG0007", StringComparison.Ordinal) > -1); - result.WarningLines.ShouldContain(l => l.IndexOf("net461", StringComparison.Ordinal) > -1); + result.IsErrorExitCode.ShouldBeTrue(); + result.ErrorLines.ShouldContain(l => l.IndexOf("CCG0007", StringComparison.Ordinal) > -1); + result.ErrorLines.ShouldContain(l => l.IndexOf(missingTfm, StringComparison.Ordinal) > -1); } [Fact] @@ -246,6 +251,7 @@ public void Missing_Suggested_Target_net5_for_Cake_v100_results_in_CCG0007_warni fixture.WithoutDefaultCakeReference(); fixture.WithPackageReference("Cake.Core", "1.0.0", "all"); fixture.WithTargetFrameworks("netstandard2.0;net461"); + fixture.OmitRecommendedCakeVersion(); // when var result = fixture.Run(); @@ -351,7 +357,7 @@ public void ProjectType_When_Assembly_Is_Module_Is_Module() { // given fixture.WithAssemblyName("Cake.Buildsystems.Module"); - fixture.WithTargetFrameworks("netstandard2.0"); + fixture.WithTargetFrameworks(fixture.DefaultTargetFrameworkForModules); fixture.WithCustomContent(@" Cake.Buildsystems.Module @@ -498,7 +504,7 @@ public void Packaging_Different_Types_Should_Add_The_Correct_Icon(string assembl fixture.WithAssemblyName(assemblyName); if (isModule) { - fixture.WithTargetFrameworks("netstandard2.0"); + fixture.WithTargetFrameworks(fixture.DefaultTargetFrameworkForModules); } fixture.WithCustomContent(@" @@ -537,7 +543,7 @@ public void Packaging_Different_Types_Should_Add_The_Correct_IconUrl_To_Properti fixture.WithAssemblyName(assemblyName); if (isModule) { - fixture.WithTargetFrameworks("netstandard2.0"); + fixture.WithTargetFrameworks(fixture.DefaultTargetFrameworkForModules); } fixture.WithCustomContent(@" @@ -589,7 +595,7 @@ public void Missing_Module_Tag_Should_Raise_CCG0008_For_Modules() Cake.Buildsystems.Module "); fixture.WithTags("cake build cake-build script"); - fixture.WithTargetFrameworks("netstandard2.0"); + fixture.WithTargetFrameworks(fixture.DefaultTargetFrameworkForModules); // when var result = fixture.Run(); @@ -651,6 +657,7 @@ public void Incorrect_Cake_Reference_Should_Raise_CCG0009() // given fixture.WithoutDefaultCakeReference(); fixture.WithPackageReference("cake.core", "0.38.5", "All"); + fixture.WithTargetFrameworks("netstandard2.0;net461;net5.0"); // when var result = fixture.Run(); @@ -667,6 +674,7 @@ public void Incorrect_But_Omitted_Cake_Reference_Should_Not_Raise_CCG0009() // given fixture.WithoutDefaultCakeReference(); fixture.WithPackageReference("cake.core", "0.38.5", "All"); + fixture.WithTargetFrameworks("netstandard2.0;net461;net5.0"); fixture.WithCustomContent(@" @@ -680,29 +688,11 @@ public void Incorrect_But_Omitted_Cake_Reference_Should_Not_Raise_CCG0009() result.WarningLines.ShouldBeEmpty(); } - [Fact] - public void Missing_Suggested_Target_results_not_in_CCG0007_warning_if_NoWarn_is_set() - { - // given - fixture.WithTargetFrameworks("netstandard2.0"); - fixture.WithCustomContent(@" - - 1701;1702;ccg0007 -"); - - // when - var result = fixture.Run(); - - // then - result.IsErrorExitCode.ShouldBeFalse(); - result.WarningLines.ShouldNotContain(l => l.IndexOf("CCG0007", StringComparison.Ordinal) > -1); - } - [Fact] public void Missing_Suggested_Target_results_in_CCG0007_error_if_WarningsAsErrors_is_set() { // given - fixture.WithTargetFrameworks("netstandard2.0"); + fixture.WithTargetFrameworks(fixture.DefaultTargetFrameworkForModules); fixture.WithCustomContent(@" NU1605;ccg0007 @@ -738,5 +728,21 @@ public void Missing_Required_Target_results_in_CCG0007_error_if_Cake_Version_Is_ err.ShouldNotBeNull(); err.ShouldContain("netstandard2.0"); } + + [Fact] + public void Central_Package_Management_Correct_Cake_Reference_Should_Not_Raise_CCG0009() + { + // given + fixture.WithoutDefaultCakeReference(); + fixture.WithPackageReference("cake.core", privateAssets: "All"); + fixture.WithCpmPackageVersion("Cake.Core", fixture.DefaultCakeVersion); + + // when + var result = fixture.Run(); + + // then + result.IsErrorExitCode.ShouldBeFalse(); + result.WarningLines.ShouldBeEmpty(); + } } } diff --git a/src/Tasks.IntegrationTests/Fixtures/E2eTestFixture.cs b/src/Tasks.IntegrationTests/Fixtures/E2eTestFixture.cs index 7e0de69..49fc8ab 100644 --- a/src/Tasks.IntegrationTests/Fixtures/E2eTestFixture.cs +++ b/src/Tasks.IntegrationTests/Fixtures/E2eTestFixture.cs @@ -22,11 +22,16 @@ public class E2eTestFixture : IDisposable private bool hasEditorConfig = true; private bool omitRecommendedCakeVersion = false; private bool hasDefaultCakeReference = true; - private readonly List customContent = new List(); - private string targetFrameworks = "netstandard2.0;net461;net5.0"; - private readonly List references = new List(); + private readonly List customContent = new(); + private string targetFrameworks = "net8.0;net9.0"; + private readonly List references = new(); + private readonly Dictionary cpmPackageVersions = new(); private string tags = "cake;cake-build;build;script;addin;cake-addin;module;cake-module;recipe;cake-recipe"; + public string DefaultCakeVersion => "5.0.0"; + public string DefaultTargetFrameworkForModules => "net8.0"; + public string DefaultTargetFrameworksForAddins => "net8.0;net9.0"; + public E2eTestFixture(string tempFolder, ITestOutputHelper logger) { this.tempFolder = tempFolder; @@ -78,6 +83,7 @@ private string WriteProject() { properties.Add($"{tags}"); } + if (hasStylecopJson) { var stylecopJson = Path.Combine(tempFolder, "stylecop.json"); @@ -95,8 +101,14 @@ private string WriteProject() } if (hasStylecopReference) { - items.Add(@" - + var version = "Version=\"1.1.118\""; + if (cpmPackageVersions.Any()) + { + version = string.Empty; + } + + items.Add($@" + runtime; build; native; contentfiles; analyzers; buildtransitive all "); @@ -104,7 +116,7 @@ private string WriteProject() if (hasDefaultCakeReference) { - WithPackageReference("cake.core","1.0.0", "all"); + WithPackageReference("cake.core", DefaultCakeVersion, "all"); } if (omitRecommendedCakeVersion) @@ -116,6 +128,32 @@ private string WriteProject() "); } + if (cpmPackageVersions.Any()) + { + if (hasStylecopReference) + { + cpmPackageVersions.Add("StyleCop.Analyzers", "1.1.118"); + } + + var cpmFile = Path.Combine(tempFolder, "Directory.Packages.props"); + File.WriteAllText(cpmFile, +$""" + + + true + + + { + string.Join( + Environment.NewLine, + cpmPackageVersions.Select(kvp => $"").ToArray() + ) + } + + +"""); + } + items.AddRange(references); File.WriteAllText(csproj, string.Format(template, @@ -157,12 +195,18 @@ internal void OmitRecommendedCakeVersion() internal void WithPackageReference( string packageName, - string version, + string version = null, string privateAssets = null, params Tuple[] additionalAttributes) { var reference = new StringBuilder(); - reference.Append($@" - net6.0;net7.0; + net8.0;net9.0; $(TargetFrameworks);net472 CakeContrib.Guidelines.Tasks.IntegrationTests CakeContrib.Guidelines.Tasks.IntegrationTests diff --git a/src/Tasks.Tests/Fixtures/RecommendedCakeVersionFixture.cs b/src/Tasks.Tests/Fixtures/RecommendedCakeVersionFixture.cs index 758200f..b669c41 100644 --- a/src/Tasks.Tests/Fixtures/RecommendedCakeVersionFixture.cs +++ b/src/Tasks.Tests/Fixtures/RecommendedCakeVersionFixture.cs @@ -8,6 +8,7 @@ namespace CakeContrib.Guidelines.Tasks.Tests.Fixtures public class RecommendedCakeVersionFixture : BaseBuildFixture { private readonly List references; + private readonly List cpmVersions; private readonly List omitted; private readonly List referencesToCheck; @@ -16,6 +17,7 @@ public RecommendedCakeVersionFixture() references = new List(); omitted = new List(); referencesToCheck = new List(); + cpmVersions = new List(); Task.ProjectType = CakeProjectType.Addin.ToString(); } @@ -24,6 +26,7 @@ public override bool Execute() Task.Omitted = omitted.ToArray(); Task.References = references.ToArray(); Task.ReferencesToCheck = referencesToCheck.ToArray(); + Task.PackageVersions = cpmVersions.ToArray(); return base.Execute(); } @@ -67,7 +70,7 @@ public void WithProjectTypeRecipe() { Task.ProjectType = "Recipe"; } - + public void WithNoWarn(params string[] rules) { Task.NoWarn = rules; @@ -77,5 +80,30 @@ public void WithWarningsAsErrors(params string[] rules) { Task.WarningsAsErrors = rules; } + + public void WithCpmReference(string referenceName, string versionOverride = null) + { + var attributes = new Dictionary(); + if (versionOverride != null) + { + attributes.Add("VersionOverride", versionOverride); + } + + var reference = GetMockTaskItem( + referenceName, + attributes); + references.Add(reference.Object); + } + + public void WithCpmPackageVersion(string referenceName, string version) + { + var reference = GetMockTaskItem( + referenceName, + new Dictionary + { + { "version", version } + }); + cpmVersions.Add(reference.Object); + } } } diff --git a/src/Tasks.Tests/RecommendedCakeVersionTests.cs b/src/Tasks.Tests/RecommendedCakeVersionTests.cs index 8052112..1b4215e 100644 --- a/src/Tasks.Tests/RecommendedCakeVersionTests.cs +++ b/src/Tasks.Tests/RecommendedCakeVersionTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using CakeContrib.Guidelines.Tasks.Tests.Fixtures; @@ -157,5 +158,75 @@ public void Should_Suggest_Consolidation_Versions() && x.Code.Equals("CCG0009", StringComparison.OrdinalIgnoreCase) && x.Message.StartsWith("2 different")); } + + [Fact] + public void Should_Not_Warn_For_Central_Package_Management_And_Correct_Version() + { + // given + var fixture = new RecommendedCakeVersionFixture(); + fixture.WithCpmPackageVersion("cake.core", "1.0.0"); + fixture.WithCpmReference("cake.core"); + fixture.WithReferencesToCheck("cake.core"); + fixture.WithRecommendedVersion("1.0.0"); + + // when + fixture.Execute(); + + // then + fixture.BuildEngine.WarningEvents.Count.ShouldBe(0); + } + + [Fact] + public void Should_Warn_For_Central_Package_Management_And_Incorrect_Version() + { + // given + var fixture = new RecommendedCakeVersionFixture(); + fixture.WithCpmPackageVersion("cake.core", "0.38.5"); + fixture.WithCpmReference("cake.core"); + fixture.WithReferencesToCheck("cake.core"); + fixture.WithRecommendedVersion("1.0.0"); + + // when + fixture.Execute(); + + // then + fixture.BuildEngine.WarningEvents.Count.ShouldBe(1); + fixture.BuildEngine.WarningEvents.First().Code.ShouldBe("CCG0009"); + } + + [Fact] + public void Should_Not_Warn_For_Central_Package_Management_And_Correct_Version_Override() + { + // given + var fixture = new RecommendedCakeVersionFixture(); + fixture.WithCpmPackageVersion("cake.core", "0.38.5"); + fixture.WithCpmReference("cake.core", "1.0.0"); + fixture.WithReferencesToCheck("cake.core"); + fixture.WithRecommendedVersion("1.0.0"); + + // when + fixture.Execute(); + + // then + fixture.BuildEngine.WarningEvents.Count.ShouldBe(0); + } + + [Fact] + public void Should_Warn_For_Central_Package_Management_And_Wrong_Version_Override() + { + // given + var fixture = new RecommendedCakeVersionFixture(); + fixture.WithCpmPackageVersion("cake.core", "1.0.0"); + fixture.WithCpmReference("cake.core", "0.38.5"); + fixture.WithReferencesToCheck("cake.core"); + fixture.WithRecommendedVersion("1.0.0"); + + // when + fixture.Execute(); + + // then + fixture.BuildEngine.WarningEvents.Count.ShouldBe(1); + fixture.BuildEngine.WarningEvents.First().Code.ShouldBe("CCG0009"); + } } } diff --git a/src/Tasks.Tests/Tasks.Tests.csproj b/src/Tasks.Tests/Tasks.Tests.csproj index 1caad29..bc73b46 100644 --- a/src/Tasks.Tests/Tasks.Tests.csproj +++ b/src/Tasks.Tests/Tasks.Tests.csproj @@ -7,7 +7,7 @@ Remove this hack if Cake.Recipe bumps the usage of Cake.Incubator to version 7.0.0 --> netcoreapp3.1 - net6.0;net7.0; + net8.0;net9.0; $(TargetFrameworks);net472 CakeContrib.Guidelines.Tasks.Tests CakeContrib.Guidelines.Tasks.Tests diff --git a/src/Tasks/CheckCakeInternalReferences.cs b/src/Tasks/CheckCakeInternalReferences.cs index 7831a04..ed0dc95 100644 --- a/src/Tasks/CheckCakeInternalReferences.cs +++ b/src/Tasks/CheckCakeInternalReferences.cs @@ -194,6 +194,12 @@ public class CheckCakeInternalReferences : Task [Required] public ITaskItem[] References { get; set; } + /// + /// Gets or sets the PackageVersions (For Central Package Management). + /// + [Required] + public ITaskItem[] PackageVersions { get; set; } + /// /// Gets or sets the warnings that are suppressed. /// @@ -238,7 +244,7 @@ public override bool Execute() return true; } - CakeVersion = cakeCore.GetMetadata("version"); + CakeVersion = cakeCore.GetVersion(PackageVersions, Log); var prereleaseIndex = CakeVersion.IndexOf("-", StringComparison.Ordinal); if (prereleaseIndex > -1) { @@ -277,7 +283,7 @@ public override bool Execute() var referencesInProject = References.Select(x => new { Name = x.ToString(), - Version = x.GetMetadata("version"), + Version = x.GetVersion(PackageVersions, Log), IsPrivate = (x.GetMetadata("PrivateAssets")?.ToLower() ?? string.Empty) == "all", }).ToArray(); diff --git a/src/Tasks/ITaskItemExtensions.cs b/src/Tasks/ITaskItemExtensions.cs new file mode 100644 index 0000000..61260d7 --- /dev/null +++ b/src/Tasks/ITaskItemExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace CakeContrib.Guidelines.Tasks +{ + internal static class ITaskItemExtensions + { + internal static string GetVersion( + this ITaskItem item, + ITaskItem[] centralPackageManagementVersions, + TaskLoggingHelper log) + { + var name = item.ToString(); + var version = item.GetMetadata("version"); + + if (string.IsNullOrEmpty(version)) + { + // check Central Package Management + var cpmItem = centralPackageManagementVersions.FirstOrDefault(x => x.ToString().Equals(name, StringComparison.OrdinalIgnoreCase)); + if (cpmItem != null) + { + version = cpmItem.GetMetadata("version"); + } + + // this IS a CPM reference, but now it could be overridden. + var versionOverride = item.GetMetadata("VersionOverride"); + if (!string.IsNullOrEmpty(versionOverride)) + { + version = versionOverride; + } + } + + if (string.IsNullOrEmpty(version)) + { + // we have a reference, without a "version" that is not a CPM reference. + // set version to "null" as string, so we can generate a nicer warning. + version = "null"; + } + + log.LogMessage(MessageImportance.Low, $"Version of {name}: {version}"); + return version; + } + } +} diff --git a/src/Tasks/RecommendedCakeVersion.cs b/src/Tasks/RecommendedCakeVersion.cs index 1fe42ab..a227fb5 100644 --- a/src/Tasks/RecommendedCakeVersion.cs +++ b/src/Tasks/RecommendedCakeVersion.cs @@ -26,6 +26,12 @@ public class RecommendedCakeVersion : Task [Required] public ITaskItem[] References { get; set; } + /// + /// Gets or sets the PackageVersions (For Central Package Management). + /// + [Required] + public ITaskItem[] PackageVersions { get; set; } + /// /// Gets or sets the references to omit. /// @@ -74,7 +80,8 @@ public override bool Execute() foreach (var r in References) { var package = r.ToString(); - var version = r.GetMetadata("version"); + var version = r.GetVersion(PackageVersions, Log); + if (!toCheck.Any(x => x.Equals(package, StringComparison.OrdinalIgnoreCase))) { // not a cake reference diff --git a/src/Tasks/TargetFrameworkVersions.cs b/src/Tasks/TargetFrameworkVersions.cs index 8730425..9512f8e 100644 --- a/src/Tasks/TargetFrameworkVersions.cs +++ b/src/Tasks/TargetFrameworkVersions.cs @@ -174,6 +174,12 @@ public class TargetFrameworkVersions : Task [Required] public ITaskItem[] References { get; set; } + /// + /// Gets or sets the PackageVersions (For Central Package Management). + /// + [Required] + public ITaskItem[] PackageVersions { get; set; } + /// /// Gets or sets the TargetFrameworks. /// @@ -242,7 +248,7 @@ public override bool Execute() return Execute(DefaultTarget); } - CakeVersion = cakeCore.GetMetadata("version"); + CakeVersion = cakeCore.GetVersion(PackageVersions, Log); var prereleaseIndex = CakeVersion.IndexOf("-", StringComparison.Ordinal); if (prereleaseIndex > -1) { diff --git a/src/Tasks/Tasks.csproj b/src/Tasks/Tasks.csproj index cf4322d..ba83622 100644 --- a/src/Tasks/Tasks.csproj +++ b/src/Tasks/Tasks.csproj @@ -9,8 +9,8 @@ - - + + @@ -51,9 +51,9 @@ - $(SolutionDir)/Guidelines/tasks/$(TargetFramework) + $(ProjectDir)../Guidelines/tasks/$(TargetFramework) - \ No newline at end of file +