Skip to content

Commit 4d84dcd

Browse files
authored
Merge pull request #83 from simonhauck/16-support-alpha-beta-and-rc-releases-for-automatic-version-release
#16 Add support for releases with a pre-release type
2 parents 205012e + 7d754bb commit 4d84dcd

23 files changed

+639
-193
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,19 @@ You can set the release and post-release version explicitly with the following g
6868
Alternately, if you have a [semver](https://semver.org/) compatible version you can use the simplified API:
6969

7070
```shell
71+
# Current version: 1.0.0, release-type=major -> Release Version: 2.0.0, Post Release Version: 2.0.1-SNAPSHOT
7172
./gradlew release -PreleaseType=<release-type>
73+
74+
# Or
75+
76+
# Current version: 1.0.0, release-type=major, preReleaseType=RC -> Release Version: 2.0.0-RC1, Post Release Version: 1.0.0
77+
./gradlew release -PreleaseType=<release-type> -PpreReleaseType=<pre-release-type>
7278
```
7379

7480
Replace the _release-type_ with ``major``, ``minor`` or ``patch``. This will determine the version automatically.
81+
The _pre-release-type_ is an optional string (e.g. ALPHA). If set, the plugin will automatically apply a counter based
82+
on the existing git tags and set the post release version back to the current project version, so when you create the "
83+
final" release, you can select the same release type - just without the _pre-release-type_.
7584

7685
### What does the plugin do?
7786

release-plugin/src/main/kotlin/io/github/simonhauck/release/git/api/GitCommandApi.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ interface GitCommandApi {
3838

3939
fun tag(tagName: String, tagMessage: String): GitVoidResult
4040

41+
fun fetchRemoteTags(): GitVoidResult
42+
4143
fun listTags(): GitResult<List<String>>
4244

4345
fun deleteLocalTag(tagName: String): GitVoidResult

release-plugin/src/main/kotlin/io/github/simonhauck/release/git/api/GitCommandResult.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.simonhauck.release.git.api
22

33
import arrow.core.Either
4+
import org.gradle.api.GradleException
45

56
typealias GitVoidResult = Either<GitError, GitOk>
67

@@ -29,4 +30,11 @@ data class GitStatusResult(
2930
fun notEmpty() = !allEmpty()
3031
}
3132

32-
fun Either<GitError, *>.isOk(): Boolean = isRight()
33+
internal fun Either<GitError, *>.isOk(): Boolean = isRight()
34+
35+
internal fun <T> GitResult<T>.getOrThrowGradleException(): T {
36+
return when (this) {
37+
is Either.Left -> throw GradleException(this.value.message, this.value.throwable)
38+
is Either.Right -> this.value
39+
}
40+
}

release-plugin/src/main/kotlin/io/github/simonhauck/release/git/internal/commands/GitCommandProcessWrapper.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ internal class GitCommandProcessWrapper(
134134
return gitVoidCommand(listOf("tag", "-d", tagName))
135135
}
136136

137+
override fun fetchRemoteTags(): GitVoidResult {
138+
return gitVoidCommand(listOf("fetch", "--tags"))
139+
}
140+
137141
override fun listTags(): GitResult<List<String>> {
138142
return gitCommand(listOf("tag")).map { processSuccess ->
139143
processSuccess.output.map { it.trim() }

release-plugin/src/main/kotlin/io/github/simonhauck/release/plugin/ReleasePlugin.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class ReleasePlugin : Plugin<Project> {
164164
tasks.register("calculateReleaseVersion", CalculateReleaseVersionTask::class.java) {
165165
val stringMap = properties.map { (key, value) -> key to value.toString() }.toMap()
166166
it.commandLineParameters.set(stringMap)
167-
it.releaseVersionStorePath.set(releaseVersionStore.get().asFile)
167+
it.releaseTagName.set(extension.tagName)
168168
it.versionPropertyFile.set(extension.versionPropertyFile)
169169
it.releaseVersionStore.set(releaseVersionStore)
170170
it.dependsOn(preCheckTask)

release-plugin/src/main/kotlin/io/github/simonhauck/release/tasks/CalculateReleaseVersionTask.kt

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,25 @@ import org.gradle.api.file.RegularFileProperty
99
import org.gradle.api.logging.Logging
1010
import org.gradle.api.provider.MapProperty
1111
import org.gradle.api.provider.Property
12-
import org.gradle.api.tasks.Input
13-
import org.gradle.api.tasks.InputFile
14-
import org.gradle.api.tasks.OutputFile
15-
import org.gradle.api.tasks.TaskAction
12+
import org.gradle.api.tasks.*
1613

17-
abstract class CalculateReleaseVersionTask : BaseReleaseTask() {
14+
abstract class CalculateReleaseVersionTask : BaseReleaseTask(), GitTask {
1815

1916
private val log = Logging.getLogger(CalculateReleaseVersionTask::class.java)
2017

2118
init {
2219
description = "Calculate the release version and the next development version"
20+
// Run this task always, because the git repository could have changed, (e.g. the tags)
21+
outputs.upToDateWhen { false }
2322
}
2423

2524
@get:InputFile abstract val versionPropertyFile: RegularFileProperty
26-
@get:Input abstract val releaseVersionStorePath: Property<File>
25+
@get:Deprecated("Use releaseVersionStore instead")
26+
@get:Input
27+
@get:Optional
28+
abstract val releaseVersionStorePath: Property<File>
2729
@get:Input abstract val commandLineParameters: MapProperty<String, String>
30+
@get:Input abstract val releaseTagName: Property<String>
2831

2932
@get:OutputFile abstract val releaseVersionStore: RegularFileProperty
3033

@@ -41,6 +44,6 @@ abstract class CalculateReleaseVersionTask : BaseReleaseTask() {
4144
}
4245

4346
private fun getReleaseVersions(currentVersion: Version): ReleaseVersions =
44-
VersionIncrementStrategyParserApi.create()
47+
VersionIncrementStrategyParserApi.create(gitCommandApi(), releaseTagName.get())
4548
.parseOrThrow(currentVersion, commandLineParameters.get())
4649
}

release-plugin/src/main/kotlin/io/github/simonhauck/release/tasks/CheckForUncommittedFilesTask.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.simonhauck.release.tasks
22

33
import io.github.simonhauck.release.git.api.GitStatusResult
4+
import io.github.simonhauck.release.git.api.getOrThrowGradleException
45
import org.gradle.api.GradleException
56
import org.gradle.api.tasks.TaskAction
67

release-plugin/src/main/kotlin/io/github/simonhauck/release/tasks/CommitAndTagTask.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.github.simonhauck.release.tasks
33
import arrow.core.fold
44
import io.github.simonhauck.release.file.internal.PropertiesFileUtil
55
import io.github.simonhauck.release.git.api.RevertCommand
6+
import io.github.simonhauck.release.git.api.getOrThrowGradleException
67
import java.io.File
78
import org.gradle.api.file.RegularFileProperty
89
import org.gradle.api.provider.ListProperty

release-plugin/src/main/kotlin/io/github/simonhauck/release/tasks/GitTask.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package io.github.simonhauck.release.tasks
33
import arrow.core.Either
44
import io.github.simonhauck.release.git.api.*
55
import java.io.File
6-
import org.gradle.api.GradleException
76
import org.gradle.api.Task
87
import org.gradle.api.file.RegularFileProperty
98
import org.gradle.api.provider.Property
@@ -37,11 +36,4 @@ interface GitTask : Task {
3736
): Either<GitError, T> {
3837
return onRight { gitCommandHistoryApi.get().registerRevertCommand(revertCommand) }
3938
}
40-
41-
fun <T> GitResult<T>.getOrThrowGradleException(): T {
42-
return when (this) {
43-
is Either.Left -> throw GradleException(this.value.message, this.value.throwable)
44-
is Either.Right -> this.value
45-
}
46-
}
4739
}

release-plugin/src/main/kotlin/io/github/simonhauck/release/tasks/PushTask.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.simonhauck.release.tasks
22

33
import io.github.simonhauck.release.git.api.RevertCommand
4+
import io.github.simonhauck.release.git.api.getOrThrowGradleException
45
import java.time.Duration
56
import org.gradle.api.logging.Logging
67
import org.gradle.api.provider.Property
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package io.github.simonhauck.release.version.api
22

3+
import io.github.simonhauck.release.git.api.GitCommandApi
34
import io.github.simonhauck.release.version.internal.VersionIncrementStrategyParser
45

56
interface VersionIncrementStrategyParserApi {
67

78
fun parseOrThrow(currentVersion: Version, parameters: Map<String, String>): ReleaseVersions
89

910
companion object {
10-
fun create(): VersionIncrementStrategyParserApi = VersionIncrementStrategyParser()
11+
fun create(
12+
gitCommandApi: GitCommandApi,
13+
releaseTagTemplate: String,
14+
): VersionIncrementStrategyParserApi =
15+
VersionIncrementStrategyParser(gitCommandApi, releaseTagTemplate)
1116
}
1217
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.github.simonhauck.release.version.internal
2+
3+
import io.github.simonhauck.release.version.api.ReleaseVersions
4+
import io.github.simonhauck.release.version.api.Version
5+
6+
internal class ManualVersionSelectionStrategy() : VersionIncrementStrategy {
7+
override val strategyName: String = "Manual Version Selection"
8+
9+
override val requiredPropertyDescription =
10+
listOf(
11+
"$RELEASE_VERSION_KEY - The version to release",
12+
"$POST_RELEASE_VERSION_KEY - The version after the release",
13+
)
14+
15+
override fun tryParse(
16+
currentVersion: Version,
17+
parameters: Map<String, String>,
18+
): ReleaseVersions? {
19+
val releaseVersion = parameters[RELEASE_VERSION_KEY]
20+
val postReleaseVersion = parameters[POST_RELEASE_VERSION_KEY]
21+
22+
if (releaseVersion == null || postReleaseVersion == null) {
23+
return null
24+
}
25+
26+
return ReleaseVersions(Version(releaseVersion), Version(postReleaseVersion))
27+
}
28+
29+
companion object {
30+
private const val RELEASE_VERSION_KEY = "releaseVersion"
31+
private const val POST_RELEASE_VERSION_KEY = "postReleaseVersion"
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package io.github.simonhauck.release.version.internal
2+
3+
import arrow.core.Either
4+
import arrow.core.flatten
5+
import io.github.simonhauck.release.git.api.GitCommandApi
6+
import io.github.simonhauck.release.git.api.GitError
7+
import io.github.simonhauck.release.version.api.ReleaseVersions
8+
import io.github.simonhauck.release.version.api.Version
9+
import org.gradle.api.logging.Logging
10+
11+
internal class ReleaseTypeSelectionStrategy(
12+
private val gitCommandApi: GitCommandApi,
13+
private val releaseTagName: String,
14+
) : VersionIncrementStrategy {
15+
16+
private val log = Logging.getLogger(ReleaseTypeSelectionStrategy::class.java)
17+
18+
override val strategyName: String
19+
get() = "ReleaseType selection"
20+
21+
override val requiredPropertyDescription: List<String>
22+
get() =
23+
listOf(
24+
"$RELEASE_TYPE - The type of release ($MAJOR_KEY, $MINOR_KEY, $PATCH_KEY)",
25+
"$PRE_RELEASE_TYPE - (Optional) Type of pre-release (e.g. alpha, RC, beta, ...). A counter will be automatically applied.",
26+
)
27+
28+
override fun tryParse(
29+
currentVersion: Version,
30+
parameters: Map<String, String>,
31+
): ReleaseVersions? {
32+
val releaseType = parameters[RELEASE_TYPE] ?: return null
33+
val preReleaseType = parameters[PRE_RELEASE_TYPE]
34+
35+
val currentVersionInfo = VersionInfo.fromVersion(currentVersion)
36+
37+
val releaseVersion =
38+
when (releaseType) {
39+
"major" -> currentVersionInfo.bumpMajor(preReleaseType)
40+
"minor" -> currentVersionInfo.bumpMinor(preReleaseType)
41+
"patch" ->
42+
if (currentVersionInfo.isPreRelease()) currentVersionInfo.dropPreReleaseSuffix()
43+
else currentVersionInfo.bumpPatch(preReleaseType)
44+
else -> return null
45+
}.applyPreReleaseTypeIfAvailable(preReleaseType)
46+
47+
// If the next version is a pre-release, the post-release version is reverted so that the
48+
// next release can be triggered with the same level again
49+
val postReleaseVersion =
50+
if (preReleaseType == null) releaseVersion.bumpPatch("SNAPSHOT") else currentVersionInfo
51+
52+
return ReleaseVersions(releaseVersion.toVersion(), postReleaseVersion.toVersion())
53+
}
54+
55+
private fun VersionInfo.applyPreReleaseTypeIfAvailable(preReleaseSuffix: String?): VersionInfo {
56+
if (preReleaseSuffix == null) return this
57+
58+
val tags =
59+
gitCommandApi
60+
.fetchRemoteTags()
61+
.map { gitCommandApi.listTags() }
62+
.flatten()
63+
.logErrorOnLeft()
64+
.getOrNull()
65+
?.toSet() ?: return this
66+
67+
return generateSequence(1) { it + 1 }
68+
.map { index -> this.copy(preReleaseSuffix = "$preReleaseSuffix$index") }
69+
.first { version -> !tags.containsVersion(version) }
70+
}
71+
72+
private fun Set<String>.containsVersion(version: VersionInfo): Boolean {
73+
val expectedReleaseTag = releaseTagName.replace("{version}", version.toVersion().value)
74+
return contains(expectedReleaseTag)
75+
}
76+
77+
private fun <B> Either<GitError, B>.logErrorOnLeft(): Either<GitError, B> = onLeft {
78+
log.info("Failed to get all local and remote tags - This will not fail the build")
79+
log.info(it.message)
80+
}
81+
82+
companion object {
83+
private const val RELEASE_TYPE = "releaseType"
84+
private const val PRE_RELEASE_TYPE = "preReleaseType"
85+
private const val MAJOR_KEY = "major"
86+
private const val MINOR_KEY = "minor"
87+
private const val PATCH_KEY = "patch"
88+
}
89+
}

release-plugin/src/main/kotlin/io/github/simonhauck/release/version/internal/VersionIncrementStrategy.kt

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -11,70 +11,3 @@ internal sealed interface VersionIncrementStrategy {
1111

1212
fun tryParse(currentVersion: Version, parameters: Map<String, String>): ReleaseVersions?
1313
}
14-
15-
internal class ManualVersionSelectionStrategy() : VersionIncrementStrategy {
16-
override val strategyName: String = "Manual Version Selection"
17-
18-
override val requiredPropertyDescription =
19-
listOf(
20-
"$RELEASE_VERSION_KEY - The version to release",
21-
"$POST_RELEASE_VERSION_KEY - The version after the release",
22-
)
23-
24-
override fun tryParse(
25-
currentVersion: Version,
26-
parameters: Map<String, String>,
27-
): ReleaseVersions? {
28-
val releaseVersion = parameters[RELEASE_VERSION_KEY]
29-
val postReleaseVersion = parameters[POST_RELEASE_VERSION_KEY]
30-
31-
if (releaseVersion == null || postReleaseVersion == null) {
32-
return null
33-
}
34-
35-
return ReleaseVersions(Version(releaseVersion), Version(postReleaseVersion))
36-
}
37-
38-
companion object {
39-
private const val RELEASE_VERSION_KEY = "releaseVersion"
40-
private const val POST_RELEASE_VERSION_KEY = "postReleaseVersion"
41-
}
42-
}
43-
44-
internal class ReleaseTypeSelectionStrategy : VersionIncrementStrategy {
45-
override val strategyName: String
46-
get() = "ReleaseType selection"
47-
48-
override val requiredPropertyDescription: List<String>
49-
get() = listOf("$RELEASE_TYPE - The type of release ($MAJOR_KEY, $MINOR_KEY, $PATCH_KEY)")
50-
51-
override fun tryParse(
52-
currentVersion: Version,
53-
parameters: Map<String, String>,
54-
): ReleaseVersions? {
55-
val releaseType = parameters[RELEASE_TYPE] ?: return null
56-
57-
val versionInfo = VersionInfo.fromVersion(currentVersion)
58-
59-
val releaseVersion =
60-
when (releaseType) {
61-
"major" -> versionInfo.bumpMajor()
62-
"minor" -> versionInfo.bumpMinor()
63-
"patch" ->
64-
if (versionInfo.isPreRelease()) versionInfo.bumpToRelease()
65-
else versionInfo.bumpPatch()
66-
else -> return null
67-
}
68-
69-
val postReleaseVersion = releaseVersion.bumpPatch("SNAPSHOT")
70-
71-
return ReleaseVersions(releaseVersion.toVersion(), postReleaseVersion.toVersion())
72-
}
73-
74-
companion object {
75-
private const val RELEASE_TYPE = "releaseType"
76-
private const val MAJOR_KEY = "major"
77-
private const val MINOR_KEY = "minor"
78-
private const val PATCH_KEY = "patch"
79-
}
80-
}

0 commit comments

Comments
 (0)