diff --git a/src/integration/groovy/pl/allegro/tech/build/axion/release/BaseIntegrationTest.groovy b/src/integration/groovy/pl/allegro/tech/build/axion/release/BaseIntegrationTest.groovy index 771ed37e..c9850b18 100644 --- a/src/integration/groovy/pl/allegro/tech/build/axion/release/BaseIntegrationTest.groovy +++ b/src/integration/groovy/pl/allegro/tech/build/axion/release/BaseIntegrationTest.groovy @@ -2,6 +2,7 @@ package pl.allegro.tech.build.axion.release import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.internal.DefaultBuildResult import java.nio.file.Files @@ -41,14 +42,20 @@ class BaseIntegrationTest extends RepositoryBasedTest { BuildResult runGradle(String... arguments) { def args = [] - args.addAll(arguments) args.add("--stacktrace") args.add("--configuration-cache") args.add("--warning-mode=fail") + args.addAll(arguments) + runGradleWithArgs(args) + } + BuildResult runGradleWithArgs(List args) { try { return gradle().withArguments(args).build() } + catch (e) { + throw new RuntimeException("Gradle build failed", e) + } finally { def ccDir = new File(temporaryFolder, "build/reports/configuration-cache") diff --git a/src/integration/groovy/pl/allegro/tech/build/axion/release/SimpleIntegrationTest.groovy b/src/integration/groovy/pl/allegro/tech/build/axion/release/SimpleIntegrationTest.groovy index 24ce14aa..0759379b 100644 --- a/src/integration/groovy/pl/allegro/tech/build/axion/release/SimpleIntegrationTest.groovy +++ b/src/integration/groovy/pl/allegro/tech/build/axion/release/SimpleIntegrationTest.groovy @@ -203,4 +203,69 @@ class SimpleIntegrationTest extends BaseIntegrationTest { cleanup: environmentVariablesRule.clear("GITHUB_ACTIONS", "GITHUB_OUTPUT") } + + def "should not update project.version after release when updateProjectVersionAfterRelease option is not set"() { + given: + buildFile(""" + task assemble { + inputs.property("version", project.version) + doLast { + println("Assembling project: " + version) + } + } + """) + + def result = runGradleWithArgs(['currentVersion', 'release', 'assemble', '-Prelease.localOnly', '-Prelease.disableChecks']) + + expect: + result.output.contains('Project version: 0.1.0-SNAPSHOT') + result.output.contains('Creating tag: v0.1.0') + result.output.contains('Assembling project: 0.1.0-SNAPSHOT') + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + result.task(":release").outcome == TaskOutcome.SUCCESS + result.task(":assemble").outcome == TaskOutcome.SUCCESS + } + + def "should update project.version after release when updateProjectVersionAfterRelease option is set"() { + given: + buildFile(""" + scmVersion { + updateProjectVersionAfterRelease = true + } + + task assemble { + inputs.property("version", project.version) + doLast { + println("Assembling project: " + version) + } + } + """) + + def result = runGradleWithArgs(['currentVersion', 'release', 'assemble', '-Prelease.localOnly', '-Prelease.disableChecks']) + + expect: + result.output.contains('Project version: 0.1.0-SNAPSHOT') + result.output.contains('Creating tag: v0.1.0') + result.output.contains('Project version will be updated after release.') + result.output.contains('Assembling project: 0.1.0') + result.task(":currentVersion").outcome == TaskOutcome.SUCCESS + result.task(":release").outcome == TaskOutcome.SUCCESS + result.task(":assemble").outcome == TaskOutcome.SUCCESS + } + + def "should throw error when using updateProjectVersionAfterRelease and configuration cache"() { + given: + buildFile(""" + scmVersion { + updateProjectVersionAfterRelease = true + } + """) + + when: + runGradle('release', '--stacktrace', '-Prelease.localOnly', '-Prelease.disableChecks') + + then: + def e = thrown(RuntimeException) + e.getCause().getMessage().contains('Configuration cache is enabled and `scmVersion.updateProjectVersionAfterRelease` is set to `true`. This is not supported. Set `scmVersion.updateProjectVersionAfterRelease` to `false` and remember to run release task separately.') + } } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/ConfigurationCacheConfiguration.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/ConfigurationCacheConfiguration.groovy new file mode 100644 index 00000000..26cab58a --- /dev/null +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/ConfigurationCacheConfiguration.groovy @@ -0,0 +1,13 @@ +package pl.allegro.tech.build.axion.release + +class ConfigurationCacheConfiguration { + boolean updateProjectVersionAfterRelease + boolean configurationCacheEnabled + public Closure updateProjectVersion + + ConfigurationCacheConfiguration(boolean updateProjectVersionAfterRelease, boolean configurationCacheEnabled, Closure updateProjectVersion) { + this.updateProjectVersionAfterRelease = updateProjectVersionAfterRelease + this.configurationCacheEnabled = configurationCacheEnabled + this.updateProjectVersion = updateProjectVersion + } +} diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/CreateReleaseTask.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/CreateReleaseTask.groovy index 0ba97aa5..152e0c6a 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/CreateReleaseTask.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/CreateReleaseTask.groovy @@ -1,20 +1,31 @@ package pl.allegro.tech.build.axion.release +import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import pl.allegro.tech.build.axion.release.domain.Releaser +import pl.allegro.tech.build.axion.release.domain.scm.ScmService import pl.allegro.tech.build.axion.release.infrastructure.di.VersionResolutionContext abstract class CreateReleaseTask extends BaseAxionTask { + @Input + boolean configurationCacheEnabled + @TaskAction void release() { VersionResolutionContext context = resolutionContext() Releaser releaser = context.releaser() + ScmService scmService = context.scmService() ReleaseBranchesConfiguration releaseBranchesConfiguration = new ReleaseBranchesConfiguration( - context.scmService().isReleaseOnlyOnReleaseBranches(), + scmService.isReleaseOnlyOnReleaseBranches(), context.repository().currentPosition().getBranch(), - context.scmService().getReleaseBranchNames() + scmService.getReleaseBranchNames() + ) + ConfigurationCacheConfiguration configurationCacheConfiguration = new ConfigurationCacheConfiguration( + scmService.isUpdateProjectVersionAfterRelease(), + configurationCacheEnabled, + (version) -> project.setVersion(version) ) - releaser.release(context.rules(), releaseBranchesConfiguration) + releaser.release(context.rules(), releaseBranchesConfiguration, configurationCacheConfiguration) } } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/ReleasePlugin.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/ReleasePlugin.groovy index 91f28c1d..9e034b13 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/ReleasePlugin.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/ReleasePlugin.groovy @@ -1,7 +1,9 @@ package pl.allegro.tech.build.axion.release +import com.github.zafarkhaja.semver.Version import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.internal.StartParameterInternal import pl.allegro.tech.build.axion.release.domain.SnapshotDependenciesChecker import pl.allegro.tech.build.axion.release.domain.VersionConfig import pl.allegro.tech.build.axion.release.util.FileLoader @@ -41,12 +43,26 @@ abstract class ReleasePlugin implements Plugin { group = 'Release' description = 'Performs release - creates tag and pushes it to remote.' dependsOn(VERIFY_RELEASE_TASK) + configurationCacheEnabled = Utils.isConfigurationCacheEnabled(project) + if (versionConfig.updateProjectVersionAfterRelease.get() && !project.tasks.matching { it.name == "assemble" }.isEmpty()) { + doLast { + logger.quiet("Project version will be updated after release.") + } + finalizedBy(project.tasks.named("assemble")) + } } project.tasks.register(CREATE_RELEASE_TASK, CreateReleaseTask) { group = 'Release' description = 'Performs first stage of release - creates tag.' dependsOn(VERIFY_RELEASE_TASK) + configurationCacheEnabled = Utils.isConfigurationCacheEnabled(project) + if (versionConfig.updateProjectVersionAfterRelease.get() && !project.tasks.matching { it.name == "assemble" }.isEmpty()) { + doLast { + logger.quiet("Project version will be updated after release.") + } + finalizedBy(project.tasks.named("assemble")) + } } project.tasks.register(PUSH_RELEASE_TASK, PushReleaseTask) { diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/ReleaseTask.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/ReleaseTask.groovy index 702e27fc..b022aa20 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/ReleaseTask.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/ReleaseTask.groovy @@ -1,9 +1,11 @@ package pl.allegro.tech.build.axion.release +import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import pl.allegro.tech.build.axion.release.domain.Releaser import pl.allegro.tech.build.axion.release.domain.scm.ScmPushResult import pl.allegro.tech.build.axion.release.domain.scm.ScmPushResultOutcome +import pl.allegro.tech.build.axion.release.domain.scm.ScmService import pl.allegro.tech.build.axion.release.infrastructure.di.VersionResolutionContext import java.nio.file.Files @@ -12,17 +14,32 @@ import java.nio.file.StandardOpenOption abstract class ReleaseTask extends BaseAxionTask { + @Input + boolean configurationCacheEnabled + @TaskAction void release() { VersionResolutionContext context = resolutionContext() Releaser releaser = context.releaser() + ScmService service = context.scmService() + ReleaseBranchesConfiguration releaseBranchesConfiguration = new ReleaseBranchesConfiguration( - context.scmService().isReleaseOnlyOnReleaseBranches(), + service.isReleaseOnlyOnReleaseBranches(), context.repository().currentPosition().getBranch(), - context.scmService().getReleaseBranchNames() + service.getReleaseBranchNames() ) - ScmPushResult result = releaser.releaseAndPush(context.rules(), releaseBranchesConfiguration) + ConfigurationCacheConfiguration configurationCacheConfiguration = new ConfigurationCacheConfiguration( + service.isUpdateProjectVersionAfterRelease(), + configurationCacheEnabled, + (version) -> project.setVersion(version) + ) + + ScmPushResult result = releaser.releaseAndPush( + context.rules(), + releaseBranchesConfiguration, + configurationCacheConfiguration + ) if (result.outcome === ScmPushResultOutcome.FAILED) { def status = result.failureStatus diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/Utils.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/Utils.groovy new file mode 100644 index 00000000..632acf25 --- /dev/null +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/Utils.groovy @@ -0,0 +1,33 @@ +package pl.allegro.tech.build.axion.release + +import com.github.zafarkhaja.semver.Version +import org.gradle.api.Project +import org.gradle.api.internal.StartParameterInternal + +class Utils { + static boolean isConfigurationCacheEnabled(Project project) { + try { + def gradleVersion = getGradleVersion(project.gradle.gradleVersion) + if (gradleVersion.greaterThan(Version.valueOf("7.6.0"))) { + return project.gradle.startParameter.isConfigurationCacheRequested() + } else if (gradleVersion.greaterThanOrEqualTo(Version.valueOf("7.0.0"))) { + return (project.gradle.startParameter as StartParameterInternal).isConfigurationCache() + } + } catch (e) { + project.logger.quiet("Cannot determine if configuration cache is enabled. Assuming it is not.", e) + return false + } + return false + } + + static Version getGradleVersion(String gradleVersionString) { + def split = gradleVersionString.split("\\.", 3) + if (split.length === 2) { + return Version.forIntegers(split[0].toInteger(), split[1].toInteger()) + } else if (split.length === 3) { + return Version.forIntegers(split[0].toInteger(), split[1].toInteger(), split[2].toInteger()) + } else { + throw new IllegalStateException("Cannot parse Gradle version: $gradleVersionString") + } + } +} diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionConfig.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionConfig.groovy index cf1d1282..8a215201 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionConfig.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/domain/VersionConfig.groovy @@ -29,6 +29,7 @@ abstract class VersionConfig extends BaseExtension { private static final String IGNORE_UNCOMMITTED_CHANGES_PROPERTY = 'release.ignoreUncommittedChanges' private static final String FORCE_SNAPSHOT_PROPERTY = 'release.forceSnapshot' private static final String USE_HIGHEST_VERSION_PROPERTY = 'release.useHighestVersion' + private static final String UPDATE_PROJECT_VERSION_AFTER_RELEASE_PROPERTY = 'release.updateProjectVersionAfterRelease' private static final String LOCAL_ONLY = "release.localOnly" private static final String FORCE_VERSION_PROPERTY = 'release.version' private static final String DEPRECATED_FORCE_VERSION_PROPERTY = 'release.forceVersion' @@ -43,6 +44,7 @@ abstract class VersionConfig extends BaseExtension { getLocalOnly().convention(false) getIgnoreUncommittedChanges().convention(true) getUseHighestVersion().convention(false) + getUpdateProjectVersionAfterRelease().convention(false) getUnshallowRepoOnCI().convention(false) getIgnoreGlobalGitConfig().convention(false) getReleaseBranchNames().convention(gradlePropertyAsSet(RELEASE_BRANCH_NAMES_PROPERTY).orElse(['master', 'main'] as Set)) @@ -127,6 +129,9 @@ abstract class VersionConfig extends BaseExtension { @Internal abstract Property getUseHighestVersion(); + @Internal + abstract Property getUpdateProjectVersionAfterRelease(); + Provider ignoreUncommittedChanges() { gradlePropertyPresent(IGNORE_UNCOMMITTED_CHANGES_PROPERTY) .orElse(ignoreUncommittedChanges) @@ -140,6 +145,10 @@ abstract class VersionConfig extends BaseExtension { gradlePropertyPresent(USE_HIGHEST_VERSION_PROPERTY).orElse(useHighestVersion) } + Provider updateProjectVersionAfterRelease() { + gradlePropertyPresent(UPDATE_PROJECT_VERSION_AFTER_RELEASE_PROPERTY).orElse(updateProjectVersionAfterRelease) + } + Provider localOnly() { gradlePropertyPresent(LOCAL_ONLY).orElse(localOnly) } diff --git a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/ScmPropertiesFactory.groovy b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/ScmPropertiesFactory.groovy index 8900f4cb..2190028e 100644 --- a/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/ScmPropertiesFactory.groovy +++ b/src/main/groovy/pl/allegro/tech/build/axion/release/infrastructure/config/ScmPropertiesFactory.groovy @@ -20,7 +20,8 @@ class ScmPropertiesFactory { config.getUnshallowRepoOnCI().get(), config.getReleaseBranchNames().get(), config.getReleaseOnlyOnReleaseBranches().get(), - config.getIgnoreGlobalGitConfig().get() + config.getIgnoreGlobalGitConfig().get(), + config.getUpdateProjectVersionAfterRelease().get(), ) } } diff --git a/src/main/java/pl/allegro/tech/build/axion/release/domain/Releaser.java b/src/main/java/pl/allegro/tech/build/axion/release/domain/Releaser.java index c70dd4d3..6228c76c 100644 --- a/src/main/java/pl/allegro/tech/build/axion/release/domain/Releaser.java +++ b/src/main/java/pl/allegro/tech/build/axion/release/domain/Releaser.java @@ -3,6 +3,7 @@ import com.github.zafarkhaja.semver.Version; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; +import pl.allegro.tech.build.axion.release.ConfigurationCacheConfiguration; import pl.allegro.tech.build.axion.release.ReleaseBranchesConfiguration; import pl.allegro.tech.build.axion.release.domain.hooks.ReleaseHooksRunner; import pl.allegro.tech.build.axion.release.domain.properties.Properties; @@ -25,7 +26,11 @@ public Releaser(VersionService versionService, ScmService repository, ReleaseHoo this.hooksRunner = hooksRunner; } - public Optional release(Properties properties, ReleaseBranchesConfiguration releaseBranchesConfiguration) { + public Optional release( + Properties properties, + ReleaseBranchesConfiguration releaseBranchesConfiguration, + ConfigurationCacheConfiguration configurationCacheConfiguration + ) { if (releaseBranchesConfiguration.shouldRelease()) { String message = String.format( "Release step skipped since 'releaseOnlyOnReleaseBranches' option is set, and '%s' was not in 'releaseBranchNames' list [%s]", @@ -42,6 +47,14 @@ public Optional release(Properties properties, ReleaseBranchesConfigurat Version version = versionContext.getVersion(); + if (configurationCacheConfiguration.isUpdateProjectVersionAfterRelease()) { + if (configurationCacheConfiguration.isConfigurationCacheEnabled()) { + throw new IllegalStateException("Configuration cache is enabled and `scmVersion.updateProjectVersionAfterRelease` is set to `true`. " + + "This is not supported. Set `scmVersion.updateProjectVersionAfterRelease` to `false` and remember to run release task separately."); + } + configurationCacheConfiguration.updateProjectVersion.call(version.toString()); + } + if (versionContext.isSnapshot()) { String tagName = properties.getTag().getSerialize().apply(properties.getTag(), version.toString()); @@ -58,8 +71,12 @@ public Optional release(Properties properties, ReleaseBranchesConfigurat } } - public ScmPushResult releaseAndPush(Properties rules, ReleaseBranchesConfiguration releaseBranchesConfiguration) { - Optional releasedTagName = release(rules, releaseBranchesConfiguration); + public ScmPushResult releaseAndPush( + Properties rules, + ReleaseBranchesConfiguration releaseBranchesConfiguration, + ConfigurationCacheConfiguration configurationCacheEnabled + ) { + Optional releasedTagName = release(rules, releaseBranchesConfiguration, configurationCacheEnabled); if (releasedTagName.isEmpty()) { return new ScmPushResult(ScmPushResultOutcome.SKIPPED, Optional.empty(), Optional.empty()); diff --git a/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmProperties.java b/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmProperties.java index a264aa65..5b731317 100644 --- a/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmProperties.java +++ b/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmProperties.java @@ -20,6 +20,7 @@ public class ScmProperties { private final Set releaseBranchNames; private final boolean releaseOnlyOnReleaseBranches; private final boolean ignoreGlobalGitConfig; + private final boolean updateProjectVersionAfterRelease; public ScmProperties( String type, @@ -35,7 +36,8 @@ public ScmProperties( Boolean unshallowRepoOnCI, Set releaseBranchNames, boolean releaseOnlyOnReleaseBranches, - boolean ignoreGlobalGitConfig + boolean ignoreGlobalGitConfig, + boolean updateProjectVersionAfterRelease ) { this.type = type; this.directory = directory; @@ -51,6 +53,7 @@ public ScmProperties( this.releaseBranchNames = releaseBranchNames; this.releaseOnlyOnReleaseBranches = releaseOnlyOnReleaseBranches; this.ignoreGlobalGitConfig = ignoreGlobalGitConfig; + this.updateProjectVersionAfterRelease = updateProjectVersionAfterRelease; } public ScmPushOptions pushOptions() { @@ -112,4 +115,8 @@ public boolean isReleaseOnlyOnReleaseBranches() { public boolean isIgnoreGlobalGitConfig() { return ignoreGlobalGitConfig; } + + public boolean isUpdateProjectVersionAfterRelease() { + return updateProjectVersionAfterRelease; + } } diff --git a/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmService.java b/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmService.java index 9fd7b9ab..2e43c924 100644 --- a/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmService.java +++ b/src/main/java/pl/allegro/tech/build/axion/release/domain/scm/ScmService.java @@ -78,4 +78,8 @@ public Set getReleaseBranchNames() { public boolean isReleaseOnlyOnReleaseBranches(){ return scmProperties.isReleaseOnlyOnReleaseBranches(); } + + public boolean isUpdateProjectVersionAfterRelease() { + return scmProperties.isUpdateProjectVersionAfterRelease(); + } } diff --git a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/scm/ScmPropertiesBuilder.groovy b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/scm/ScmPropertiesBuilder.groovy index 3e496d6b..8348b353 100644 --- a/src/test/groovy/pl/allegro/tech/build/axion/release/domain/scm/ScmPropertiesBuilder.groovy +++ b/src/test/groovy/pl/allegro/tech/build/axion/release/domain/scm/ScmPropertiesBuilder.groovy @@ -41,7 +41,8 @@ class ScmPropertiesBuilder { true, ['main', 'master'] as Set, false, - true + true, + false ) }