diff --git a/src/integrationTest/groovy/wooga/gradle/version/VersionPluginIntegrationSpec.groovy b/src/integrationTest/groovy/wooga/gradle/version/VersionPluginIntegrationSpec.groovy index 3645001..36d2242 100644 --- a/src/integrationTest/groovy/wooga/gradle/version/VersionPluginIntegrationSpec.groovy +++ b/src/integrationTest/groovy/wooga/gradle/version/VersionPluginIntegrationSpec.groovy @@ -92,43 +92,49 @@ class VersionPluginIntegrationSpec extends IntegrationSpec { result.standardOutput.contains("${extensionName}.${property}: ${testValue}") where: - property | value | expectedValue | location - "scope" | "major" | ChangeScope.MAJOR | PropertyLocation.env - "scope" | "MAJOR" | ChangeScope.MAJOR | PropertyLocation.env - "scope" | "Major" | ChangeScope.MAJOR | PropertyLocation.env - "scope" | "minor" | ChangeScope.MINOR | PropertyLocation.env - "scope" | "MINOR" | ChangeScope.MINOR | PropertyLocation.env - "scope" | "Minor" | ChangeScope.MINOR | PropertyLocation.env - "scope" | "patch" | ChangeScope.PATCH | PropertyLocation.env - "scope" | "PATCH" | ChangeScope.PATCH | PropertyLocation.env - "scope" | "Patch" | ChangeScope.PATCH | PropertyLocation.env - "scope" | "major" | ChangeScope.MAJOR | PropertyLocation.property - "scope" | "MAJOR" | ChangeScope.MAJOR | PropertyLocation.property - "scope" | "Major" | ChangeScope.MAJOR | PropertyLocation.property - "scope" | "minor" | ChangeScope.MINOR | PropertyLocation.property - "scope" | "MINOR" | ChangeScope.MINOR | PropertyLocation.property - "scope" | "Minor" | ChangeScope.MINOR | PropertyLocation.property - "scope" | "patch" | ChangeScope.PATCH | PropertyLocation.property - "scope" | "PATCH" | ChangeScope.PATCH | PropertyLocation.property - "scope" | "Patch" | ChangeScope.PATCH | PropertyLocation.property - "scope" | "null" | _ | PropertyLocation.none - "stage" | "snapshot" | _ | PropertyLocation.env - "stage" | "final" | _ | PropertyLocation.property - "stage" | "null" | _ | PropertyLocation.none - "versionScheme" | "semver2" | VersionScheme.semver2 | PropertyLocation.env - "versionScheme" | "semver" | VersionScheme.semver | PropertyLocation.env - "versionScheme" | "semver2" | VersionScheme.semver2 | PropertyLocation.property - "versionScheme" | "semver" | VersionScheme.semver | PropertyLocation.property - "versionCodeScheme" | "releaseCountBasic" | VersionCodeScheme.releaseCountBasic | PropertyLocation.env - "versionCodeScheme" | "releaseCount" | VersionCodeScheme.releaseCount | PropertyLocation.env - "versionCodeScheme" | "semver" | VersionCodeScheme.semver | PropertyLocation.env - "versionCodeScheme" | "none" | VersionCodeScheme.none | PropertyLocation.env - "versionCodeScheme" | "releaseCountBasic" | VersionCodeScheme.releaseCountBasic | PropertyLocation.property - "versionCodeScheme" | "releaseCount" | VersionCodeScheme.releaseCount | PropertyLocation.property - "versionCodeScheme" | "semver" | VersionCodeScheme.semver | PropertyLocation.property - "versionCodeScheme" | "none" | VersionCodeScheme.none | PropertyLocation.property - "versionCodeOffset" | 100 | _ | PropertyLocation.env - "versionCodeOffset" | 200 | _ | PropertyLocation.property + property | value | expectedValue | location + "scope" | "major" | ChangeScope.MAJOR | PropertyLocation.env + "scope" | "MAJOR" | ChangeScope.MAJOR | PropertyLocation.env + "scope" | "Major" | ChangeScope.MAJOR | PropertyLocation.env + "scope" | "minor" | ChangeScope.MINOR | PropertyLocation.env + "scope" | "MINOR" | ChangeScope.MINOR | PropertyLocation.env + "scope" | "Minor" | ChangeScope.MINOR | PropertyLocation.env + "scope" | "patch" | ChangeScope.PATCH | PropertyLocation.env + "scope" | "PATCH" | ChangeScope.PATCH | PropertyLocation.env + "scope" | "Patch" | ChangeScope.PATCH | PropertyLocation.env + "scope" | "major" | ChangeScope.MAJOR | PropertyLocation.property + "scope" | "MAJOR" | ChangeScope.MAJOR | PropertyLocation.property + "scope" | "Major" | ChangeScope.MAJOR | PropertyLocation.property + "scope" | "minor" | ChangeScope.MINOR | PropertyLocation.property + "scope" | "MINOR" | ChangeScope.MINOR | PropertyLocation.property + "scope" | "Minor" | ChangeScope.MINOR | PropertyLocation.property + "scope" | "patch" | ChangeScope.PATCH | PropertyLocation.property + "scope" | "PATCH" | ChangeScope.PATCH | PropertyLocation.property + "scope" | "Patch" | ChangeScope.PATCH | PropertyLocation.property + "scope" | "null" | _ | PropertyLocation.none + "stage" | "snapshot" | _ | PropertyLocation.env + "stage" | "final" | _ | PropertyLocation.property + "stage" | "null" | _ | PropertyLocation.none + "versionScheme" | "semver2" | VersionScheme.semver2 | PropertyLocation.env + "versionScheme" | "semver" | VersionScheme.semver | PropertyLocation.env + "versionScheme" | "semver2" | VersionScheme.semver2 | PropertyLocation.property + "versionScheme" | "semver" | VersionScheme.semver | PropertyLocation.property + "versionCodeScheme" | "releaseCountBasic" | VersionCodeScheme.releaseCountBasic | PropertyLocation.env + "versionCodeScheme" | "releaseCount" | VersionCodeScheme.releaseCount | PropertyLocation.env + "versionCodeScheme" | "semver" | VersionCodeScheme.semver | PropertyLocation.env + "versionCodeScheme" | "none" | VersionCodeScheme.none | PropertyLocation.env + "versionCodeScheme" | "releaseCountBasic" | VersionCodeScheme.releaseCountBasic | PropertyLocation.property + "versionCodeScheme" | "releaseCount" | VersionCodeScheme.releaseCount | PropertyLocation.property + "versionCodeScheme" | "semver" | VersionCodeScheme.semver | PropertyLocation.property + "versionCodeScheme" | "none" | VersionCodeScheme.none | PropertyLocation.property + "versionCodeOffset" | 100 | _ | PropertyLocation.env + "versionCodeOffset" | 200 | _ | PropertyLocation.property + "releaseBranchPattern" | /^m.*/ | _ | PropertyLocation.property + "releaseBranchPattern" | /(some|value)/ | _ | PropertyLocation.env + "releaseBranchPattern" | '/.*/' | /.*/ | PropertyLocation.script + "mainBranchPattern" | /^m.*/ | _ | PropertyLocation.property + "mainBranchPattern" | /(some|value)/ | _ | PropertyLocation.env + "mainBranchPattern" | '/.*/' | /.*/ | PropertyLocation.script extensionName = "versionBuilder" testValue = (expectedValue == _) ? value : expectedValue @@ -162,31 +168,47 @@ class VersionPluginIntegrationSpec extends IntegrationSpec { result.standardOutput.contains("${extensionName}.${property}: ${rawValue}") where: - property | method | rawValue | type - "versionScheme" | "versionScheme" | "semver" | "String" - "versionScheme" | "versionScheme" | VersionScheme.semver2 | "VersionScheme" - "versionScheme" | "versionScheme" | VersionScheme.semver | "Provider" - "versionScheme" | "versionScheme.set" | VersionScheme.semver2 | "VersionScheme" - "versionScheme" | "versionScheme.set" | VersionScheme.semver | "Provider" - "versionScheme" | "setVersionScheme" | "semver" | "String" - "versionScheme" | "setVersionScheme" | VersionScheme.semver2 | "VersionScheme" - "versionScheme" | "setVersionScheme" | VersionScheme.semver | "Provider" - - "versionCodeScheme" | "versionCodeScheme" | "releaseCountBasic" | "String" - "versionCodeScheme" | "versionCodeScheme" | VersionCodeScheme.semver | "VersionCodeScheme" - "versionCodeScheme" | "versionCodeScheme" | VersionCodeScheme.releaseCountBasic | "Provider" - "versionCodeScheme" | "versionCodeScheme.set" | VersionCodeScheme.semver | "VersionCodeScheme" - "versionCodeScheme" | "versionCodeScheme.set" | VersionCodeScheme.releaseCountBasic | "Provider" - "versionCodeScheme" | "setVersionCodeScheme" | "releaseCount" | "String" - "versionCodeScheme" | "setVersionCodeScheme" | VersionCodeScheme.none | "VersionCodeScheme" - "versionCodeScheme" | "setVersionCodeScheme" | VersionCodeScheme.releaseCount | "Provider" - - "versionCodeOffset" | "versionCodeOffset" | 1 | "Integer" - "versionCodeOffset" | "versionCodeOffset" | 2 | "Provider" - "versionCodeOffset" | "versionCodeOffset.set" | 3 | "Integer" - "versionCodeOffset" | "versionCodeOffset.set" | 4 | "Provider" - "versionCodeOffset" | "setVersionCodeOffset" | 5 | "Integer" - "versionCodeOffset" | "setVersionCodeOffset" | 6 | "Provider" + property | method | rawValue | type + "versionScheme" | "versionScheme" | "semver" | "String" + "versionScheme" | "versionScheme" | VersionScheme.semver2 | "VersionScheme" + "versionScheme" | "versionScheme" | VersionScheme.semver | "Provider" + "versionScheme" | "versionScheme" | VersionScheme.staticMarker | "Provider" + "versionScheme" | "versionScheme.set" | VersionScheme.semver2 | "VersionScheme" + "versionScheme" | "versionScheme.set" | VersionScheme.semver | "Provider" + "versionScheme" | "versionScheme.set" | VersionScheme.staticMarker | "Provider" + "versionScheme" | "setVersionScheme" | "semver" | "String" + "versionScheme" | "setVersionScheme" | VersionScheme.semver2 | "VersionScheme" + "versionScheme" | "setVersionScheme" | VersionScheme.semver | "Provider" + + "versionCodeScheme" | "versionCodeScheme" | "releaseCountBasic" | "String" + "versionCodeScheme" | "versionCodeScheme" | VersionCodeScheme.semver | "VersionCodeScheme" + "versionCodeScheme" | "versionCodeScheme" | VersionCodeScheme.releaseCountBasic | "Provider" + "versionCodeScheme" | "versionCodeScheme.set" | VersionCodeScheme.semver | "VersionCodeScheme" + "versionCodeScheme" | "versionCodeScheme.set" | VersionCodeScheme.releaseCountBasic | "Provider" + "versionCodeScheme" | "setVersionCodeScheme" | "releaseCount" | "String" + "versionCodeScheme" | "setVersionCodeScheme" | VersionCodeScheme.none | "VersionCodeScheme" + "versionCodeScheme" | "setVersionCodeScheme" | VersionCodeScheme.releaseCount | "Provider" + + "versionCodeOffset" | "versionCodeOffset" | 1 | "Integer" + "versionCodeOffset" | "versionCodeOffset" | 2 | "Provider" + "versionCodeOffset" | "versionCodeOffset.set" | 3 | "Integer" + "versionCodeOffset" | "versionCodeOffset.set" | 4 | "Provider" + "versionCodeOffset" | "setVersionCodeOffset" | 5 | "Integer" + "versionCodeOffset" | "setVersionCodeOffset" | 6 | "Provider" + + "releaseBranchPattern" | "releaseBranchPattern" | /.*/ | "String" + "releaseBranchPattern" | "releaseBranchPattern" | /(some|value)/ | "Provider" + "releaseBranchPattern" | "releaseBranchPattern.set" | /[a-z]/ | "String" + "releaseBranchPattern" | "releaseBranchPattern.set" | /[0-9]/ | "Provider" + "releaseBranchPattern" | "setReleaseBranchPattern" | /[alto]-[sierra]/ | "String" + "releaseBranchPattern" | "setReleaseBranchPattern" | /[whooom]/ | "Provider" + + "mainBranchPattern" | "mainBranchPattern" | /.*/ | "String" + "mainBranchPattern" | "mainBranchPattern" | /(some|value)/ | "Provider" + "mainBranchPattern" | "mainBranchPattern.set" | /[a-z]/ | "String" + "mainBranchPattern" | "mainBranchPattern.set" | /[0-9]/ | "Provider" + "mainBranchPattern" | "setMainBranchPattern" | /[alto]-[sierra]/ | "String" + "mainBranchPattern" | "setMainBranchPattern" | /[whooom]/ | "Provider" value = wrapValueBasedOnType(rawValue, type) extensionName = "versionBuilder" diff --git a/src/main/groovy/wooga/gradle/version/VersionConsts.groovy b/src/main/groovy/wooga/gradle/version/VersionConsts.groovy index e7dba77..903acfc 100644 --- a/src/main/groovy/wooga/gradle/version/VersionConsts.groovy +++ b/src/main/groovy/wooga/gradle/version/VersionConsts.groovy @@ -22,6 +22,9 @@ class VersionConsts { static final String GIT_ROOT_PROPERTY = "git.root" static final String UNINITIALIZED_VERSION = '0.1.0-dev.0.uninitialized' + static final String DEFAULT_MAIN_BRANCH_PATTERN = /(^master$|^develop$)/ + static final String DEFAULT_RELEASE_BRANCH_PATTERN = /(^release\/.*|^master$)/ + static final VersionScheme VERSION_SCHEME_DEFAULT = VersionScheme.semver static final VersionCodeScheme VERSION_CODE_SCHEME_DEFAULT = VersionCodeScheme.none @@ -43,4 +46,10 @@ class VersionConsts { static final String VERSION_STAGE_OPTION = "versionBuilder.stage" static final String VERSION_STAGE_ENV_VAR = "VERSION_BUILDER_STAGE" + + static final String RELEASE_BRANCH_PATTERN_OPTION = "versionBuilder.releaseBranchPattern" + static final String RELEASE_BRANCH_PATTERN_ENV_VAR = "VERSION_BUILDER_RELEASE_BRANCH_PATTERN" + + static final String MAIN_BRANCH_PATTERN_OPTION = "versionBuilder.mainBranchPattern" + static final String MAIN_BRANCH_PATTERN_ENV_VAR = "VERSION_BUILDER_MAIN_BRANCH_PATTERN" } diff --git a/src/main/groovy/wooga/gradle/version/VersionPluginExtension.groovy b/src/main/groovy/wooga/gradle/version/VersionPluginExtension.groovy index 12d4a2c..d71382f 100644 --- a/src/main/groovy/wooga/gradle/version/VersionPluginExtension.groovy +++ b/src/main/groovy/wooga/gradle/version/VersionPluginExtension.groovy @@ -49,6 +49,18 @@ interface VersionPluginExtension { void setVersionCodeOffset(Integer value) void setVersionCodeOffset(Provider value) + Property getReleaseBranchPattern() + void releaseBranchPattern(String value) + void releaseBranchPattern(Provider value) + void setReleaseBranchPattern(String value) + void setReleaseBranchPattern(Provider value) + + Property getMainBranchPattern() + void mainBranchPattern(String value) + void mainBranchPattern(Provider value) + void setMainBranchPattern(String value) + void setMainBranchPattern(Provider value) + Property getGit() Provider getVersion() diff --git a/src/main/groovy/wooga/gradle/version/VersionScheme.groovy b/src/main/groovy/wooga/gradle/version/VersionScheme.groovy index 236053c..709c8a8 100644 --- a/src/main/groovy/wooga/gradle/version/VersionScheme.groovy +++ b/src/main/groovy/wooga/gradle/version/VersionScheme.groovy @@ -19,7 +19,7 @@ package wooga.gradle.version enum VersionScheme { - semver, semver2 + semver, semver2, staticMarker } diff --git a/src/main/groovy/wooga/gradle/version/internal/DefaultVersionPluginExtension.groovy b/src/main/groovy/wooga/gradle/version/internal/DefaultVersionPluginExtension.groovy index 67fefce..9696f53 100644 --- a/src/main/groovy/wooga/gradle/version/internal/DefaultVersionPluginExtension.groovy +++ b/src/main/groovy/wooga/gradle/version/internal/DefaultVersionPluginExtension.groovy @@ -34,6 +34,7 @@ import wooga.gradle.version.VersionConsts import wooga.gradle.version.VersionPluginExtension import wooga.gradle.version.strategies.SemverV1Strategies import wooga.gradle.version.strategies.SemverV2Strategies +import wooga.gradle.version.strategies.StaticMarkerStrategies class DefaultVersionPluginExtension implements VersionPluginExtension { @@ -55,6 +56,8 @@ class DefaultVersionPluginExtension implements VersionPluginExtension { final TagStrategy tagStrategy = new TagStrategy() final Provider version final Provider versionCode + final Property releaseBranchPattern + final Property mainBranchPattern @Override void versionScheme(VersionScheme value) { @@ -136,6 +139,46 @@ class DefaultVersionPluginExtension implements VersionPluginExtension { versionCodeOffset.set(value) } + @Override + void releaseBranchPattern(String value) { + setReleaseBranchPattern(value) + } + + @Override + void releaseBranchPattern(Provider value) { + setReleaseBranchPattern(value) + } + + @Override + void setReleaseBranchPattern(String value) { + releaseBranchPattern.set(value) + } + + @Override + void setReleaseBranchPattern(Provider value) { + releaseBranchPattern.set(value) + } + + @Override + void mainBranchPattern(String value) { + setMainBranchPattern(value) + } + + @Override + void mainBranchPattern(Provider value) { + setMainBranchPattern(value) + } + + @Override + void setMainBranchPattern(String value) { + mainBranchPattern.set(value) + } + + @Override + void setMainBranchPattern(Provider value) { + mainBranchPattern.set(value) + } + DefaultVersionPluginExtension(Project project) { this.project = project versionScheme = project.objects.property(VersionScheme) @@ -148,21 +191,59 @@ class DefaultVersionPluginExtension implements VersionPluginExtension { preReleaseStrategy = project.objects.property(VersionStrategy) finalStrategy = project.objects.property(VersionStrategy) defaultStrategy = project.objects.property(VersionStrategy) + releaseBranchPattern = project.objects.property(String) + mainBranchPattern = project.objects.property(String) snapshotStrategy.set(versionScheme.map({ scheme -> - scheme == VersionScheme.semver2 ? SemverV2Strategies.SNAPSHOT : SemverV1Strategies.SNAPSHOT + switch (scheme) { + case VersionScheme.semver2: + SemverV2Strategies.SNAPSHOT + break; + case VersionScheme.staticMarker: + StaticMarkerStrategies.SNAPSHOT + break + default: + SemverV1Strategies.SNAPSHOT + } })) developmentStrategy.set(versionScheme.map({ scheme -> - scheme == VersionScheme.semver2 ? SemverV2Strategies.DEVELOPMENT : SemverV1Strategies.DEVELOPMENT + switch (scheme) { + case VersionScheme.semver2: + SemverV2Strategies.DEVELOPMENT + break; + case VersionScheme.staticMarker: + StaticMarkerStrategies.DEVELOPMENT + break + default: + SemverV1Strategies.DEVELOPMENT + } })) preReleaseStrategy.set(versionScheme.map({ scheme -> - scheme == VersionScheme.semver2 ? SemverV2Strategies.PRE_RELEASE : SemverV1Strategies.PRE_RELEASE + switch (scheme) { + case VersionScheme.semver2: + SemverV2Strategies.PRE_RELEASE + break; + case VersionScheme.staticMarker: + StaticMarkerStrategies.PRE_RELEASE + break + default: + SemverV1Strategies.PRE_RELEASE + } })) finalStrategy.set(versionScheme.map({ scheme -> - scheme == VersionScheme.semver2 ? SemverV2Strategies.FINAL : SemverV1Strategies.FINAL + switch (scheme) { + case VersionScheme.semver2: + SemverV2Strategies.FINAL + break; + case VersionScheme.staticMarker: + StaticMarkerStrategies.FINAL + break + default: + SemverV1Strategies.FINAL + } })) defaultStrategy.set(developmentStrategy) @@ -191,6 +272,18 @@ class DefaultVersionPluginExtension implements VersionPluginExtension { project.properties.get(VersionConsts.LEGACY_VERSION_STAGE_OPTION) }) + releaseBranchPattern.set( project.provider({ + (System.getenv()[VersionConsts.RELEASE_BRANCH_PATTERN_ENV_VAR] ?: + project.properties.get(VersionConsts.RELEASE_BRANCH_PATTERN_OPTION) ?: + VersionConsts.DEFAULT_RELEASE_BRANCH_PATTERN).toString() + })) + + mainBranchPattern.set(project.provider({ + (System.getenv()[VersionConsts.MAIN_BRANCH_PATTERN_ENV_VAR] ?: + project.properties.get(VersionConsts.MAIN_BRANCH_PATTERN_OPTION) ?: + VersionConsts.DEFAULT_MAIN_BRANCH_PATTERN).toString() + })) + version = new MemoisationProvider(project.provider({ def versionStrategies = versionStrategies.get() def defaultStrategy = defaultStrategy.get() diff --git a/src/main/groovy/wooga/gradle/version/internal/release/base/MarkerTagStrategy.groovy b/src/main/groovy/wooga/gradle/version/internal/release/base/MarkerTagStrategy.groovy new file mode 100644 index 0000000..3f499e4 --- /dev/null +++ b/src/main/groovy/wooga/gradle/version/internal/release/base/MarkerTagStrategy.groovy @@ -0,0 +1,47 @@ +/* + * Copyright 2018-2020 Wooga GmbH + * + * 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.internal.release.base + +import com.github.zafarkhaja.semver.ParseException +import com.github.zafarkhaja.semver.Version +import org.ajoberstar.grgit.Tag + +class MarkerTagStrategy extends TagStrategy { + + private final String prefix + + Closure parseTag = { Tag tag -> + try { + if(tag.name.startsWith(prefix)) { + Version.valueOf(tag.name.replace(prefix,"")) + } else + { + null + } + + } catch (ParseException e) { + null + } + } + + MarkerTagStrategy(String prefix) { + super(false) + this.prefix = prefix + } +} 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 e384eef..1165de9 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 @@ -15,6 +15,8 @@ */ package wooga.gradle.version.internal.release.opinion +import wooga.gradle.version.internal.release.semver.ChangeScope +import wooga.gradle.version.internal.release.semver.SemVerStrategyState import static wooga.gradle.version.internal.release.semver.StrategyUtil.* @@ -150,13 +152,13 @@ final class Strategies { if (majorDiff == 1 && minor <= 0) { // major is off by one and minor is either 0 or not in the branch name - return incrementNormalFromScope(state, wooga.gradle.version.internal.release.semver.ChangeScope.MAJOR) + 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, wooga.gradle.version.internal.release.semver.ChangeScope.MINOR) + 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, wooga.gradle.version.internal.release.semver.ChangeScope.PATCH) + return incrementNormalFromScope(state, ChangeScope.PATCH) } else if (majorDiff == 0 && minor < 0) { // only major specified in branch name and already matches return state @@ -172,9 +174,18 @@ final class Strategies { /** * Always use the scope provided to increment the normal component. */ - static PartialSemVerStrategy useScope(wooga.gradle.version.internal.release.semver.ChangeScope scope) { + static PartialSemVerStrategy useScope(ChangeScope scope) { return closure { state -> incrementNormalFromScope(state, scope) } } + + static final PartialSemVerStrategy USE_MARKER_TAG = closure { SemVerStrategyState state -> + def markerVersion = state.nearestCiMarker + if(state.currentBranch.name.matches(state.releaseBranchPattern)) { + markerVersion = state.nearestReleaseMarker + } + state = state.copyWith(inferredNormal: markerVersion.normal) + return state + } } /** @@ -251,6 +262,17 @@ final class Strategies { state } } + + static final PartialSemVerStrategy COUNT_COMMITS_SINCE_MARKER = closure { SemVerStrategyState state -> + def markerVersion = state.nearestCiMarker + if(state.currentBranch.name.matches(state.releaseBranchPattern)) { + markerVersion = state.nearestReleaseMarker + } + + def count = markerVersion.distanceFromAny + def inferred = state.inferredPreRelease ? "${state.inferredPreRelease}.${count}" : "${count}" + return state.copyWith(inferredPreRelease: inferred) + } } /** @@ -280,11 +302,11 @@ final class Strategies { * If the {@code release.scope} property is set, use it. Or if the nearest any's normal component is different * than the nearest normal version, use it. Or, if nothing else, use PATCH scope. */ - static final wooga.gradle.version.internal.release.semver.SemVerStrategy DEFAULT = new wooga.gradle.version.internal.release.semver.SemVerStrategy( + static final SemVerStrategy DEFAULT = new SemVerStrategy( name: '', stages: [] as SortedSet, allowDirtyRepo: false, - normalStrategy: one(Normal.USE_SCOPE_PROP, Normal.USE_NEAREST_ANY, Normal.useScope(wooga.gradle.version.internal.release.semver.ChangeScope.PATCH)), + normalStrategy: one(Normal.USE_SCOPE_PROP, Normal.USE_NEAREST_ANY, Normal.useScope(ChangeScope.PATCH)), preReleaseStrategy: PreRelease.NONE, buildMetadataStrategy: BuildMetadata.NONE, createTag: true, @@ -296,7 +318,7 @@ final class Strategies { * not enforce precedence. The pre-release compoment will always be "SNAPSHOT" * and no build metadata will be used. Tags will not be created for these versions. */ - static final wooga.gradle.version.internal.release.semver.SemVerStrategy SNAPSHOT = DEFAULT.copyWith( + static final SemVerStrategy SNAPSHOT = DEFAULT.copyWith( name: 'snapshot', stages: ['SNAPSHOT'] as SortedSet, allowDirtyRepo: true, @@ -314,7 +336,7 @@ final class Strategies { * will note if the repository is dirty. The abbreviated ID of the HEAD will * be used as build metadata. */ - static final wooga.gradle.version.internal.release.semver.SemVerStrategy DEVELOPMENT = DEFAULT.copyWith( + static final SemVerStrategy DEVELOPMENT = DEFAULT.copyWith( name: 'development', stages: ['dev'] as SortedSet, allowDirtyRepo: true, @@ -331,7 +353,7 @@ final class Strategies { * note that this strategy uses the same name as {@code PRE_RELEASE_ALPHA_BETA} * so it cannot be used at the same time. */ - static final wooga.gradle.version.internal.release.semver.SemVerStrategy PRE_RELEASE = DEFAULT.copyWith( + static final SemVerStrategy PRE_RELEASE = DEFAULT.copyWith( name: 'pre-release', stages: ['milestone', 'rc'] as SortedSet, preReleaseStrategy: all(PreRelease.STAGE_FIXED, PreRelease.COUNT_INCREMENTED) @@ -345,7 +367,7 @@ final class Strategies { * that this strategy uses the same name as {@code PRE_RELEASE} so it cannot be used * at the same time. */ - static final wooga.gradle.version.internal.release.semver.SemVerStrategy PRE_RELEASE_ALPHA_BETA = PRE_RELEASE.copyWith( + static final SemVerStrategy PRE_RELEASE_ALPHA_BETA = PRE_RELEASE.copyWith( name: 'pre-release', stages: ['alpha', 'beta', 'rc'] as SortedSet ) @@ -355,7 +377,7 @@ final class Strategies { * will enforce precedence. The pre-release and build metadata components * will always be empty. */ - static final wooga.gradle.version.internal.release.semver.SemVerStrategy FINAL = DEFAULT.copyWith( + static final SemVerStrategy FINAL = DEFAULT.copyWith( name: 'final', stages: ['final'] as SortedSet ) diff --git a/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategy.groovy b/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategy.groovy index 48ce219..93f5077 100644 --- a/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategy.groovy +++ b/src/main/groovy/wooga/gradle/version/internal/release/semver/SemVerStrategy.groovy @@ -19,6 +19,7 @@ import groovy.transform.Immutable import groovy.transform.PackageScope import com.github.zafarkhaja.semver.Version +import wooga.gradle.version.internal.release.base.MarkerTagStrategy import wooga.gradle.version.internal.release.base.ReleaseVersion import wooga.gradle.version.internal.release.base.DefaultVersionStrategy import org.ajoberstar.grgit.Grgit @@ -148,12 +149,18 @@ final class SemVerStrategy implements DefaultVersionStrategy { ReleaseVersion doInfer(Project project, Grgit grgit, NearestVersionLocator locator) { ChangeScope scope = project.extensions.getByType(VersionPluginExtension).scope.getOrNull() String stage = project.extensions.getByType(VersionPluginExtension).stage.getOrNull() ?: stages.first() + + String releaseBranchPattern = project.extensions.getByType(VersionPluginExtension).releaseBranchPattern.get() + String mainBranchPattern = project.extensions.getByType(VersionPluginExtension).mainBranchPattern.get() + if (!stages.contains(stage)) { throw new GradleException("Stage ${stage} is not one of ${stages} allowed for strategy ${name}.") } logger.info('Beginning version inference using {} strategy and input scope ({}) and stage ({})', name, scope, stage) NearestVersion nearestVersion = locator.locate(grgit) + NearestVersion nearestCiMarker = new NearestVersionLocator(new MarkerTagStrategy("ci-")).locate(grgit) + NearestVersion nearestReleaseMarker = new NearestVersionLocator(new MarkerTagStrategy("release-")).locate(grgit) logger.debug('Located nearest version: {}', nearestVersion) SemVerStrategyState state = new SemVerStrategyState( @@ -162,7 +169,11 @@ final class SemVerStrategy implements DefaultVersionStrategy { currentHead: grgit.head(), currentBranch: grgit.branch.current, repoDirty: !grgit.status().clean, - nearestVersion: nearestVersion + nearestVersion: nearestVersion, + nearestCiMarker: nearestCiMarker, + nearestReleaseMarker: nearestReleaseMarker, + releaseBranchPattern: releaseBranchPattern, + mainBranchPattern: mainBranchPattern ) Version version = StrategyUtil.all( 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 63c725d..cfe3c84 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 @@ -22,6 +22,7 @@ import com.github.zafarkhaja.semver.Version import org.ajoberstar.grgit.Branch import org.ajoberstar.grgit.Commit +import org.ajoberstar.grgit.Tag /** * Working state used by {@link PartialSemVerStrategy}. @@ -38,6 +39,11 @@ final class SemVerStrategyState { String inferredNormal String inferredPreRelease String inferredBuildMetadata + NearestVersion nearestCiMarker + NearestVersion nearestReleaseMarker + + String releaseBranchPattern + String mainBranchPattern Version toVersion() { return new Version.Builder().with { diff --git a/src/main/groovy/wooga/gradle/version/strategies/SemverV1Strategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/SemverV1Strategies.groovy index 3e59677..8281989 100644 --- a/src/main/groovy/wooga/gradle/version/strategies/SemverV1Strategies.groovy +++ b/src/main/groovy/wooga/gradle/version/strategies/SemverV1Strategies.groovy @@ -195,6 +195,7 @@ class SemverV1Strategies { if( branchName != "master") { branchName = "$prefix${branchName.capitalize()}" } + branchName = branchName.replaceAll(/(\/|-|_)([\w])/) {all, delimiter, firstAfter -> "${firstAfter.capitalize()}" } branchName = branchName.replaceAll(/\./, "Dot") branchName = branchName.replaceAll(/0/, "Zero") diff --git a/src/main/groovy/wooga/gradle/version/strategies/SemverV2Strategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/SemverV2Strategies.groovy index 181f304..d3d8287 100644 --- a/src/main/groovy/wooga/gradle/version/strategies/SemverV2Strategies.groovy +++ b/src/main/groovy/wooga/gradle/version/strategies/SemverV2Strategies.groovy @@ -66,7 +66,7 @@ final class SemverV2Strategies { branchName = System.getenv("BRANCH_NAME") } - if (branchName != "master" && branchName != "develop") { + if (!branchName.matches(state.mainBranchPattern)) { branchName = "$prefix.${branchName.toLowerCase()}" } //Split at branch delimiter /-_+ and replace with . diff --git a/src/main/groovy/wooga/gradle/version/strategies/StaticMarkerStrategies.groovy b/src/main/groovy/wooga/gradle/version/strategies/StaticMarkerStrategies.groovy new file mode 100644 index 0000000..82a483c --- /dev/null +++ b/src/main/groovy/wooga/gradle/version/strategies/StaticMarkerStrategies.groovy @@ -0,0 +1,161 @@ +/* + * Copyright 2018-2020 Wooga GmbH + * + * 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.internal.release.opinion.Strategies +import wooga.gradle.version.internal.release.semver.SemVerStrategy + +import static wooga.gradle.version.internal.release.semver.StrategyUtil.all +import static wooga.gradle.version.internal.release.semver.StrategyUtil.one + +class StaticMarkerStrategies { + + private static final scopes = one(Strategies.Normal.USE_MARKER_TAG) + + static final SemVerStrategy DEFAULT = new SemVerStrategy( + name: '', + stages: [] as SortedSet, + allowDirtyRepo: true, + normalStrategy: scopes, + preReleaseStrategy: Strategies.PreRelease.NONE, + buildMetadataStrategy: Strategies.BuildMetadata.NONE, + createTag: true, + enforcePrecedence: true + ) + + /** + * 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 = DEFAULT.copyWith( + name: 'production', + stages: ['production'] as SortedSet + ) + + /** + * 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 = DEFAULT.copyWith( + name: 'pre-release', + stages: ['staging'] as SortedSet, + preReleaseStrategy: all(Strategies.PreRelease.STAGE_FIXED, Strategies.PreRelease.COUNT_INCREMENTED) + ) + + /** + * 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: scopes, + buildMetadataStrategy: NetflixOssStrategies.BuildMetadata.DEVELOPMENT_METADATA_STRATEGY) + + /** + * 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 = DEFAULT.copyWith( + name: 'snapshot', + stages: ['ci'] as SortedSet, + createTag: false, + preReleaseStrategy: all(SemverV2Strategies.PreRelease.STAGE_BRANCH_NAME, Strategies.PreRelease.COUNT_COMMITS_SINCE_MARKER), + enforcePrecedence: false + ) +} diff --git a/src/test/groovy/wooga/gradle/version/VersionPluginSpec.groovy b/src/test/groovy/wooga/gradle/version/VersionPluginSpec.groovy index c42f525..00e5075 100644 --- a/src/test/groovy/wooga/gradle/version/VersionPluginSpec.groovy +++ b/src/test/groovy/wooga/gradle/version/VersionPluginSpec.groovy @@ -609,6 +609,105 @@ class VersionPluginSpec extends ProjectSpec { scopeTitle = (scope == _) ? "unset" : scope } + @Unroll('verify inferred semver 2.x version from productionMarker: #productionMarkerTitle, ciMarker: #ciMarkerTitle, stage: #stage and branch: #branchName to be #expectedVersion') + def "uses custom wooga application strategies static marker"() { + given: "a project with specified release stage and scope" + + project.ext.set('release.stage', stage) + project.ext.set('version.scheme', 'staticMarker') + + if (releaseBranchPattern != _) { + project.ext.set('versionBuilder.releaseBranchPattern', releaseBranchPattern) + } + + if (mainBranchPattern != _) { + project.ext.set('versionBuilder.mainBranchPattern', mainBranchPattern) + } + + and: "a history" + + if (branchName != "master") { + git.checkout(branch: "$branchName", startPoint: 'master', createBranch: true) + } + + 5.times { + git.commit(message: 'feature commit') + } + + if (ciMarker != _) { + git.tag.add(name: "ci-${ciMarker}") + } + + if (productionMarker != _) { + git.tag.add(name: "release-${productionMarker}") + } + + (distance - distanceNearestAny).times { + git.commit(message: 'fix commit') + } + + if (nearestAny != _) { + git.tag.add(name: "v${nearestAny}") + } + + distanceNearestAny.times { + git.commit(message: 'fix commit') + } + + when: + project.plugins.apply(PLUGIN_NAME) + + then: + project.version.toString() == expectedVersion + + + where: + nearestAny | ciMarker | productionMarker | distance | distanceNearestAny | stage | branchName | releaseBranchPattern | mainBranchPattern | expectedVersion + _ | "0.2.0" | "0.1.0" | 1 | 0 | "ci" | "develop" | _ | _ | "0.2.0-develop.1" + _ | "0.2.0" | "0.1.0" | 2 | 0 | "ci" | "develop" | _ | /^master$/ | "0.2.0-branch.develop.2" + _ | "0.2.0" | "0.1.0" | 3 | 0 | "ci" | "master" | _ | _ | "0.1.0-master.3" + _ | "0.2.0" | "0.1.0" | 4 | 0 | "ci" | "master" | /^(release\/.*|production)$/ | _ | "0.2.0-master.4" + _ | "0.2.0" | "0.1.0" | 5 | 0 | "ci" | "production" | /^(release\/.*|production)$/ | /^(master|production)$/ | "0.1.0-production.5" + _ | "0.2.0" | "0.1.0" | 6 | 0 | "ci" | "feature/test" | _ | _ | "0.2.0-branch.feature.test.6" + "0.2.0" | "0.3.0" | "0.2.0" | 7 | 3 | "ci" | "master" | _ | _ | "0.2.0-master.7" + "0.2.0" | "0.3.0" | "0.2.0" | 7 | 3 | "ci" | "develop" | _ | _ | "0.3.0-develop.7" + + _ | "0.2.0" | "0.1.0" | 1 | 0 | "staging" | "develop" | _ | _ | "0.2.0-staging.1" + _ | "0.2.0" | "0.1.0" | 2 | 0 | "staging" | "develop" | _ | /^master$/ | "0.2.0-staging.1" + _ | "0.2.0" | "0.1.0" | 3 | 0 | "staging" | "master" | _ | _ | "0.1.0-staging.1" + _ | "0.2.0" | "0.1.0" | 4 | 0 | "staging" | "master" | /^(release\/.*|production)$/ | _ | "0.2.0-staging.1" + _ | "0.2.0" | "0.1.0" | 5 | 0 | "staging" | "production" | /^(release\/.*|production)$/ | /^(master|production)$/ | "0.1.0-staging.1" + _ | "0.2.0" | "0.1.0" | 6 | 0 | "staging" | "feature/test" | _ | _ | "0.2.0-staging.1" + "0.2.0-staging.1" | "0.3.0" | "0.2.0" | 7 | 3 | "staging" | "master" | _ | _ | "0.2.0-staging.2" + "0.2.0-staging.1" | "0.3.0" | "0.2.0" | 7 | 3 | "staging" | "develop" | _ | _ | "0.3.0-staging.1" + + _ | "0.2.0" | "0.1.0" | 1 | 0 | "production" | "develop" | _ | _ | "0.2.0" + _ | "0.2.0" | "0.1.0" | 2 | 0 | "production" | "develop" | _ | /^master$/ | "0.2.0" + _ | "0.2.0" | "0.1.0" | 3 | 0 | "production" | "master" | _ | _ | "0.1.0" + _ | "0.2.0" | "0.1.0" | 4 | 0 | "production" | "master" | /^(release\/.*|production)$/ | _ | "0.2.0" + _ | "0.2.0" | "0.1.0" | 5 | 0 | "production" | "production" | /^(release\/.*|production)$/ | /^(master|production)$/ | "0.1.0" + _ | "0.2.0" | "0.1.0" | 6 | 0 | "production" | "feature/test" | _ | _ | "0.2.0" + "0.2.0-staging.1" | "0.3.0" | "0.2.0" | 7 | 3 | "production" | "master" | _ | _ | "0.2.0" + "0.2.0-staging.1" | "0.3.0" | "0.2.0" | 7 | 3 | "production" | "develop" | _ | _ | "0.3.0" + + _ | "0.2.0" | "0.1.0" | 10 | 0 | "ci" | "test/build01-" | _ | _ | "0.2.0-branch.test.build.1.10" + _ | "0.2.0" | _ | 22 | 0 | "ci" | "test/build01+" | _ | _ | "0.2.0-branch.test.build.1.22" + _ | "0.2.0" | _ | 45 | 0 | "ci" | "test/build01_" | _ | _ | "0.2.0-branch.test.build.1.45" + _ | "0.2.0" | _ | 204 | 0 | "ci" | "test/build01" | _ | _ | "0.2.0-branch.test.build.1.204" + _ | "0.2.0" | _ | 100 | 0 | "ci" | "test/build.01" | _ | _ | "0.2.0-branch.test.build.1.100" + _ | "0.2.0" | _ | 55 | 0 | "ci" | "test/build002" | _ | _ | "0.2.0-branch.test.build.2.55" + _ | "0.2.0" | _ | 66 | 0 | "ci" | "test/build.002" | _ | _ | "0.2.0-branch.test.build.2.66" + _ | "0.2.0" | _ | 789 | 0 | "ci" | "test/build000000000003" | _ | _ | "0.2.0-branch.test.build.3.789" + _ | "0.2.0" | _ | 777 | 0 | "ci" | "test/build.000000000003" | _ | _ | "0.2.0-branch.test.build.3.777" + _ | "0.2.0" | _ | 789 | 0 | "ci" | "test/build000000.000003" | _ | _ | "0.2.0-branch.test.build.0.3.789" + _ | "0.2.0" | _ | 3 | 0 | "ci" | "test/build.000000.000003" | _ | _ | "0.2.0-branch.test.build.0.3.3" + _ | "0.2.0" | "0.1.0" | 3 | 0 | "ci" | "release/1.00.x" | _ | _ | "0.1.0-branch.release.1.0.x.3" + + nearestNormal = '1.0.0' + productionMarkerTitle = (productionMarker == _) ? "unset" : productionMarker + ciMarkerTitle = (ciMarker == _) ? "unset" : ciMarker + } + @Unroll("Finds correct nearest version #useTagPrefix from nearestNormal: #nearestNormal, nearestAny: #nearestAnyTitle, scope: #scopeTitle, stage: #stage and branch: #branchName") def "finds nearest version tag"() { given: "a project with specified release stage and scope"