diff --git a/.github/workflows/dependabot-cake.yml b/.github/workflows/dependabot-cake.yml
new file mode 100644
index 0000000..f34a967
--- /dev/null
+++ b/.github/workflows/dependabot-cake.yml
@@ -0,0 +1,13 @@
+name: Run dependabot for cake
+on:
+ workflow_dispatch:
+ schedule:
+ # run everyday at 6
+ - cron: '0 6 * * *'
+
+jobs:
+ dependabot-cake:
+ runs-on: ubuntu-latest # linux, because this is a docker-action
+ steps:
+ - name: check/update cake dependencies
+ uses: nils-org/dependabot-cake-action@v1
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0b598f7..f007d44 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,6 @@ docs/input/tasks/*
# Wyam related
config.wyam.*
+
+# JetBrains Rider
+.idea/
diff --git a/docs/input/guidelines/Analysers.md b/docs/input/guidelines/RecommendedReferences.md
similarity index 94%
rename from docs/input/guidelines/Analysers.md
rename to docs/input/guidelines/RecommendedReferences.md
index c996298..e824a01 100644
--- a/docs/input/guidelines/Analysers.md
+++ b/docs/input/guidelines/RecommendedReferences.md
@@ -21,8 +21,8 @@ To have consistency in code-style among the different tools/plugins the use of A
Example-Files can be found at:
-* [`stylecop.json`](./examples/StyleCopJson.md)
-* [`.editorconfig`](./examples/Editorconfig.md)
+* [`stylecop.json`](./examples/StyleCopJson)
+* [`.editorconfig`](./examples/Editorconfig)
## Related rules
diff --git a/docs/input/guidelines/TargetFramework.md b/docs/input/guidelines/TargetFramework.md
new file mode 100644
index 0000000..f8bc333
--- /dev/null
+++ b/docs/input/guidelines/TargetFramework.md
@@ -0,0 +1,55 @@
+---
+Order: 4
+Title: Target Frameworks
+---
+
+
+
+## Table of Contents
+
+- [Goals](#goals)
+ - [Required / Suggested versions](#required--suggested-versions)
+- [Related rules](#related-rules)
+- [Usage](#usage)
+- [Settings](#settings)
+ - [Opt-Out](#opt-out)
+
+
+
+## Goals
+
+As .NET Framework < 4.7.2 has issues with running .NET Standard assemblies, and Cake itself can run on .NET Framework 4.6.1 it is suggested to multi-target addins to `netstandard2.0` and `net461` to have the maximum compatibility.
+
+### Required / Suggested versions
+
+Depending on the referenced `Cake.Core`-version different target versions are required and/or suggested.
+Missing a required target version will raise [CCG0007](../rules/ccg0007) as an error
+while missing a suggested target version will raise [CCG0007](../rules/ccg0007) as a warning.
+
+* Cake.Core <= 0.33.0
+ * Required: `netstandard2.0`
+ * Suggested: `net461`
+ * alternative: `net46`
+
+## Related rules
+
+ * [CCG0007](../rules/ccg0007)
+
+## Usage
+
+Using this package automatically enables this guideline.
+
+## Settings
+
+### Opt-Out
+
+It it possible to opt-out of the check for target framework using the following setting:
+
+(*Keep in mind, though that it is not recommended to opt-out of this feature*)
+
+```xml
+
+
+
+
+```
diff --git a/docs/input/rules/ccg0006.md b/docs/input/rules/ccg0006.md
index f3c7e0e..7918565 100644
--- a/docs/input/rules/ccg0006.md
+++ b/docs/input/rules/ccg0006.md
@@ -4,7 +4,7 @@ Title: CCG0006
Description: Missing recommended configuraion-file
---
- > No reference to `.editorconfig` found. Usage of `.editorconfig` is strongly recommended.
+ > No reference to `[fileName]` found. Usage of `[fileName]` is strongly recommended.
@@ -23,20 +23,19 @@ This warning is raised, when a recommended configuration-file (i.e. `stylecop.js
## Description
-Code-Formatting and layout should be properly configured. This is done using
-blah and blah.
+Code-Formatting and layout should be properly configured. This is done using `.editorconfig` and `stylecop.json`.
## How to fix violations
-Add a balh and blah to the project.
+Add the required files to the project.
Example-Files can be found at:
-* [`stylecop.json`](../guidelines/examples/StyleCopJson.md)
-* [`.editorconfig`](../guidelines/examples/Editorconfig.md)
+* [`stylecop.json`](../guidelines/examples/StyleCopJson)
+* [`.editorconfig`](../guidelines/examples/Editorconfig)
(Or opt-out of this rule, by setting `CakeContribGuidelinesOmitRecommendedConfigFile`)
## Related guidelines
-* [Usage of Analysers](../guidelines/Analysers)
\ No newline at end of file
+* [Recommended References](../guidelines/RecommendedReferences)
\ No newline at end of file
diff --git a/docs/input/rules/ccg0007.md b/docs/input/rules/ccg0007.md
new file mode 100644
index 0000000..27afffc
--- /dev/null
+++ b/docs/input/rules/ccg0007.md
@@ -0,0 +1,43 @@
+---
+Order: 7
+Title: CCG0007
+Description: Missing recommended target
+---
+
+ > Missing required target: netstandard2.0
+
+
+
+## Table of Contents
+
+- [Cause](#cause)
+- [Description](#description)
+- [How to fix violations](#how-to-fix-violations)
+- [Related guidelines](#related-guidelines)
+
+
+
+## Cause
+
+This warning is raised, when the addin is not targeted to a recommended target version.
+Also, This could be raised as an error, if a required target version is not set.
+
+## Description
+
+Addins should be multi-targeted to `netstandard2.0` and `net461` to have the maximum compatibility.
+
+## How to fix violations
+
+Add the recommended target(s) to the project:
+
+```xml
+
+ netstandard2.0;net472
+
+```
+
+(Or opt-out of this rule, by setting `CakeContribGuidelinesOmitTargetFramework`)
+
+## Related guidelines
+
+* [Target Frameworks](../guidelines/TargetFramework)
\ No newline at end of file
diff --git a/recipe.cake b/recipe.cake
index 59360d1..9089caa 100644
--- a/recipe.cake
+++ b/recipe.cake
@@ -1,4 +1,4 @@
-#load nuget:?package=Cake.Recipe&version=2.0.0
+#load nuget:?package=Cake.Recipe&version=2.0.1
Environment.SetVariableNames();
diff --git a/src/CakeContrib.Guidelines.sln.DotSettings b/src/CakeContrib.Guidelines.sln.DotSettings
new file mode 100644
index 0000000..61f5d6a
--- /dev/null
+++ b/src/CakeContrib.Guidelines.sln.DotSettings
@@ -0,0 +1,6 @@
+
+ True
+ True
+ True
+ True
+ True
\ No newline at end of file
diff --git a/src/Guidelines/build/CakeContrib.Guidelines.targets b/src/Guidelines/build/CakeContrib.Guidelines.targets
index da22dc5..645ec72 100644
--- a/src/Guidelines/build/CakeContrib.Guidelines.targets
+++ b/src/Guidelines/build/CakeContrib.Guidelines.targets
@@ -9,4 +9,5 @@
+
diff --git a/src/Guidelines/build/TargetFrameworkVersions.targets b/src/Guidelines/build/TargetFrameworkVersions.targets
new file mode 100644
index 0000000..611d042
--- /dev/null
+++ b/src/Guidelines/build/TargetFrameworkVersions.targets
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tasks.IntegrationTests/E2eTests.cs b/src/Tasks.IntegrationTests/E2eTests.cs
index 32cafe6..07d4f74 100644
--- a/src/Tasks.IntegrationTests/E2eTests.cs
+++ b/src/Tasks.IntegrationTests/E2eTests.cs
@@ -1,14 +1,14 @@
using System;
using System.IO;
-using CakeContrib.Guidelines.Tasks.Tests.Fixtures;
+using CakeContrib.Guidelines.Tasks.IntegrationTests.Fixtures;
using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
-namespace CakeContrib.Guidelines.Tasks.Tests
+namespace CakeContrib.Guidelines.Tasks.IntegrationTests
{
// TODO: Writing things to disk is not deterministic...
// TODO: Running Code-Coverage on the integration-tests breaks all tests
@@ -194,5 +194,35 @@ public void Missing_file_editorconfig_results_in_CCG0006_warning()
result.WarningLines.Should().Contain(l => l.IndexOf("CCG0006", StringComparison.Ordinal) > -1);
result.WarningLines.Should().Contain(l => l.IndexOf(".editorconfig", StringComparison.Ordinal) > -1);
}
+
+ [Fact]
+ public void Missing_Required_Target_results_in_CCG0007_error()
+ {
+ // given
+ fixture.WithTargetFrameworks("net47");
+
+ // when
+ var result = fixture.Run();
+
+ // then
+ result.IsErrorExitCode.Should().BeTrue();
+ result.ErrorLines.Should().Contain(l => l.IndexOf("CCG0007", StringComparison.Ordinal) > -1);
+ result.ErrorLines.Should().Contain(l => l.IndexOf("netstandard2.0", StringComparison.Ordinal) > -1);
+ }
+
+ [Fact]
+ public void Missing_Suggested_Target_results_in_CCG0007_warning()
+ {
+ // given
+ fixture.WithTargetFrameworks("netstandard2.0");
+
+ // when
+ var result = fixture.Run();
+
+ // then
+ result.IsErrorExitCode.Should().BeFalse();
+ result.WarningLines.Should().Contain(l => l.IndexOf("CCG0007", StringComparison.Ordinal) > -1);
+ result.WarningLines.Should().Contain(l => l.IndexOf("net461", StringComparison.Ordinal) > -1);
+ }
}
}
diff --git a/src/Tasks.IntegrationTests/Fixtures/E2eTestFixture.cs b/src/Tasks.IntegrationTests/Fixtures/E2eTestFixture.cs
index 7b814c3..aa7aa83 100644
--- a/src/Tasks.IntegrationTests/Fixtures/E2eTestFixture.cs
+++ b/src/Tasks.IntegrationTests/Fixtures/E2eTestFixture.cs
@@ -6,7 +6,7 @@
using Xunit.Abstractions;
-namespace CakeContrib.Guidelines.Tasks.Tests.Fixtures
+namespace CakeContrib.Guidelines.Tasks.IntegrationTests.Fixtures
{
public class E2eTestFixture : IDisposable
{
@@ -18,7 +18,8 @@ public class E2eTestFixture : IDisposable
private bool hasStylecopJson = true;
private bool hasStylecopReference = true;
private bool hasEditorConfig = true;
- private List customContent = new List();
+ private readonly List customContent = new List();
+ private string targetFrameworks = "netstandard2.0;net461";
public E2eTestFixture(string tempFolder, ITestOutputHelper logger)
{
@@ -41,7 +42,7 @@ private string WriteProject()
- netstandard2.0
+ {5}
{2}
@@ -95,7 +96,8 @@ private string WriteProject()
targets.Item2,
string.Join(Environment.NewLine, properties),
string.Join(Environment.NewLine, items),
- string.Join(Environment.NewLine, customContent)));
+ string.Join(Environment.NewLine, customContent),
+ targetFrameworks));
return csproj;
}
@@ -135,6 +137,11 @@ internal void WithoutFileEditorconfig()
hasEditorConfig = false;
}
+ internal void WithTargetFrameworks(string targetFrameworks)
+ {
+ this.targetFrameworks = targetFrameworks;
+ }
+
private Tuple GetTargetsToImport()
{
var codeBase = typeof(E2eTestFixture).Assembly.CodeBase;
diff --git a/src/Tasks.IntegrationTests/Tasks.IntegrationTests.csproj b/src/Tasks.IntegrationTests/Tasks.IntegrationTests.csproj
index e97ecb1..aeadf7d 100644
--- a/src/Tasks.IntegrationTests/Tasks.IntegrationTests.csproj
+++ b/src/Tasks.IntegrationTests/Tasks.IntegrationTests.csproj
@@ -28,9 +28,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
-
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Tasks.Tests/CheckPrivateAssetsOnReferencesTests.cs b/src/Tasks.Tests/CheckPrivateAssetsOnReferencesTests.cs
index 657193f..7597e8a 100644
--- a/src/Tasks.Tests/CheckPrivateAssetsOnReferencesTests.cs
+++ b/src/Tasks.Tests/CheckPrivateAssetsOnReferencesTests.cs
@@ -45,7 +45,7 @@ public void Should_Error_If_Package_Has_Not_PrivateAssets()
{
// given
var fixture = new CheckPrivateAssetsOnReferencesFixture();
- fixture.WithReferencedPackage("Cake.Core", "");
+ fixture.WithReferencedPackage("Cake.Core");
fixture.WithPackageToCheck("Cake.Core");
// when
@@ -60,7 +60,7 @@ public void Should_Log_Correct_ErrorCode_On_Error()
{
// given
var fixture = new CheckPrivateAssetsOnReferencesFixture();
- fixture.WithReferencedPackage("Cake.Core", "");
+ fixture.WithReferencedPackage("Cake.Core");
fixture.WithPackageToCheck("Cake.Core");
// when
@@ -78,7 +78,7 @@ public void Should_Log_Correct_ErrorSourceFile_On_Error_With_Given_ProjectFile()
// given
const string projectFileName = "some.project.csproj";
var fixture = new CheckPrivateAssetsOnReferencesFixture();
- fixture.WithReferencedPackage("Cake.Core", "");
+ fixture.WithReferencedPackage("Cake.Core");
fixture.WithPackageToCheck("Cake.Core");
fixture.WithProjectFile(projectFileName);
diff --git a/src/Tasks.Tests/Extensions/ExtensionTests.cs b/src/Tasks.Tests/Extensions/ExtensionTests.cs
new file mode 100644
index 0000000..07a5693
--- /dev/null
+++ b/src/Tasks.Tests/Extensions/ExtensionTests.cs
@@ -0,0 +1,67 @@
+using System;
+
+using CakeContrib.Guidelines.Tasks.Extensions;
+
+using FluentAssertions;
+
+using Xunit;
+
+namespace CakeContrib.Guidelines.Tasks.Tests.Extensions
+{
+ public class VersionExtensionTests
+ {
+ [Fact]
+ public void GreaterEqual_Is_True_For_Equal_Versions()
+ {
+ var a = new Version(1, 2, 3);
+ var b = new Version(a.Major, a.Minor, a.Build);
+
+ a.GreaterEqual(b).Should().BeTrue();
+ }
+
+ [Fact]
+ public void GreaterEqual_Is_True_For_Greater_Versions()
+ {
+ var a = new Version(1, 2, 3);
+ var b = new Version(a.Major, a.Minor, a.Build - 1);
+
+ a.GreaterEqual(b).Should().BeTrue();
+ }
+
+ [Fact]
+ public void GreaterEqual_Is_False_For_Lesser_Versions()
+ {
+ var a = new Version(1, 2, 3);
+ var b = new Version(a.Major, a.Minor, a.Build + 1);
+
+ a.GreaterEqual(b).Should().BeFalse();
+ }
+
+ [Fact]
+ public void LessEqual_Is_True_For_Equal_Versions()
+ {
+ var a = new Version(1, 2, 3);
+ var b = new Version(a.Major, a.Minor, a.Build);
+
+ a.LessEqual(b).Should().BeTrue();
+ }
+
+ [Fact]
+ public void LessEqual_Is_True_For_Lesser_Versions()
+ {
+ var a = new Version(1, 2, 3);
+ var b = new Version(a.Major, a.Minor, a.Build + 1);
+
+ a.LessEqual(b).Should().BeTrue();
+ }
+
+ [Fact]
+ public void LessEqual_Is_False_For_Greater_Versions()
+ {
+ var a = new Version(1, 2, 3);
+ var b = new Version(a.Major, a.Minor, a.Build - 1);
+
+ a.LessEqual(b).Should().BeFalse();
+ }
+ }
+}
diff --git a/src/Tasks.Tests/Fixtures/RequiredFileStylecopJsonFixture.cs b/src/Tasks.Tests/Fixtures/RequiredFileStylecopJsonFixture.cs
index 2bb8beb..10c107a 100644
--- a/src/Tasks.Tests/Fixtures/RequiredFileStylecopJsonFixture.cs
+++ b/src/Tasks.Tests/Fixtures/RequiredFileStylecopJsonFixture.cs
@@ -2,8 +2,6 @@
using Microsoft.Build.Framework;
-using Moq;
-
namespace CakeContrib.Guidelines.Tasks.Tests.Fixtures
{
public class RequiredFileStylecopJsonFixture : BaseBuildFixture
diff --git a/src/Tasks.Tests/Fixtures/TargetFrameworkVersionsFixture.cs b/src/Tasks.Tests/Fixtures/TargetFrameworkVersionsFixture.cs
new file mode 100644
index 0000000..578bf23
--- /dev/null
+++ b/src/Tasks.Tests/Fixtures/TargetFrameworkVersionsFixture.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using System.Linq;
+
+using Microsoft.Build.Framework;
+
+using Moq;
+
+namespace CakeContrib.Guidelines.Tasks.Tests.Fixtures
+{
+ public class TargetFrameworkVersionsFixture : BaseBuildFixture
+ {
+ private readonly List references;
+ private readonly List targetFrameworks;
+ private ITaskItem targetFramework;
+ private readonly List omittedTargets;
+
+ public TargetFrameworkVersionsFixture()
+ {
+ references = new List();
+ omittedTargets = new List();
+ targetFrameworks = new List();
+ targetFramework = null;
+ }
+
+ public override bool Execute()
+ {
+ Task.References = references.ToArray();
+ Task.TargetFramework = targetFramework;
+ Task.TargetFrameworks = targetFrameworks.ToArray();
+ Task.Omitted = omittedTargets.ToArray();
+ return base.Execute();
+ }
+
+ public void WithTargetFramwork(string packageName)
+ {
+ targetFramework = GetMockTaskItem(packageName).Object;
+ }
+
+ public void WithTargetFramworks(params string[] packageNames)
+ {
+ targetFrameworks.AddRange(packageNames.Select(n => GetMockTaskItem(n).Object));
+ }
+
+ public void WithOmittedTargetFramework(string targetFramework)
+ {
+ omittedTargets.Add(GetMockTaskItem(targetFramework).Object);
+ }
+
+ public void WithCakeCoreReference(int major = 0, int minor = 0, int patch = 0)
+ {
+ var cakeRef = GetMockTaskItem("Cake.Core");
+ cakeRef.Setup(x => x.GetMetadata("Version")).Returns($"{major}.{minor}.{patch}");
+ references.Add(cakeRef.Object);
+ }
+
+ public void WithProjectFile(string fileName)
+ {
+ Task.ProjectFile = fileName;
+ }
+ }
+}
diff --git a/src/Tasks.Tests/RequiredReferencesTests.cs b/src/Tasks.Tests/RequiredReferencesTests.cs
index 54b98fd..5a4b30b 100644
--- a/src/Tasks.Tests/RequiredReferencesTests.cs
+++ b/src/Tasks.Tests/RequiredReferencesTests.cs
@@ -35,7 +35,7 @@ public void Should_Warn_If_RequiredPackage_Is_Not_Referenced()
fixture.WithRequiredReferences("Some.Analyser");
// when
- var actual = fixture.Execute();
+ fixture.Execute();
// then
fixture.BuildEngine.WarningEvents.Should().HaveCount(1);
@@ -54,7 +54,7 @@ public void Should_Not_Warn_If_RequiredPackage_Is_Omitted()
fixture.WithOmittedReferences(required);
// when
- var actual = fixture.Execute();
+ fixture.Execute();
// then
fixture.BuildEngine.WarningEvents.Should().HaveCount(0);
diff --git a/src/Tasks.Tests/TargetFrameworkVersionsTests.cs b/src/Tasks.Tests/TargetFrameworkVersionsTests.cs
new file mode 100644
index 0000000..d27c38e
--- /dev/null
+++ b/src/Tasks.Tests/TargetFrameworkVersionsTests.cs
@@ -0,0 +1,107 @@
+using System.Linq;
+
+using CakeContrib.Guidelines.Tasks.Tests.Fixtures;
+
+using FluentAssertions;
+
+using Xunit;
+
+namespace CakeContrib.Guidelines.Tasks.Tests
+{
+ public class TargetFrameworkVersionsTests
+ {
+ private const string NetStandard20 = "netstandard2.0";
+ private const string Net46 = "net46";
+ private const string Net461 = "net461";
+
+ [Fact]
+ public void Should_Error_If_RequiredTargetFramework_Is_Not_Targeted()
+ {
+ // given
+ var fixture = new TargetFrameworkVersionsFixture();
+
+ // when
+ fixture.Execute();
+
+ // then
+ fixture.BuildEngine.ErrorEvents.Should().HaveCount(1);
+ fixture.BuildEngine.ErrorEvents.First().Message.Should().Contain(NetStandard20);
+ }
+
+ [Fact]
+ public void Should_Not_Error_If_RequiredTargetFramework_Is_Not_Targeted_But_Omitted()
+ {
+ // given
+ var fixture = new TargetFrameworkVersionsFixture();
+ fixture.WithOmittedTargetFramework(NetStandard20);
+
+ // when
+ fixture.Execute();
+
+ // then
+ fixture.BuildEngine.ErrorEvents.Should().HaveCount(0);
+ }
+
+ [Fact]
+ public void Should_Warn_If_SuggestedTargetFramework_Is_Not_Targeted()
+ {
+ // given
+ var fixture = new TargetFrameworkVersionsFixture();
+ fixture.WithTargetFramwork(NetStandard20);
+
+ // when
+ fixture.Execute();
+
+ // then
+ fixture.BuildEngine.ErrorEvents.Should().HaveCount(0);
+ fixture.BuildEngine.WarningEvents.Should().HaveCount(1);
+ fixture.BuildEngine.WarningEvents.First().Message.Should().Contain(Net46);
+ }
+
+ [Fact]
+ public void Should_Not_Warn_If_SuggestedTargetFramework_Is_Not_Targeted_But_Omitted()
+ {
+ // given
+ var fixture = new TargetFrameworkVersionsFixture();
+ fixture.WithTargetFramwork(NetStandard20);
+ fixture.WithOmittedTargetFramework(Net461);
+
+ // when
+ fixture.Execute();
+
+ // then
+ fixture.BuildEngine.ErrorEvents.Should().HaveCount(0);
+ fixture.BuildEngine.WarningEvents.Should().HaveCount(0);
+ }
+
+ [Fact]
+ public void Should_Not_Warn_If_Required_And_SuggestedTargetFramework_Is_Targeted()
+ {
+ // given
+ var fixture = new TargetFrameworkVersionsFixture();
+ fixture.WithTargetFramworks(NetStandard20, Net461);
+
+ // when
+ fixture.Execute();
+
+ // then
+ fixture.BuildEngine.ErrorEvents.Should().HaveCount(0);
+ fixture.BuildEngine.WarningEvents.Should().HaveCount(0);
+ }
+
+ [Fact]
+ public void Should_Not_Warn_If_Required_And_Alternative_SuggestedTargetFramework_Is_Targeted()
+ {
+ // given
+ var fixture = new TargetFrameworkVersionsFixture();
+ fixture.WithTargetFramworks(NetStandard20, Net46);
+
+ // when
+ fixture.Execute();
+
+ // then
+ fixture.BuildEngine.ErrorEvents.Should().HaveCount(0);
+ fixture.BuildEngine.WarningEvents.Should().HaveCount(0);
+ }
+ }
+}
diff --git a/src/Tasks.Tests/Tasks.Tests.csproj b/src/Tasks.Tests/Tasks.Tests.csproj
index b9cb776..fe81bbc 100644
--- a/src/Tasks.Tests/Tasks.Tests.csproj
+++ b/src/Tasks.Tests/Tasks.Tests.csproj
@@ -28,9 +28,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
-
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Tasks/Extensions/VersionExtensions.cs b/src/Tasks/Extensions/VersionExtensions.cs
new file mode 100644
index 0000000..a84f80a
--- /dev/null
+++ b/src/Tasks/Extensions/VersionExtensions.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace CakeContrib.Guidelines.Tasks.Extensions
+{
+ ///
+ /// internal extensions to .
+ ///
+ internal static class VersionExtensions
+ {
+ ///
+ /// compares two versions.
+ ///
+ /// the version that is extended.
+ /// the version to compare to.
+ ///
+ /// true, if is greater or equal to .
+ /// false, otherwise.
+ ///
+ public static bool GreaterEqual(this Version baseVersion, Version compareTo)
+ {
+ if (baseVersion == null)
+ {
+ throw new ArgumentNullException(nameof(baseVersion));
+ }
+
+ if (compareTo == null)
+ {
+ throw new ArgumentNullException(nameof(compareTo));
+ }
+
+ return baseVersion.CompareTo(compareTo) > -1;
+ }
+
+ ///
+ /// compares two versions.
+ ///
+ /// the version that is extended.
+ /// the version to compare to.
+ ///
+ /// true, if is less or equal to .
+ /// false, otherwise.
+ ///
+ public static bool LessEqual(this Version baseVersion, Version compareTo)
+ {
+ if (baseVersion == null)
+ {
+ throw new ArgumentNullException(nameof(baseVersion));
+ }
+
+ if (compareTo == null)
+ {
+ throw new ArgumentNullException(nameof(compareTo));
+ }
+
+ return baseVersion.CompareTo(compareTo) < 1;
+ }
+ }
+}
diff --git a/src/Tasks/TargetFrameworkVersions.cs b/src/Tasks/TargetFrameworkVersions.cs
new file mode 100644
index 0000000..144b13c
--- /dev/null
+++ b/src/Tasks/TargetFrameworkVersions.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using CakeContrib.Guidelines.Tasks.Extensions;
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace CakeContrib.Guidelines.Tasks
+{
+ ///
+ /// The Task to check for References for the guideline .
+ ///
+ public class TargetFrameworkVersions : Task
+ {
+ private const string NetStandard20 = "netstandard2.0";
+ private const string Net46 = "net46";
+ private const string Net461 = "net461";
+
+ private static readonly Version Zero26 = new Version(0, 26, 0);
+
+ private static readonly TargetsDefinitions DefaultTarget = new TargetsDefinitions
+ {
+ RequiredTargets = new[] { TargetsDefinition.From(NetStandard20) },
+ SuggestedTargets = new[] { TargetsDefinition.From(Net461, Net46) },
+ };
+
+ private static readonly Dictionary, TargetsDefinitions> SpecificTargets =
+ new Dictionary, TargetsDefinitions>
+ {
+ {
+ v => v.GreaterEqual(Zero26),
+ new TargetsDefinitions
+ {
+ RequiredTargets = new[] { TargetsDefinition.From(NetStandard20) },
+ SuggestedTargets = new[] { TargetsDefinition.From(Net461, Net46) },
+ }
+ },
+ };
+
+ ///
+ /// Gets or sets the References.
+ ///
+ [Required]
+ public ITaskItem[] References { get; set; }
+
+ ///
+ /// Gets or sets the TargetFrameworks.
+ ///
+ [Required]
+ public ITaskItem[] TargetFrameworks { get; set; }
+
+ ///
+ /// Gets or sets the TargetFramework.
+ ///
+ [Required]
+ public ITaskItem TargetFramework { get; set; }
+
+ ///
+ /// Gets or sets the project file.
+ ///
+ public string ProjectFile { get; set; }
+
+ ///
+ /// Gets or sets Targets to omit. I.e. if those are missing, they will not be reported.
+ ///
+ public ITaskItem[] Omitted { get; set; }
+
+ ///
+ public override bool Execute()
+ {
+ // find cake.core version
+ var cakeCore =
+ References?.FirstOrDefault(x => x.ToString().Equals("Cake.Core", StringComparison.OrdinalIgnoreCase));
+ if (cakeCore == null)
+ {
+ Log.LogMessage(
+ MessageImportance.Low,
+ "Could not find Cake.Core reference. Using default TargetVersions.");
+ return Execute(DefaultTarget);
+ }
+
+ if (!Version.TryParse(cakeCore.GetMetadata("version"), out Version version))
+ {
+ Log.LogWarning(
+ $"Cake.Core has a version of {cakeCore.GetMetadata("version")} which is not a valid version. Using default TargetVersions.");
+ return Execute(DefaultTarget);
+ }
+
+ foreach (var targetsDefinition in SpecificTargets)
+ {
+ var match = targetsDefinition.Key(version);
+ if (!match)
+ {
+ continue;
+ }
+
+ return Execute(targetsDefinition.Value);
+ }
+
+ Log.LogMessage(
+ MessageImportance.Low,
+ $"Could not find a specific TargetVersions-setting for Cake.Core version {version}. Using default TargetVersions.");
+ return Execute(DefaultTarget);
+ }
+
+ private bool Execute(TargetsDefinitions targets)
+ {
+ var allTargets = new List();
+ if (TargetFramework != null)
+ {
+ allTargets.Add(TargetFramework.ToString());
+ }
+
+ if (TargetFrameworks != null)
+ {
+ allTargets.AddRange(TargetFrameworks.Select(x => x.ToString()));
+ }
+
+ allTargets = allTargets.Distinct().ToList();
+
+ // first, check required targets
+ Log.LogMessage(
+ MessageImportance.Low,
+ $"Comparing TargetFramework[s] ({string.Join(";", allTargets)}) to required: {string.Join(",", targets.RequiredTargets.Select(x => x.Name))}.");
+
+ foreach (var requiredTarget in targets.RequiredTargets)
+ {
+ if (Omitted != null && Omitted.Any(x => x.ToString().Equals(requiredTarget.Name, StringComparison.OrdinalIgnoreCase)))
+ {
+ Log.LogMessage(MessageImportance.Low, $"Required TargetFramework '{requiredTarget.Name}' is set to omit.");
+ continue;
+ }
+
+ if (allTargets.Any(x => x.Equals(requiredTarget.Name, StringComparison.OrdinalIgnoreCase)))
+ {
+ continue;
+ }
+
+ var found = requiredTarget.Alternatives?.Any(alternative => allTargets.Contains(alternative));
+ if (found.GetValueOrDefault(false))
+ {
+ continue;
+ }
+
+ Log.LogError(
+ null,
+ "CCG0007",
+ string.Empty,
+ ProjectFile ?? string.Empty,
+ 0,
+ 0,
+ 0,
+ 0,
+ "Missing required target: " + requiredTarget.Name);
+ return false;
+ }
+
+ // now, check suggested targets
+ Log.LogMessage(
+ MessageImportance.Low,
+ $"Comparing TargetFramework[s] ({string.Join(";", allTargets)}) to suggested: {string.Join(",", targets.SuggestedTargets.Select(x => x.Name))}.");
+
+ foreach (var suggestedTarget in targets.SuggestedTargets)
+ {
+ if (Omitted != null && Omitted.Any(x => x.ToString().Equals(suggestedTarget.Name, StringComparison.OrdinalIgnoreCase)))
+ {
+ Log.LogMessage(MessageImportance.Low, $"Suggested TargetFramework '{suggestedTarget.Name}' is set to omit.");
+ continue;
+ }
+
+ if (allTargets.Any(x => x.Equals(suggestedTarget.Name, StringComparison.OrdinalIgnoreCase)))
+ {
+ continue;
+ }
+
+ var found = suggestedTarget.Alternatives?.Any(alternative => allTargets.Contains(alternative));
+ if (found.GetValueOrDefault(false))
+ {
+ continue;
+ }
+
+ Log.LogWarning(
+ null,
+ "CCG0007",
+ string.Empty,
+ ProjectFile ?? string.Empty,
+ 0,
+ 0,
+ 0,
+ 0,
+ "Missing suggested target: " + suggestedTarget.Name);
+ }
+
+ return true;
+ }
+
+ private class TargetsDefinitions
+ {
+ public TargetsDefinition[] RequiredTargets { get; set; }
+
+ public TargetsDefinition[] SuggestedTargets { get; set; }
+ }
+
+ private class TargetsDefinition
+ {
+ public string Name { get; private set; }
+
+ public string[] Alternatives { get; private set; }
+
+ public static TargetsDefinition From(string name, params string[] alternatives)
+ {
+ return new TargetsDefinition { Name = name, Alternatives = alternatives ?? Array.Empty(), };
+ }
+ }
+ }
+}