From cc8c34112acc0e870e624ec0255bd8df760898e0 Mon Sep 17 00:00:00 2001 From: Joaquim Alvino de Mesquita Neto Date: Mon, 5 Dec 2022 13:59:23 +0100 Subject: [PATCH] Unopioninate and Refactor strategies (#52) --- .../wooga/gradle/version/VersionPlugin.groovy | 12 +- .../wooga/gradle/version/VersionScheme.groovy | 20 +- .../release/opinion/Strategies.groovy | 176 +++++++++++++++--- .../release/semver/SemVerStrategyState.groovy | 1 + .../release/semver/StrategyUtil.groovy | 61 ++++++ .../strategies/NetflixOssStrategies.groovy | 102 ++++------ .../strategies/NewSemverV1Strategies.groovy | 163 ++++++++++++++++ .../strategies/NewSemverV2Strategies.groovy | 161 ++++++++++++++++ .../strategies/SemverV1Strategies.groovy | 9 +- .../strategies/SemverV2Strategies.groovy | 6 +- .../strategies/StaticMarkerStrategies.groovy | 5 +- .../version/strategies/WdkStrategies.groovy | 25 +-- .../operations/NormalPartials.groovy | 81 ++++++++ .../opinion/LegacyNuGetStrategies.groovy | 50 +++++ .../SemverV2WithDefaultStrategies.groovy | 131 +++++++++++++ .../opinion/WdkNuGetStrategies.groovy | 52 ++++++ .../strategies/partials/NormalPartials.groovy | 27 +++ .../gradle/version/VersionPluginSpec.groovy | 3 +- 18 files changed, 955 insertions(+), 130 deletions(-) create mode 100644 src/main/groovy/wooga/gradle/version/strategies/NewSemverV1Strategies.groovy create mode 100644 src/main/groovy/wooga/gradle/version/strategies/NewSemverV2Strategies.groovy create mode 100644 src/main/groovy/wooga/gradle/version/strategies/operations/NormalPartials.groovy create mode 100644 src/main/groovy/wooga/gradle/version/strategies/opinion/LegacyNuGetStrategies.groovy create mode 100644 src/main/groovy/wooga/gradle/version/strategies/opinion/SemverV2WithDefaultStrategies.groovy create mode 100644 src/main/groovy/wooga/gradle/version/strategies/opinion/WdkNuGetStrategies.groovy create mode 100644 src/main/groovy/wooga/gradle/version/strategies/partials/NormalPartials.groovy diff --git a/src/main/groovy/wooga/gradle/version/VersionPlugin.groovy b/src/main/groovy/wooga/gradle/version/VersionPlugin.groovy index 417009a..edb9e09 100644 --- a/src/main/groovy/wooga/gradle/version/VersionPlugin.groovy +++ b/src/main/groovy/wooga/gradle/version/VersionPlugin.groovy @@ -60,13 +60,10 @@ class VersionPlugin implements Plugin { } finally { def sharedVersion = new ToStringProvider(extension.version.map({ it.version })) - project.allprojects(new Action() { - @Override - void execute(Project prj) { - prj.setVersion(sharedVersion) - prj.extensions.create(VersionCodeExtension, "versionCode", DefaultVersionCodeExtension.class, prj, extension.versionCode) - } - }) + project.allprojects{ Project prj -> + prj.setVersion(sharedVersion) + prj.extensions.create(VersionCodeExtension, "versionCode", DefaultVersionCodeExtension.class, prj, extension.versionCode) + } } } @@ -86,7 +83,6 @@ class VersionPlugin implements Plugin { def rawValue = VersionPluginConventions.versionCodeOffset.getValue(project) Integer.parseInt(rawValue.toString()) })) - extension } } diff --git a/src/main/groovy/wooga/gradle/version/VersionScheme.groovy b/src/main/groovy/wooga/gradle/version/VersionScheme.groovy index 61db2e6..bd6f641 100644 --- a/src/main/groovy/wooga/gradle/version/VersionScheme.groovy +++ b/src/main/groovy/wooga/gradle/version/VersionScheme.groovy @@ -20,10 +20,10 @@ package wooga.gradle.version import wooga.gradle.version.internal.release.base.VersionStrategy import wooga.gradle.version.internal.release.semver.SemVerStrategy -import wooga.gradle.version.strategies.SemverV1Strategies -import wooga.gradle.version.strategies.SemverV2Strategies import wooga.gradle.version.strategies.StaticMarkerStrategies import wooga.gradle.version.strategies.WdkStrategies +import wooga.gradle.version.strategies.opinion.LegacyNuGetStrategies +import wooga.gradle.version.strategies.opinion.SemverV2WithDefaultStrategies //TODO: Rename to VersionSchemes when breaking change /** @@ -32,14 +32,14 @@ import wooga.gradle.version.strategies.WdkStrategies * plus additional strategies if so desired. Check IVersionScheme for more details. */ enum VersionScheme implements IVersionScheme { - semver(SemverV1Strategies.DEVELOPMENT, - SemverV1Strategies.SNAPSHOT, - SemverV1Strategies.PRE_RELEASE, - SemverV1Strategies.FINAL), - semver2(SemverV2Strategies.DEVELOPMENT, - SemverV2Strategies.SNAPSHOT, - SemverV2Strategies.PRE_RELEASE, - SemverV2Strategies.FINAL), + semver(LegacyNuGetStrategies.DEVELOPMENT, + LegacyNuGetStrategies.SNAPSHOT, + LegacyNuGetStrategies.PRE_RELEASE, + LegacyNuGetStrategies.FINAL), + semver2(SemverV2WithDefaultStrategies.DEVELOPMENT, + SemverV2WithDefaultStrategies.SNAPSHOT, + SemverV2WithDefaultStrategies.PRE_RELEASE, + SemverV2WithDefaultStrategies.FINAL), /** * Strategies for versions generated based on a static marker. Based on Semver v2. */ diff --git a/src/main/groovy/wooga/gradle/version/internal/release/opinion/Strategies.groovy b/src/main/groovy/wooga/gradle/version/internal/release/opinion/Strategies.groovy index ef33b94..18c6bca 100644 --- a/src/main/groovy/wooga/gradle/version/internal/release/opinion/Strategies.groovy +++ b/src/main/groovy/wooga/gradle/version/internal/release/opinion/Strategies.groovy @@ -18,6 +18,7 @@ package wooga.gradle.version.internal.release.opinion import wooga.gradle.version.ReleaseStage import wooga.gradle.version.internal.release.semver.ChangeScope import wooga.gradle.version.internal.release.semver.SemVerStrategyState +import wooga.gradle.version.internal.release.semver.StrategyUtil import java.sql.Timestamp import java.text.SimpleDateFormat @@ -44,11 +45,15 @@ final class Strategies { * Sample strategies that infer the normal component of a version. */ static final class Normal { + /** + * Do not modify the pre-release component. + */ + static final PartialSemVerStrategy NONE = closure { state -> state } /** * Increments the nearest normal version using the scope specified * in the {@link SemVerStrategyState#scopeFromProp}. */ - static final PartialSemVerStrategy USE_SCOPE_PROP = closure { state -> + static final PartialSemVerStrategy USE_SCOPE_STATE = closure { state -> return incrementNormalFromScope(state, state.scopeFromProp) } @@ -72,6 +77,26 @@ final class Strategies { } } + /** + * If the nearest any is higher from the nearest normal, sets the + * normal component to the nearest any's normal component. Otherwise + * do nothing. + * + *

+ * For example, if the nearest any is {@code 1.2.3-alpha.1} and the + * nearest normal is {@code 1.2.2}, this will infer the normal + * component as {@code 1.2.3}. + *

+ */ + static final PartialSemVerStrategy NEAREST_HIGHER_ANY = closure { state -> + def nearest = state.nearestVersion + if (nearest.any.lessThanOrEqualTo(nearest.normal)) { + return state + } else { + return state.copyWith(inferredNormal: nearest.any.normalVersion) + } + } + /** * Enforces that the normal version complies with the current branch's major version. * If the branch is not in the format {@code #.x} (e.g. {@code 2.x}), this will do @@ -144,12 +169,31 @@ final class Strategies { * */ static PartialSemVerStrategy fromBranchPattern(Pattern pattern) { - return closure { state -> - def m = state.currentBranch.name =~ pattern + return closure { SemVerStrategyState state -> + return fromMatchingBranchName(state.currentBranch.name, pattern).infer(state) + } + } + + /** + * Uses the specified pattern to enforce that versions inferred on this branch + * comply. Patterns should have 1 or 2 capturing groups representing the + * major and, optionally, the minor component of the version. + * + *
    + *
  • If the current branch doesn't match the pattern do nothing.
  • + *
  • If only the major is specified in the branch name, and the nearest normal complies with that major, do nothing.
  • + *
  • If the patch component can be incremented and still comply with the branch, do so.
  • + *
  • If the minor component can be incremented to comply with the branch, do so.
  • + *
  • If the major component can be incremented to comply with the branch, do so.
  • + *
  • Otherwise fail, because the version can't comply with the branch.
  • + *
+ */ + static PartialSemVerStrategy fromMatchingBranchName(String branchName, Pattern pattern) { + return closure { SemVerStrategyState state -> + def m = branchName =~ pattern if (m) { def major = m.groupCount() >= 1 ? parseIntOrZero(m[0][1]) : -1 def minor = m.groupCount() >= 2 ? parseIntOrZero(m[0][2]) : -1 - def normal = state.nearestVersion.normal def majorDiff = major - normal.majorVersion def minorDiff = minor - normal.minorVersion @@ -167,7 +211,7 @@ final class Strategies { // only major specified in branch name and already matches return state } else { - throw new GradleException("Invalid branch (${state.currentBranch.name}) for nearest normal (${normal}).") + throw new GradleException("Invalid branch (${branchName}) for nearest normal (${state.nearestVersion.normal}).", e) } } else { return state @@ -175,6 +219,21 @@ final class Strategies { } } + /** + * Uses given scope if state's branch name matches pattern + * @param pattern - Pattern to match with state's branch name + * @param scope - Scope to be used in strategy if pattern matches + * @return PartialSemVerStrategy that executes the operation described above + */ + static PartialSemVerStrategy scopeIfBranchMatchesPattern(Pattern pattern, ChangeScope scope) { + return closure { SemVerStrategyState state -> + def m = state.currentBranch.name =~ pattern + if (m.matches()) { + return useScope(scope).infer(state) + } + return state + } + } /** * Always use the scope provided to increment the normal component. */ @@ -207,10 +266,14 @@ final class Strategies { static final PartialSemVerStrategy STAGE_FIXED = closure { state -> state.copyWith(inferredPreRelease: state.stageFromProp) } /** - * Sets the pre-release component to the value of {@link SemVerStrategyState#stageFromProp}. + * Appends a timestamp in the format yyyyMMddHHmm to the pre-release component + * @param separator - join component between the pre-release component and the timestamp. + * @return partial strategy function appendint timestamp to the pre-release component. */ - static final PartialSemVerStrategy STAGE_TIMESTAMP = closure { state -> - state.copyWith(inferredPreRelease: "${GenerateTimestamp()}") + static PartialSemVerStrategy withTimestamp(String separator = ".") { + closure { SemVerStrategyState state -> + state.copyWith(inferredPreRelease: "${state.inferredPreRelease}${separator}${GenerateTimestamp()}") + } } /** @@ -243,28 +306,74 @@ final class Strategies { } } + /** + * Appends a branch name to the pre-release component. + * @param separator - string used to join prerelease and branch strings defaults to Dot(.) + * @param branchTransform - potential transformations to branch name, defaults to identity + * @return + */ + static PartialSemVerStrategy withBranchName(String separator = ".", + Closure branchTransform = {it -> it}) { + closure { SemVerStrategyState state -> + String branchName = branchFromEnvIfDetached(state) + branchName = branchTransform(~state.mainBranchPattern, branchName) + + def inferred = state.inferredPreRelease ? + "${state.inferredPreRelease}${separator}${branchName}" : + "${branchName}" + state.copyWith(inferredPreRelease: inferred) + } + } + + static final PartialSemVerStrategy WITH_BRANCH_NAME = withBranchName(".") { + Pattern mainBranchPattern, String branchName -> + branchName = branchName.matches(mainBranchPattern)? branchName : "branch.${branchName.toLowerCase()}" + //Split at branch delimiter /-_+ and replace with . + branchName = branchName.replaceAll(/((\/|-|_|\.)+)([\w])/) { _, __, ___, firstAfter -> ".${firstAfter}" } + //Remove all hanging /-_+ + branchName = branchName.replaceAll(/[-\/_\+]+$/, "") + //parse all digits and replace with unpadded value e.g. 001 -> 1 + branchName = branchName.replaceAll(/([\w\.])([0-9]+)/) { all, s, delimiter -> + "${s == "."? "" : s}.${Integer.parseInt(delimiter).toString()}" + } + return branchName + } + + /** + * Appends the pre-release component to a string compatible with semver v1 + * pre-release processing on all of Paket, Nuget and JFrog/Artifactory. + * + * Numbers are only allowed at the end of the string, so numbers not belonging to the count element are expressed in verbose textual form. + * Also, only [A-Za-z0-9] is permitted in the the pre-release component as well, so any '.' is replaced by the 'Dot' string. + */ + static final PartialSemVerStrategy PAKET_BRANCH_NAME = withBranchName("") { + Pattern mainBranchPattern, String branchName -> + branchName = branchName == "master"? branchName : "branch${branchName.capitalize()}" + + branchName = branchName.replaceAll(/(\/|-|_)([\w])/) { _, __, firstAfter -> "${firstAfter.capitalize()}" } + .replaceAll(/\./, "Dot") + return numbersAsLiteralNumbers(branchName) + } + /** * If the nearest any's pre-release component starts with the so far inferred pre-release component, * increment the count of the nearest any and append it to the so far inferred pre-release * component. Otherwise append 1 to the so far inferred pre-release component. */ - static final PartialSemVerStrategy COUNT_INCREMENTED = closure { state -> - def nearest = state.nearestVersion - def currentPreIdents = state.inferredPreRelease ? state.inferredPreRelease.split('\\.') as List : [] - if (nearest.any == nearest.normal || nearest.any.normalVersion != state.inferredNormal) { - currentPreIdents << '1' - } else { - def nearestPreIdents = nearest.any.preReleaseVersion.split('\\.') - if (nearestPreIdents.size() <= currentPreIdents.size()) { - currentPreIdents << '1' - } else if (currentPreIdents == nearestPreIdents[0..(currentPreIdents.size() - 1)]) { - def count = parseIntOrZero(nearestPreIdents[currentPreIdents.size()]) - currentPreIdents << Integer.toString(count + 1) - } else { - currentPreIdents << '1' - } + static final PartialSemVerStrategy COUNT_INCREMENTED = countIncremented() + + /** + * If the nearest any's pre-release component starts with the so far inferred pre-release component, + * increment the count of the nearest any and append it to the so far inferred pre-release + * component. Otherwise append 1 to the so far inferred pre-release component. + * @param separator - string to join the string and count pre-release together. Defaults to dot (.) + * @param countPadding - padding for the count pre-release. For instance ['rc', '1'] with a padding of 3 turns into ['rc', '001']. Defaults to zero. + * @return PartialSemVerStrategy that executes the described operation. + */ + static final PartialSemVerStrategy countIncremented(String separator = ".", int countPadding = 0) { + closure { SemVerStrategyState state -> + state.copyWith(inferredPreRelease: incrementsPreRelease(state, separator, countPadding)) } - return state.copyWith(inferredPreRelease: currentPreIdents.join('.')) } /** @@ -288,6 +397,9 @@ final class Strategies { } } + /** + * Appends the commit count since the nearest CI marker to the pre-release compnent using a dot(.). + */ static final PartialSemVerStrategy COUNT_COMMITS_SINCE_MARKER = closure { SemVerStrategyState state -> def markerVersion = state.nearestCiMarker if (state.currentBranch.name.matches(state.releaseBranchPattern)) { @@ -298,6 +410,20 @@ final class Strategies { def inferred = state.inferredPreRelease ? "${state.inferredPreRelease}.${count}" : "${count}" return state.copyWith(inferredPreRelease: inferred) } + + /** + * Appends the commit count since the nearest normal version + * @param separator - join component between the pre-release component and the count. + * @param paddingCount - with hom many zeros to pad the generate count. + * @return PartialSemverStrategy executing the operation described above. + */ + static final PartialSemVerStrategy countCommitsSinceNormal(String separator = ".", int paddingCount = 0) { + return closure { SemVerStrategyState state -> + def buildSinceAny = state.nearestVersion.distanceFromNormal + def padded = "$buildSinceAny".padLeft(paddingCount, '0') + return state.copyWith(inferredPreRelease: [state.inferredPreRelease, padded].join(separator)) + } + } } /** @@ -331,7 +457,7 @@ final class Strategies { name: '', stages: [] as SortedSet, allowDirtyRepo: false, - normalStrategy: one(Normal.USE_SCOPE_PROP, Normal.USE_NEAREST_ANY, Normal.useScope(ChangeScope.PATCH)), + normalStrategy: one(Normal.USE_SCOPE_STATE, Normal.USE_NEAREST_ANY, Normal.useScope(ChangeScope.PATCH)), preReleaseStrategy: PreRelease.NONE, buildMetadataStrategy: BuildMetadata.NONE, createTag: true, diff --git a/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategyState.groovy b/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategyState.groovy index dd61585..c1cad4d 100644 --- a/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategyState.groovy +++ b/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategyState.groovy @@ -30,6 +30,7 @@ import org.gradle.api.GradleException @Immutable(copyWith=true, knownImmutableClasses=[Commit, Branch]) @ToString(includeNames=true) final class SemVerStrategyState { + ChangeScope scopeFromProp String stageFromProp Commit currentHead diff --git a/src/main/groovy/wooga/gradle/version/internal/release/semver/StrategyUtil.groovy b/src/main/groovy/wooga/gradle/version/internal/release/semver/StrategyUtil.groovy index a81cf99..d5d6cfe 100644 --- a/src/main/groovy/wooga/gradle/version/internal/release/semver/StrategyUtil.groovy +++ b/src/main/groovy/wooga/gradle/version/internal/release/semver/StrategyUtil.groovy @@ -15,6 +15,8 @@ */ package wooga.gradle.version.internal.release.semver +import java.util.regex.Pattern + /** * Utility class to more easily create {@link PartialSemVerStrategy} instances. */ @@ -74,6 +76,65 @@ final class StrategyUtil { } } + static String incrementsPreRelease(final SemVerStrategyState state, String separator = ".", int countPadding = 0) { + def nearest = state.nearestVersion + def currentPreIdents = state.inferredPreRelease ? splitsPrerelease(state.inferredPreRelease) as List : [] + if (nearest.any == nearest.normal || nearest.any.normalVersion != state.inferredNormal) { + currentPreIdents << '1'.padLeft(countPadding, "0") + } else { + def nearestPreIdents = splitsPrerelease(nearest.any.preReleaseVersion) + if (nearestPreIdents.size() <= currentPreIdents.size()) { + currentPreIdents << '1'.padLeft(countPadding, "0") + } else if (currentPreIdents == nearestPreIdents[0..(currentPreIdents.size() - 1)]) { + def count = parseIntOrZero(nearestPreIdents[currentPreIdents.size()]) + currentPreIdents << Integer.toString(count + 1).padLeft(countPadding, "0") + } else { + currentPreIdents << '1'.padLeft(countPadding, "0") + } + } + return currentPreIdents.join(separator) + } + + static def splitsPrerelease(String preReleaseVersion) { + def withDot = preReleaseVersion =~ (~$/^(\w+)\.(\d+)$$/$) + if(withDot.matches()) { + return [withDot.group(1), withDot.group(2)] + } + + def withoutDot = preReleaseVersion =~ (~$/^(\D+)(\d+)$$/$) + if(withoutDot.matches()) { + return [withoutDot.group(1), withoutDot.group(2)] + } + return [preReleaseVersion] + } + + /** + * Gets the branch name from the BRANCH_NAME environment if the state's current branch is HEAD + */ + static String branchFromEnvIfDetached(final SemVerStrategyState state) { + String branchName = state.currentBranch.name + if (branchName == "HEAD" && System.getenv("BRANCH_NAME")) { + return System.getenv("BRANCH_NAME") + } + return branchName + } + + + static String numbersAsLiteralNumbers(String str) { + return str.replaceAll(/0/, "Zero") + .replaceAll(/1/, "One") + .replaceAll(/2/, "Two") + .replaceAll(/3/, "Three") + .replaceAll(/4/, "Four") + .replaceAll(/5/, "Five") + .replaceAll(/6/, "Six") + .replaceAll(/7/, "Seven") + .replaceAll(/8/, "Eight") + .replaceAll(/9/, "Nine") + } + + + private static class ClosureBackedPartialSemVerStrategy implements PartialSemVerStrategy { private final Closure behavior diff --git a/src/main/groovy/wooga/gradle/version/strategies/NetflixOssStrategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/NetflixOssStrategies.groovy index c815839..4879e2d 100644 --- a/src/main/groovy/wooga/gradle/version/strategies/NetflixOssStrategies.groovy +++ b/src/main/groovy/wooga/gradle/version/strategies/NetflixOssStrategies.groovy @@ -19,85 +19,71 @@ import wooga.gradle.version.internal.release.opinion.Strategies import wooga.gradle.version.internal.release.semver.ChangeScope import wooga.gradle.version.internal.release.semver.PartialSemVerStrategy import wooga.gradle.version.internal.release.semver.SemVerStrategy +import wooga.gradle.version.internal.release.semver.SemVerStrategyState import wooga.gradle.version.internal.release.semver.StrategyUtil -import org.gradle.api.GradleException import org.gradle.api.Project +import wooga.gradle.version.strategies.operations.NormalPartials import java.util.regex.Pattern import static wooga.gradle.version.internal.release.semver.StrategyUtil.closure -import static wooga.gradle.version.internal.release.semver.StrategyUtil.incrementNormalFromScope -import static wooga.gradle.version.internal.release.semver.StrategyUtil.parseIntOrZero +/** + * + * WARNING: This class have known issues. If you want this to work, please set NetflixOssStrategies.project with your current project. + * This class is deprecated and will be removed in the next major release. + */ +@Deprecated class NetflixOssStrategies { + + @Deprecated static final PartialSemVerStrategy TRAVIS_BRANCH_MAJOR_X = fromTravisPropertyPattern(~/^(\d+)\.x$/) + @Deprecated static final PartialSemVerStrategy TRAVIS_BRANCH_MAJOR_MINOR_X = fromTravisPropertyPattern(~/^(\d+)\.(\d+)\.x$/) - static final PartialSemVerStrategy NEAREST_HIGHER_ANY = nearestHigherAny() - private static final scopes = StrategyUtil.one(Strategies.Normal.USE_SCOPE_PROP, - TRAVIS_BRANCH_MAJOR_X, TRAVIS_BRANCH_MAJOR_MINOR_X, - Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_X, Strategies.Normal.ENFORCE_BRANCH_MAJOR_X, - Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_MINOR_X, Strategies.Normal.ENFORCE_BRANCH_MAJOR_MINOR_X, - NEAREST_HIGHER_ANY, Strategies.Normal.useScope(ChangeScope.MINOR)) + static final PartialSemVerStrategy NEAREST_HIGHER_ANY = Strategies.Normal.NEAREST_HIGHER_ANY + private static final scopes = StrategyUtil.one( + Strategies.Normal.USE_SCOPE_STATE, + TRAVIS_BRANCH_MAJOR_X, + TRAVIS_BRANCH_MAJOR_MINOR_X, + NormalPartials.SCOPE_EMBED_IN_BRANCH, + NEAREST_HIGHER_ANY, + Strategies.Normal.useScope(ChangeScope.MINOR) + ) static final SemVerStrategy SNAPSHOT = Strategies.SNAPSHOT.copyWith(normalStrategy: scopes) static final SemVerStrategy DEVELOPMENT = Strategies.DEVELOPMENT.copyWith( normalStrategy: scopes, - buildMetadataStrategy: NetflixOssStrategies.BuildMetadata.DEVELOPMENT_METADATA_STRATEGY) + buildMetadataStrategy: Strategies.BuildMetadata.COMMIT_ABBREVIATED_ID) static final SemVerStrategy PRE_RELEASE = Strategies.PRE_RELEASE.copyWith(normalStrategy: scopes) static final SemVerStrategy FINAL = Strategies.FINAL.copyWith(normalStrategy: scopes) + @Deprecated static final class BuildMetadata { - //static ReleaseExtension nebulaReleaseExtension - - static final PartialSemVerStrategy DEVELOPMENT_METADATA_STRATEGY = { state -> - boolean needsBranchMetadata = false -// nebulaReleaseExtension.releaseBranchPatterns.each { -// if (state.currentBranch.name =~ it) { -// needsBranchMetadata = false -// } -// } - String shortenedBranch = (state.currentBranch.name =~ /(?:(?:bugfix|feature|hotfix|release)(?:-|\/))?(.+)/)[0][1] - shortenedBranch = shortenedBranch.replaceAll(/[_\/-]/, '.') - def metadata = needsBranchMetadata ? "${shortenedBranch}.${state.currentHead.abbreviatedId}" : state.currentHead.abbreviatedId - state.copyWith(inferredBuildMetadata: metadata) - } + static final PartialSemVerStrategy DEVELOPMENT_METADATA_STRATEGY = Strategies.BuildMetadata.COMMIT_ABBREVIATED_ID } static Project project + @Deprecated static final String TRAVIS_BRANCH_PROP = 'release.travisBranch' - static PartialSemVerStrategy fromTravisPropertyPattern(Pattern pattern) { - return StrategyUtil.closure { state -> - if (project.hasProperty(TRAVIS_BRANCH_PROP)) { - def branch = project.property(TRAVIS_BRANCH_PROP) - def m = branch =~ pattern - if (m) { - def major = m.groupCount() >= 1 ? parseIntOrZero(m[0][1]) : -1 - def minor = m.groupCount() >= 2 ? parseIntOrZero(m[0][2]) : -1 - - def normal = state.nearestVersion.normal - def majorDiff = major - normal.majorVersion - def minorDiff = minor - normal.minorVersion - if (majorDiff == 1 && minor <= 0) { - // major is off by one and minor is either 0 or not in the branch name - return incrementNormalFromScope(state, ChangeScope.MAJOR) - } else if (minorDiff == 1 && minor > 0) { - // minor is off by one and specified in the branch name - return incrementNormalFromScope(state, ChangeScope.MINOR) - } else if (majorDiff == 0 && minorDiff == 0 && minor >= 0) { - // major and minor match, both are specified in branch name - return incrementNormalFromScope(state, ChangeScope.PATCH) - } else if (majorDiff == 0 && minor < 0) { - // only major specified in branch name and already matches - return state - } else { - throw new GradleException("Invalid branch (${state.currentBranch.name}) for nearest normal (${normal}).") - } - } + /** + * Infers a normal increment based on the branch. Only here to give support for NetflixOssStrategies, will be removed on next major. + * This is deprecated, please use Strategies.Normal.fromMatchingBranchName or Strategies.Normal.fromBranchPattern + * @param project + * @param branchProperty + * @param pattern + * @return + */ + @Deprecated + static PartialSemVerStrategy fromTravisPropertyPattern(Pattern pattern) { + def branchProperty = TRAVIS_BRANCH_PROP + return closure { SemVerStrategyState state -> + if (project.hasProperty(branchProperty)) { + def branch = project.property(branchProperty).toString() + return Strategies.Normal.fromMatchingBranchName(branch, pattern).infer(state) } - return state } } @@ -113,14 +99,8 @@ class NetflixOssStrategies { * component as {@code 1.2.3}. *

*/ + @Deprecated static PartialSemVerStrategy nearestHigherAny() { - return closure { state -> - def nearest = state.nearestVersion - if (nearest.any.lessThanOrEqualTo(nearest.normal)) { - return state - } else { - return state.copyWith(inferredNormal: nearest.any.normalVersion) - } - } + return closure { state -> Strategies.Normal.NEAREST_HIGHER_ANY.infer(state) } } } diff --git a/src/main/groovy/wooga/gradle/version/strategies/NewSemverV1Strategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/NewSemverV1Strategies.groovy new file mode 100644 index 0000000..21116aa --- /dev/null +++ b/src/main/groovy/wooga/gradle/version/strategies/NewSemverV1Strategies.groovy @@ -0,0 +1,163 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package wooga.gradle.version.strategies + +import wooga.gradle.version.ReleaseStage +import wooga.gradle.version.internal.release.opinion.Strategies +import wooga.gradle.version.internal.release.semver.* +import wooga.gradle.version.strategies.partials.NormalPartials + +import static wooga.gradle.version.internal.release.semver.StrategyUtil.all +import static wooga.gradle.version.internal.release.semver.StrategyUtil.one + +class NewSemverV1Strategies { + + static final NORMAL_STRATEGY = one( + Strategies.Normal.USE_SCOPE_STATE, + NormalPartials.SCOPE_EMBED_IN_BRANCH, + Strategies.Normal.USE_NEAREST_ANY + ) + + /** + * Returns a version strategy to be used for {@code pre-release}/{@code candidate} builds. + *

+ * This strategy infers the release version by checking the nearest any release. + * If a {@code pre-release} with the same {@code major}.{@code minor}.{@code patch} version exists, bumps the count part. + *

+ * Example new pre release: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.2.0"
+     * nearestAnyVersion = ""
+     * inferred = "1.3.0-rc00001"
+     * }
+     * 
+ *

+ * Example pre release version exists: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.2.0"
+     * nearestAnyVersion = "1.3.0-rc00001"
+     * inferred = "1.3.0-rc00002"
+     * }
+     * 
+ *

+ * Example last final release higher than pre-release: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * nearestAnyVersion = "1.3.0-rc00001"
+     * inferred = "1.4.0-rc00001"
+     * }
+     * 
+ */ + static final SemVerStrategy PRE_RELEASE = Strategies.PRE_RELEASE.copyWith( + normalStrategy: NORMAL_STRATEGY, + preReleaseStrategy: all ( + Strategies.PreRelease.STAGE_FIXED, + Strategies.PreRelease.countIncremented("", 5), + ) + ) + + /** + * Returns a version strategy to be used for {@code final} builds. + *

+ * This strategy infers the release version by checking the nearest release. + *

+ * Example: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * inferred = "1.4.0"
+     * }
+     * 
+ */ + static final SemVerStrategy FINAL = Strategies.FINAL.copyWith(normalStrategy: NORMAL_STRATEGY) + + /** + * Returns a version strategy to be used for {@code development} builds. + *

+ * This strategy creates a unique version string based on last commit hash and the + * distance of commits to nearest normal version. + * This version string is not compatible with Paket or + * Nuget + *

+ * Example: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * commitHash = "90c90b9"
+     * distance = 22
+     * inferred = "1.3.0-dev.22+90c90b9"
+     * }
+     * 
+ */ + static final SemVerStrategy DEVELOPMENT = Strategies.DEVELOPMENT.copyWith( + normalStrategy: NORMAL_STRATEGY, + buildMetadataStrategy: Strategies.BuildMetadata.COMMIT_ABBREVIATED_ID + ) + + /** + * Returns a version strategy to be used for {@code snapshot} builds. + *

+ * This strategy creates a snapshot version suitable for Paket and + * Nuget. {@code Nuget} and {@code Paket} don't support + * {@code SNAPSHOT} versions or any numerical values after the {@code patch} component. We trick these package managers + * by creating a unique version based on {@code branch} and the distance of commits to nearest normal version. + * If the branch name contains numerical values, they will be converted into alphanumerical counterparts. + * The {@code master} branch is a special case so it will end up being higher than any other branch build (m>b) + *

+ * Example from master branch: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * branch = "master"
+     * distance = 22
+     * inferred = "1.4.0-master00022"
+     * }
+     * 
+ *

+ * Example from topic branch: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * branch = "feature/fix_22"
+     * distance = 34
+     * inferred = "1.4.0-branchFeatureFixTwoTwo00034"
+     * }
+     * 
+ */ + static final SemVerStrategy SNAPSHOT = Strategies.PRE_RELEASE.copyWith( + name: 'snapshot', + stages: ['snapshot','SNAPSHOT'] as SortedSet, + normalStrategy: NORMAL_STRATEGY, + preReleaseStrategy: all(Strategies.PreRelease.PAKET_BRANCH_NAME, + Strategies.PreRelease.countCommitsSinceNormal("", 5)), + createTag: false, + allowDirtyRepo: true, + enforcePrecedence: false, + releaseStage: ReleaseStage.Snapshot + ) +} diff --git a/src/main/groovy/wooga/gradle/version/strategies/NewSemverV2Strategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/NewSemverV2Strategies.groovy new file mode 100644 index 0000000..a88d53f --- /dev/null +++ b/src/main/groovy/wooga/gradle/version/strategies/NewSemverV2Strategies.groovy @@ -0,0 +1,161 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package wooga.gradle.version.strategies + +import wooga.gradle.version.ReleaseStage +import wooga.gradle.version.internal.release.opinion.Strategies +import wooga.gradle.version.internal.release.semver.SemVerStrategy +import wooga.gradle.version.strategies.partials.NormalPartials + +import static wooga.gradle.version.internal.release.semver.StrategyUtil.* + + +final class NewSemverV2Strategies { + + static final NORMAL_STRATEGY = one( + Strategies.Normal.USE_SCOPE_STATE, + NormalPartials.SCOPE_FROM_BRANCH, + Strategies.Normal.USE_NEAREST_ANY + ) + + /** + * Returns a version strategy to be used for {@code final} builds. + *

+ * This strategy infers the release version by checking the nearest release. + *

+ * Example: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * inferred = "1.4.0"
+     * }
+     * 
+ */ + static final SemVerStrategy FINAL = Strategies.FINAL.copyWith( + name: 'production', + allowDirtyRepo: true, + normalStrategy: NORMAL_STRATEGY, + stages: ['final','production'] as SortedSet, + releaseStage: ReleaseStage.Final + ) + /** + * Returns a version strategy to be used for {@code pre-release}/{@code candidate} builds. + *

+ * This strategy infers the release version by checking the nearest any release. + * If a {@code pre-release} with the same {@code major}.{@code minor}.{@code patch} version exists, bumps the count part. + *

+ * Example new pre release: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.2.0"
+     * nearestAnyVersion = ""
+     * inferred = "1.3.0-rc.1"
+     * }
+     * 
+ *

+ * Example pre release version exists: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.2.0"
+     * nearestAnyVersion = "1.3.0-rc.1"
+     * inferred = "1.3.0-rc.2"
+     * }
+     * 
+ *

+ * Example last final release higher than pre-release: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * nearestAnyVersion = "1.3.0-rc.1"
+     * inferred = "1.4.0-rc.1"
+     * }
+     * 
+ */ + static final SemVerStrategy PRE_RELEASE = Strategies.PRE_RELEASE.copyWith( + name: 'pre-release', + normalStrategy: NORMAL_STRATEGY, + stages: ['rc', 'staging'] as SortedSet, + preReleaseStrategy: all(Strategies.PreRelease.STAGE_FIXED, Strategies.PreRelease.COUNT_INCREMENTED), + releaseStage: ReleaseStage.Prerelease, + allowDirtyRepo: true, + ) + + /** + * Returns a version strategy to be used for {@code development} builds. + *

+ * This strategy creates a unique version string based on last commit hash and the + * distance of commits to nearest normal version. + * This version string is not compatible with Paket or + * Nuget + *

+ * Example: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * commitHash = "90c90b9"
+     * distance = 22
+     * inferred = "1.3.0-dev.22+90c90b9"
+     * }
+     * 
+ */ + static final SemVerStrategy DEVELOPMENT = Strategies.DEVELOPMENT.copyWith( + normalStrategy: NORMAL_STRATEGY, + buildMetadataStrategy: Strategies.BuildMetadata.COMMIT_ABBREVIATED_ID, + ) + + /** + * Returns a version strategy to be used for {@code snapshot} builds. + *

+ * This strategy creates a snapshot version based on Semver 2.0.0. + * Branch names will be encoded in the pre-release part of the version. + *

+ * Example from master branch: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * branch = "master"
+     * distance = 22
+     * inferred = "1.4.0-master.22"
+     * }
+     * 
+ *

+ * Example from topic branch: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * branch = "feature/fix_22"
+     * distance = 34
+     * inferred = "1.4.0-branch.feature.fix.22.34"
+     * }
+     * 
+ */ + static final SemVerStrategy SNAPSHOT = Strategies.SNAPSHOT.copyWith( + stages: ['ci', 'snapshot', 'SNAPSHOT'] as SortedSet, + normalStrategy: NORMAL_STRATEGY, + allowDirtyRepo: true, + preReleaseStrategy: all(Strategies.PreRelease.WITH_BRANCH_NAME, + Strategies.PreRelease.COUNT_COMMITS_SINCE_ANY), + ) +} diff --git a/src/main/groovy/wooga/gradle/version/strategies/SemverV1Strategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/SemverV1Strategies.groovy index 89016c6..05d5651 100644 --- a/src/main/groovy/wooga/gradle/version/strategies/SemverV1Strategies.groovy +++ b/src/main/groovy/wooga/gradle/version/strategies/SemverV1Strategies.groovy @@ -28,15 +28,20 @@ import wooga.gradle.version.internal.release.semver.StrategyUtil import static wooga.gradle.version.internal.release.semver.StrategyUtil.closure import static wooga.gradle.version.internal.release.semver.StrategyUtil.parseIntOrZero +/** + * Please use LegacyNuGetStrategies instead + */ +@Deprecated class SemverV1Strategies { static final scopes = StrategyUtil.one( - Strategies.Normal.USE_SCOPE_PROP, + Strategies.Normal.USE_SCOPE_STATE, Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_X, Strategies.Normal.ENFORCE_BRANCH_MAJOR_X, Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_MINOR_X, Strategies.Normal.ENFORCE_BRANCH_MAJOR_MINOR_X, Strategies.Normal.USE_NEAREST_ANY, - Strategies.Normal.useScope(ChangeScope.PATCH)) + Strategies.Normal.useScope(ChangeScope.PATCH) + ) static final PartialSemVerStrategy COUNT_INCREMENTED = closure { SemVerStrategyState state -> def nearest = state.nearestVersion diff --git a/src/main/groovy/wooga/gradle/version/strategies/SemverV2Strategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/SemverV2Strategies.groovy index b3580b0..de52119 100644 --- a/src/main/groovy/wooga/gradle/version/strategies/SemverV2Strategies.groovy +++ b/src/main/groovy/wooga/gradle/version/strategies/SemverV2Strategies.groovy @@ -29,10 +29,14 @@ import java.util.regex.Pattern import static wooga.gradle.version.internal.release.semver.StrategyUtil.* +/** + * Deprecated, please use SemverV2WithDefaultStrategies instead. + */ +@Deprecated final class SemverV2Strategies { private static final scopes = one( - Strategies.Normal.USE_SCOPE_PROP, + Strategies.Normal.USE_SCOPE_STATE, Normal.matchBranchPatternAndUseScope(~/feature(?:\/|-).+$/, ChangeScope.MINOR), Normal.matchBranchPatternAndUseScope(~/hotfix(?:\/|-).+$/, ChangeScope.PATCH), Normal.matchBranchPatternAndUseScope(~/fix(?:\/|-).+$/, ChangeScope.MINOR), diff --git a/src/main/groovy/wooga/gradle/version/strategies/StaticMarkerStrategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/StaticMarkerStrategies.groovy index 913b8f7..87df13e 100644 --- a/src/main/groovy/wooga/gradle/version/strategies/StaticMarkerStrategies.groovy +++ b/src/main/groovy/wooga/gradle/version/strategies/StaticMarkerStrategies.groovy @@ -122,7 +122,7 @@ class StaticMarkerStrategies { */ static final SemVerStrategy DEVELOPMENT = Strategies.DEVELOPMENT.copyWith( normalStrategy: scopes, - buildMetadataStrategy: NetflixOssStrategies.BuildMetadata.DEVELOPMENT_METADATA_STRATEGY) + buildMetadataStrategy: Strategies.BuildMetadata.COMMIT_ABBREVIATED_ID) /** * Returns a version strategy to be used for {@code snapshot} builds. @@ -156,7 +156,8 @@ class StaticMarkerStrategies { name: 'snapshot', stages: ['ci'] as SortedSet, createTag: false, - preReleaseStrategy: all(SemverV2Strategies.PreRelease.STAGE_BRANCH_NAME, Strategies.PreRelease.COUNT_COMMITS_SINCE_MARKER), + preReleaseStrategy: all(Strategies.PreRelease.WITH_BRANCH_NAME, + Strategies.PreRelease.COUNT_COMMITS_SINCE_MARKER), enforcePrecedence: false ) diff --git a/src/main/groovy/wooga/gradle/version/strategies/WdkStrategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/WdkStrategies.groovy index eb002b9..5ae98af 100644 --- a/src/main/groovy/wooga/gradle/version/strategies/WdkStrategies.groovy +++ b/src/main/groovy/wooga/gradle/version/strategies/WdkStrategies.groovy @@ -1,23 +1,10 @@ package wooga.gradle.version.strategies -import wooga.gradle.version.ReleaseStage -import wooga.gradle.version.internal.release.opinion.Strategies -import wooga.gradle.version.internal.release.semver.SemVerStrategy -import wooga.gradle.version.internal.release.semver.StrategyUtil +import wooga.gradle.version.strategies.opinion.WdkNuGetStrategies -class WdkStrategies { - static final SemVerStrategy DEVELOPMENT = SemverV1Strategies.DEVELOPMENT - static final SemVerStrategy SNAPSHOT = SemverV1Strategies.SNAPSHOT - static final SemVerStrategy PREFLIGHT = SemverV1Strategies.SNAPSHOT.copyWith( - releaseStage: ReleaseStage.Preflight, - // TODO: Must start Between m-r - stages: ['pre', 'preflight'] as SortedSet, - preReleaseStrategy: StrategyUtil.all(StrategyUtil.closure({ state -> - def stage = Strategies.PreRelease.STAGE_FIXED.infer(state).inferredPreRelease - def integration = Strategies.PreRelease.STAGE_TIMESTAMP.infer(state).inferredPreRelease - state.copyWith(inferredPreRelease: "$stage$integration") - })), - ) - static final SemVerStrategy PRE_RELEASE = SemverV1Strategies.PRE_RELEASE - static final SemVerStrategy FINAL = SemverV1Strategies.FINAL +/** + * Deprecated: please use WdkNuGetStrategies instead + */ +@Deprecated +class WdkStrategies extends WdkNuGetStrategies { } diff --git a/src/main/groovy/wooga/gradle/version/strategies/operations/NormalPartials.groovy b/src/main/groovy/wooga/gradle/version/strategies/operations/NormalPartials.groovy new file mode 100644 index 0000000..9411513 --- /dev/null +++ b/src/main/groovy/wooga/gradle/version/strategies/operations/NormalPartials.groovy @@ -0,0 +1,81 @@ +package wooga.gradle.version.strategies.operations + +import org.gradle.api.Project +import wooga.gradle.version.internal.release.opinion.Strategies +import wooga.gradle.version.internal.release.semver.ChangeScope +import wooga.gradle.version.internal.release.semver.PartialSemVerStrategy +import wooga.gradle.version.internal.release.semver.SemVerStrategyState +import wooga.gradle.version.strategies.NetflixOssStrategies + +import java.util.regex.Pattern + +import static wooga.gradle.version.internal.release.semver.StrategyUtil.closure +import static wooga.gradle.version.internal.release.semver.StrategyUtil.one + +class NormalPartials { + + static final SCOPE_GITFLOW_BRANCH_TYPE = one( + scopeForBranchPattern(~/hotfix(?:\/|-).+$/, ChangeScope.PATCH), + scopeForBranchPattern(~/feature(?:\/|-).+$/, ChangeScope.MINOR), + scopeForBranchPattern(~/fix(?:\/|-).+$/, ChangeScope.MINOR), + ) + + static final SCOPE_EMBED_IN_BRANCH = one( + Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_X, + Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_MINOR_X, + Strategies.Normal.ENFORCE_BRANCH_MAJOR_X, + Strategies.Normal.ENFORCE_BRANCH_MAJOR_MINOR_X, + ) + + static final SCOPE_FROM_BRANCH = one( + SCOPE_GITFLOW_BRANCH_TYPE, + SCOPE_EMBED_IN_BRANCH + ) + + @Deprecated + static final PartialSemVerStrategy TRAVIS_BRANCH_MAJOR_X = fromBranchProperty(~/^(\d+)\.x$/) + @Deprecated + static final PartialSemVerStrategy TRAVIS_BRANCH_MAJOR_MINOR_X = fromBranchProperty(~/^(\d+)\.(\d+)\.x$/) + + /** + * If the nearest any is higher from the nearest normal, sets the + * normal component to the nearest any's normal component. Otherwise + * do nothing. + * + *

+ * For example, if the nearest any is {@code 1.2.3-alpha.1} and the + * nearest normal is {@code 1.2.2}, this will infer the normal + * component as {@code 1.2.3}. + *

+ */ + static final PartialSemVerStrategy NEAREST_HIGHER_ANY = closure { state -> + def nearest = state.nearestVersion + if (nearest.any.lessThanOrEqualTo(nearest.normal)) { + return state + } else { + return state.copyWith(inferredNormal: nearest.any.normalVersion) + } + } + + static PartialSemVerStrategy scopeForBranchPattern(Pattern pattern, ChangeScope scope) { + return closure { SemVerStrategyState state -> + def m = state.currentBranch.name =~ pattern + if (m.matches()) { + return Strategies.Normal.useScope(scope).infer(state) + } + return state + } + } + + static PartialSemVerStrategy fromBranchProperty(Project project = NetflixOssStrategies.project, + String branchProperty = NetflixOssStrategies.TRAVIS_BRANCH_PROP, + Pattern pattern) { + return closure { SemVerStrategyState state -> + if (project.hasProperty(branchProperty)) { + def branch = project.property(branchProperty).toString() + return Strategies.Normal.fromMatchingBranchName(branch, pattern).infer(state) + } + return state + } + } +} diff --git a/src/main/groovy/wooga/gradle/version/strategies/opinion/LegacyNuGetStrategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/opinion/LegacyNuGetStrategies.groovy new file mode 100644 index 0000000..f03c44e --- /dev/null +++ b/src/main/groovy/wooga/gradle/version/strategies/opinion/LegacyNuGetStrategies.groovy @@ -0,0 +1,50 @@ +package wooga.gradle.version.strategies.opinion + + +import wooga.gradle.version.internal.release.opinion.Strategies +import wooga.gradle.version.internal.release.semver.ChangeScope +import wooga.gradle.version.internal.release.semver.PartialSemVerStrategy +import wooga.gradle.version.internal.release.semver.SemVerStrategy +import wooga.gradle.version.internal.release.semver.StrategyUtil +import wooga.gradle.version.strategies.NewSemverV1Strategies + +/** + * NuGet/Paket compatible strategies. Uses NewSemverV1Strategies as basis, + * plus using PATCH as a fallback if no change scope is provided. + */ +class LegacyNuGetStrategies { + + /** + * Scope that will be set when no scope is provided to the strategies under this class + */ + static final ChangeScope DEFAULT_SCOPE = ChangeScope.PATCH + + /** + * Similar to NewSemverV1Strategies.DEVELOPMENT, but its scope defaults to DEFAULT_SCOPE when no scope is provide + */ + static final SemVerStrategy DEVELOPMENT = NewSemverV1Strategies.DEVELOPMENT.copyWith( + normalStrategy: chainDefaultScope(NewSemverV1Strategies.DEVELOPMENT) + ) + /** + * Similar to NewSemverV1Strategies.SNAPSHOT, but its scope defaults to DEFAULT_SCOPE when no scope is provide + */ + static final SemVerStrategy SNAPSHOT = NewSemverV1Strategies.SNAPSHOT.copyWith( + normalStrategy: chainDefaultScope(NewSemverV1Strategies.SNAPSHOT) + ) + /** + * Similar to NewSemverV1Strategies.PRE_RELEASE, but its scope defaults to DEFAULT_SCOPE when no scope is provide + */ + static final SemVerStrategy PRE_RELEASE = NewSemverV1Strategies.PRE_RELEASE.copyWith( + normalStrategy: chainDefaultScope(NewSemverV1Strategies.PRE_RELEASE) + ) + /** + * Similar to NewSemverV1Strategies.FINAL, but its scope defaults to DEFAULT_SCOPE when no scope is provide + */ + static final SemVerStrategy FINAL = NewSemverV1Strategies.FINAL.copyWith( + normalStrategy: chainDefaultScope(NewSemverV1Strategies.FINAL) + ) + + private static PartialSemVerStrategy chainDefaultScope(SemVerStrategy base, ChangeScope scope = DEFAULT_SCOPE) { + return StrategyUtil.one(base.normalStrategy, Strategies.Normal.useScope(scope)) + } +} diff --git a/src/main/groovy/wooga/gradle/version/strategies/opinion/SemverV2WithDefaultStrategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/opinion/SemverV2WithDefaultStrategies.groovy new file mode 100644 index 0000000..6a4ff72 --- /dev/null +++ b/src/main/groovy/wooga/gradle/version/strategies/opinion/SemverV2WithDefaultStrategies.groovy @@ -0,0 +1,131 @@ +package wooga.gradle.version.strategies.opinion + +import wooga.gradle.version.internal.release.opinion.Strategies +import wooga.gradle.version.internal.release.semver.ChangeScope +import wooga.gradle.version.internal.release.semver.PartialSemVerStrategy +import wooga.gradle.version.internal.release.semver.SemVerStrategy +import wooga.gradle.version.internal.release.semver.StrategyUtil +import wooga.gradle.version.strategies.NewSemverV2Strategies + +/** + * SemverV2 strategies that uses MINOR as a fallback in case a change scope is not provided. + */ +class SemverV2WithDefaultStrategies { + + /** + * Returns a version strategy to be used for {@code final} builds. + *

+ * This strategy infers the release version by checking the nearest release. + *

+ * Example: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * inferred = "1.4.0"
+     * }
+     * 
+ */ + static final SemVerStrategy FINAL = NewSemverV2Strategies.FINAL.copyWith( + normalStrategy: chainDefaultScope(NewSemverV2Strategies.FINAL) + ) + /** + * Returns a version strategy to be used for {@code pre-release}/{@code candidate} builds. + *

+ * This strategy infers the release version by checking the nearest any release. + * If a {@code pre-release} with the same {@code major}.{@code minor}.{@code patch} version exists, bumps the count part. + *

+ * Example new pre release: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.2.0"
+     * nearestAnyVersion = ""
+     * inferred = "1.3.0-rc.1"
+     * }
+     * 
+ *

+ * Example pre release version exists: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.2.0"
+     * nearestAnyVersion = "1.3.0-rc.1"
+     * inferred = "1.3.0-rc.2"
+     * }
+     * 
+ *

+ * Example last final release higher than pre-release: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * nearestAnyVersion = "1.3.0-rc.1"
+     * inferred = "1.4.0-rc.1"
+     * }
+     * 
+ */ + static final SemVerStrategy PRE_RELEASE = NewSemverV2Strategies.PRE_RELEASE.copyWith( + normalStrategy: chainDefaultScope(NewSemverV2Strategies.PRE_RELEASE) + ) + + /** + * Returns a version strategy to be used for {@code development} builds. + *

+ * This strategy creates a unique version string based on last commit hash and the + * distance of commits to nearest normal version. + * This version string is not compatible with Paket or + * Nuget + *

+ * Example: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * commitHash = "90c90b9"
+     * distance = 22
+     * inferred = "1.3.0-dev.22+90c90b9"
+     * }
+     * 
+ */ + static final SemVerStrategy DEVELOPMENT = NewSemverV2Strategies.DEVELOPMENT.copyWith( + normalStrategy: chainDefaultScope(NewSemverV2Strategies.DEVELOPMENT), + ) + + /** + * Returns a version strategy to be used for {@code snapshot} builds. + *

+ * This strategy creates a snapshot version based on Semver 2.0.0. + * Branch names will be encoded in the pre-release part of the version. + *

+ * Example from master branch: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * branch = "master"
+     * distance = 22
+     * inferred = "1.4.0-master.22"
+     * }
+     * 
+ *

+ * Example from topic branch: + *

+     * {@code
+     * releaseScope = "minor"
+     * nearestVersion = "1.3.0"
+     * branch = "feature/fix_22"
+     * distance = 34
+     * inferred = "1.4.0-branch.feature.fix.22.34"
+     * }
+     * 
+ */ + static final SemVerStrategy SNAPSHOT = NewSemverV2Strategies.SNAPSHOT.copyWith( + normalStrategy: chainDefaultScope(NewSemverV2Strategies.SNAPSHOT), + ) + + private static PartialSemVerStrategy chainDefaultScope(SemVerStrategy base, ChangeScope scope = ChangeScope.MINOR) { + return StrategyUtil.one(base.normalStrategy, Strategies.Normal.useScope(scope)) + } + +} diff --git a/src/main/groovy/wooga/gradle/version/strategies/opinion/WdkNuGetStrategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/opinion/WdkNuGetStrategies.groovy new file mode 100644 index 0000000..dbd2be0 --- /dev/null +++ b/src/main/groovy/wooga/gradle/version/strategies/opinion/WdkNuGetStrategies.groovy @@ -0,0 +1,52 @@ +package wooga.gradle.version.strategies.opinion + +import wooga.gradle.version.ReleaseStage +import wooga.gradle.version.internal.release.opinion.Strategies +import wooga.gradle.version.internal.release.semver.SemVerStrategy + +import static wooga.gradle.version.internal.release.semver.StrategyUtil.all + +/** + * WDK strategies for NuGet/Paket. Based on LegacyNuGetStrategies, plus an extra PREFLIGHT stage. + */ +class WdkNuGetStrategies { + /** + * Same as LegacyNuGetStrategies.DEVELOPMENT + */ + static final SemVerStrategy DEVELOPMENT = LegacyNuGetStrategies.DEVELOPMENT + /** + * Same as LegacyNuGetStrategies.SNAPSHOT + */ + static final SemVerStrategy SNAPSHOT = LegacyNuGetStrategies.SNAPSHOT + /** + * Returns a version strategy to be used for {@code preflight} builds. + *

+ * This strategy creates a wdk-specific preflight version based on Semver 1.0. + * The pre-release part of the version consists of the stage name(pre or preflight) and a timestamp (yyyyMMddHHmm), with no separation. + * There are no metadata part in the generated versions. + *

+ * Example: +

+     {@code
+     stage = "pre"
+     releaseScope = "minor"
+     nearestVersion = "1.3.0"
+     current date = 01/01/2022 10:10:30
+     inferred = "1.4.0-pre202201011010"
+     }
+     */
+    static final SemVerStrategy PREFLIGHT = LegacyNuGetStrategies.SNAPSHOT.copyWith(
+        releaseStage: ReleaseStage.Preflight,
+        // TODO: Must start Between m-r
+        stages: ['pre', 'preflight'] as SortedSet,
+        preReleaseStrategy: all(Strategies.PreRelease.STAGE_FIXED, Strategies.PreRelease.withTimestamp(""))
+    )
+    /**
+     * Same as LegacyNuGetStrategies.PRE_RELEASE
+     */
+    static final SemVerStrategy PRE_RELEASE = LegacyNuGetStrategies.PRE_RELEASE
+    /**
+     * Same as LegacyNuGetStrategies.FINAL
+     */
+    static final SemVerStrategy FINAL = LegacyNuGetStrategies.FINAL
+}
diff --git a/src/main/groovy/wooga/gradle/version/strategies/partials/NormalPartials.groovy b/src/main/groovy/wooga/gradle/version/strategies/partials/NormalPartials.groovy
new file mode 100644
index 0000000..5b61302
--- /dev/null
+++ b/src/main/groovy/wooga/gradle/version/strategies/partials/NormalPartials.groovy
@@ -0,0 +1,27 @@
+package wooga.gradle.version.strategies.partials
+
+
+import wooga.gradle.version.internal.release.opinion.Strategies
+import wooga.gradle.version.internal.release.semver.ChangeScope
+import static wooga.gradle.version.internal.release.semver.StrategyUtil.one
+
+class NormalPartials {
+
+    static final SCOPE_GITFLOW_BRANCH_TYPE = one(
+            Strategies.Normal.scopeIfBranchMatchesPattern(~/hotfix(?:\/|-).+$/, ChangeScope.PATCH),
+            Strategies.Normal.scopeIfBranchMatchesPattern(~/feature(?:\/|-).+$/, ChangeScope.MINOR),
+            Strategies.Normal.scopeIfBranchMatchesPattern(~/fix(?:\/|-).+$/, ChangeScope.MINOR),
+    )
+
+    static final SCOPE_EMBED_IN_BRANCH = one(
+            Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_X,
+            Strategies.Normal.ENFORCE_GITFLOW_BRANCH_MAJOR_MINOR_X,
+            Strategies.Normal.ENFORCE_BRANCH_MAJOR_X,
+            Strategies.Normal.ENFORCE_BRANCH_MAJOR_MINOR_X,
+    )
+
+    static final SCOPE_FROM_BRANCH = one(
+            SCOPE_GITFLOW_BRANCH_TYPE,
+            SCOPE_EMBED_IN_BRANCH
+    )
+}
diff --git a/src/test/groovy/wooga/gradle/version/VersionPluginSpec.groovy b/src/test/groovy/wooga/gradle/version/VersionPluginSpec.groovy
index e1d1034..0ace3f0 100644
--- a/src/test/groovy/wooga/gradle/version/VersionPluginSpec.groovy
+++ b/src/test/groovy/wooga/gradle/version/VersionPluginSpec.groovy
@@ -39,7 +39,6 @@ class VersionPluginSpec extends ProjectSpec {
         git = Grgit.init(dir: projectDir)
         git.add(patterns: ['.gitignore'])
         git.commit(message: 'initial commit')
-//        git.tag.add(name: 'v0.0.1')
     }
 
     @Ignore
@@ -972,7 +971,7 @@ class VersionPluginSpec extends ProjectSpec {
         }
 
 
-        if(nearestNormal != _) {
+        if (nearestNormal != _) {
             5.times {
                 git.commit(message: 'feature commit')
             }