Skip to content

Commit

Permalink
Merge pull request #75 from simonhauck/69-check-for-pre-release-versi…
Browse files Browse the repository at this point in the history
…ons-before-the-release

#69 add task to check for snapshot versions
  • Loading branch information
simonhauck authored Aug 31, 2024
2 parents 4be6662 + 167d936 commit 19f1cc2
Show file tree
Hide file tree
Showing 10 changed files with 661 additions and 194 deletions.
235 changes: 105 additions & 130 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,62 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider

abstract class ReleaseExtension(
objects: ObjectFactory,
layout: ProjectLayout,
private val objects: ObjectFactory,
private val layout: ProjectLayout,
) {
val rootGitDirectory: RegularFileProperty =
objects.fileProperty().convention(layout.projectDirectory.file("./"))
// Project properties
val rootGitDirectory: RegularFileProperty = projectFileProperty("./")
val versionPropertyFile: RegularFileProperty = projectFileProperty("version.properties")

val versionPropertyFile: RegularFileProperty =
objects.fileProperty().convention(layout.projectDirectory.file("version.properties"))
// Check for snapshot / pre-release versions
val checkForPreReleaseVersions: Property<Boolean> = booleanProperty(true)
val ignorePreReleaseDependenciesFile: RegularFileProperty = fileProperty()
val ignorePreReleaseDependencies: ListProperty<String> = stringListProperty()

val releaseCommitAddFiles: ListProperty<File> =
objects
.listProperty(File::class.java)
.convention(versionPropertyFile.map { listOf(it.asFile) })
// Check for uncommited files
val checkForUncommittedFiles: Property<Boolean> = booleanProperty(true)

val releaseCommitMessage: Property<String> =
objects.property(String::class.java).convention("Release commit: v{version}")
// Git config
val gitName: Property<String> = stringProperty()
val gitEmail: Property<String> = stringProperty()
val sshKeyFile: RegularFileProperty = fileProperty()
val commitMessagePrefix: Property<String> = stringProperty("")
val disablePush: Property<Boolean> = booleanProperty(false)

val postReleaseCommitAddFiles: ListProperty<File> =
objects
.listProperty(File::class.java)
.convention(versionPropertyFile.map { listOf(it.asFile) })
// Release commit
val releaseCommitAddFiles: ListProperty<File> =
fileListProperty(versionPropertyFile.map { listOf(it.asFile) })
val tagName: Property<String> = stringProperty("v{version}")
val releaseCommitMessage: Property<String> = stringProperty("Release commit: v{version}")

val postReleaseCommitMessage: Property<String> =
objects.property(String::class.java).convention("Post release commit: v{version}")
// Post release commit
val postReleaseCommitAddFiles: ListProperty<File> =
fileListProperty(versionPropertyFile.map { listOf(it.asFile) })
val postReleaseCommitMessage = stringProperty("Post release commit: v{version}")
val delayBeforePush: Property<Duration> = durationProperty(Duration.ZERO)

val commitMessagePrefix: Property<String> = objects.property(String::class.java).convention("")
// Utility methods

val tagName: Property<String> = objects.property(String::class.java).convention("v{version}")
private fun stringProperty(default: String? = null): Property<String> =
objects.property(String::class.java).convention(default)

val gitName: Property<String> = objects.property(String::class.java)
private fun stringListProperty(default: List<String> = emptyList()): ListProperty<String> =
objects.listProperty(String::class.java).convention(default)

val gitEmail: Property<String> = objects.property(String::class.java)
private fun projectFileProperty(default: String?): RegularFileProperty =
objects.fileProperty().convention(default?.let { layout.projectDirectory.file(it) })

val sshKeyFile: RegularFileProperty = objects.fileProperty()
private fun fileProperty(): RegularFileProperty = objects.fileProperty()

val disablePush: Property<Boolean> = objects.property(Boolean::class.java).convention(false)
private fun booleanProperty(default: Boolean? = null): Property<Boolean> =
objects.property(Boolean::class.java).convention(default)

val delayBeforePush: Property<Duration> =
objects.property(Duration::class.java).convention(Duration.ZERO)
private fun fileListProperty(default: Provider<List<File>>): ListProperty<File> =
objects.listProperty(File::class.java).convention(default)

val checkForUncommittedFiles: Property<Boolean> =
objects.property(Boolean::class.java).convention(true)
private fun durationProperty(default: Duration? = null): Property<Duration> =
objects.property(Duration::class.java).convention(default)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,32 @@ class ReleasePlugin : Plugin<Project> {
val releaseVersionStore =
project.layout.buildDirectory.file("release/tmpVersion.properties")

val projectDependencies = project.getDependenciesAsProvider()

val checkForPreReleaseVersionsTask =
project.tasks.register(
"checkForPreReleaseVersions",
CheckForPreReleaseDependenciesTask::class.java,
) {
it.onlyIf { extension.checkForPreReleaseVersions.get() }
it.ignorePreReleaseDependencies.set(extension.ignorePreReleaseDependencies)
it.ignorePreReleaseDependenciesFile.set(extension.ignorePreReleaseDependenciesFile)
it.usedDependencies.set(projectDependencies)
}

val calculateReleaseVersionTask =
project.registerCalculateReleaseVersionTask(releaseVersionStore, extension)
project.registerCalculateReleaseVersionTask(
releaseVersionStore,
extension,
checkForPreReleaseVersionsTask,
)

val writeReleaseVersionTask =
project.registerWriteReleaseVersionTask(
calculateReleaseVersionTask, releaseVersionStore, extension)
calculateReleaseVersionTask,
releaseVersionStore,
extension,
)

val commitReleaseVersion =
project.registerReleaseCommitTask(writeReleaseVersionTask, extension)
Expand All @@ -37,7 +57,8 @@ class ReleasePlugin : Plugin<Project> {
project.registerPushTask(
"pushRelease",
extension,
listOf(commitReleaseVersion, checkForUncommittedFilesTask))
listOf(commitReleaseVersion, checkForUncommittedFilesTask),
)

val writePostReleaseVersionTask =
project.tasks.register("writePostReleaseVersion", WriteVersionTask::class.java) {
Expand All @@ -55,17 +76,25 @@ class ReleasePlugin : Plugin<Project> {
"pushPostRelease",
extension,
listOf(commitPostReleaseVersionTask),
extension.delayBeforePush)
extension.delayBeforePush,
)

project.tasks.register("release", BaseReleaseTask::class.java) {
it.description = "Release the current version"
it.dependsOn(pushPostRelease)
}
}

private fun Project.getDependenciesAsProvider(): Provider<List<String>> = provider {
configurations
.flatMap { configuration -> configuration.dependencies }
.filter { it.group != null && it.version != null }
.map { "${it.group}:${it.name}:${it.version}" }
}

private fun Project.checkForUncommittedFiles(
extension: ReleaseExtension,
taskDependencies: TaskProvider<*>
taskDependencies: TaskProvider<*>,
): TaskProvider<CheckForUncommittedFilesTask> {
return tasks.register(
"checkForUncommittedFiles",
Expand All @@ -80,7 +109,7 @@ class ReleasePlugin : Plugin<Project> {
name: String,
extension: ReleaseExtension,
dependsOn: List<TaskProvider<*>>,
delay: Property<Duration> = objects.property(Duration::class.java).value(Duration.ZERO)
delay: Property<Duration> = objects.property(Duration::class.java).value(Duration.ZERO),
): TaskProvider<PushTask> =
tasks.register(name, PushTask::class.java) {
it.dependsOn(dependsOn)
Expand Down Expand Up @@ -118,7 +147,7 @@ class ReleasePlugin : Plugin<Project> {
private fun Project.registerWriteReleaseVersionTask(
calculateReleaseVersionTask: TaskProvider<CalculateReleaseVersionTask>,
releaseVersionStore: Provider<RegularFile>,
extension: ReleaseExtension
extension: ReleaseExtension,
): TaskProvider<WriteVersionTask> =
tasks.register("writeReleaseVersion", WriteVersionTask::class.java) {
it.dependsOn(calculateReleaseVersionTask)
Expand All @@ -129,19 +158,19 @@ class ReleasePlugin : Plugin<Project> {

private fun Project.registerCalculateReleaseVersionTask(
releaseVersionStore: Provider<RegularFile>,
extension: ReleaseExtension
extension: ReleaseExtension,
preCheckTask: TaskProvider<*>,
): TaskProvider<CalculateReleaseVersionTask> =
tasks.register("calculateReleaseVersion", CalculateReleaseVersionTask::class.java) {
val stringMap = properties.map { (key, value) -> key to value.toString() }.toMap()
it.commandLineParameters.set(stringMap)
it.releaseVersionStorePath.set(releaseVersionStore.get().asFile)
it.versionPropertyFile.set(extension.versionPropertyFile)
it.releaseVersionStore.set(releaseVersionStore)
it.dependsOn(preCheckTask)
}

private fun Project.configureGitTasks(
extension: ReleaseExtension,
) {
private fun Project.configureGitTasks(extension: ReleaseExtension) {
val commandHistoryService = project.registerGitHistoryService()

tasks.withType(GitTask::class.java) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.github.simonhauck.release.tasks

import org.gradle.api.GradleException
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.Logging
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction

abstract class CheckForPreReleaseDependenciesTask : BaseReleaseTask() {

private val log = Logging.getLogger(CheckForPreReleaseDependenciesTask::class.java)

@get:InputFile @get:Optional abstract val ignorePreReleaseDependenciesFile: RegularFileProperty
@get:Input @get:Optional abstract val ignorePreReleaseDependencies: ListProperty<String>

@get:Input abstract val usedDependencies: SetProperty<String>

init {
description =
"Checks that no pre-release dependencies are used to ensure reproducible builds."
outputs.upToDateWhen { true }
}

@TaskAction
fun action() {
val ignoredDependencies = combineIgnoredDependencies().toSet()

val preReleaseDependencies =
usedDependencies.get().filter { isPreReleaseVersion(it) }.sorted()

val notAllowedDependencies =
preReleaseDependencies.filterNot { it.startsWithAnyOf(ignoredDependencies) }

if (notAllowedDependencies.isEmpty()) return

val errorMessage = buildErrorMessage(notAllowedDependencies)
throw GradleException(errorMessage)
}

private fun isPreReleaseVersion(it: String): Boolean {
val split = it.split(":")
if (split.size > 3)
throw GradleException(
"Something went wrong. Expected a dependency in form of group:name:version but got $it")

if (split.size < 3) return false
val version = split[2]

val allChecks =
listOf(
version.contains("alpha", ignoreCase = true),
version.contains("beta", ignoreCase = true),
version.contains("beta", ignoreCase = true),
version.contains("rc", ignoreCase = true),
version.contains("pre", ignoreCase = true),
version.contains("snapshot", ignoreCase = true),
version.contains(".*[.-]M[0-9]+.*".toRegex(RegexOption.IGNORE_CASE)),
)

return allChecks.any { it }
}

private fun buildErrorMessage(notAllowedDependencies: List<String>): String {
val header =
"Found ${notAllowedDependencies.size} dependencies with a pre-release version that are not allowed\n"

val list = notAllowedDependencies.joinToString("\n") { " - $it" }

val hint = "\nChange the versions or add them to the ignore list."
return header + list + hint
}

private fun String.startsWithAnyOf(list: Set<String>): Boolean {
return list.any { this.startsWith(it) }
}

private fun combineIgnoredDependencies(): List<String> {
val ignoreFromFile =
ignorePreReleaseDependenciesFile.map { it.asFile.readLines() }.getOrElse(emptyList())

val ignoredFromList = ignorePreReleaseDependencies.getOrElse(emptyList())

return ignoreFromFile + ignoredFromList
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import org.gradle.api.tasks.TaskAction

abstract class CheckForUncommittedFilesTask : BaseReleaseTask(), GitTask {

init {
description = "Checks for uncommitted files in the repository"
}

@TaskAction
fun action() {
val historyApi = gitCommandHistoryApi.get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ abstract class PushTask : BaseReleaseTask(), GitTask {
@get:Input @get:Optional abstract val disablePush: Property<Boolean>
@get:Input @get:Optional abstract val delayBeforePush: Property<Duration>

init {
description = "Pushes the changes to the remote repository."
}

@TaskAction
fun push() {
if (disablePush.getOrElse(false)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal data class VersionInfo(
return parseVersionInfo(version).getOrElse { throw it }
}

private fun parseVersionInfo(version: Version): Either<GradleException, VersionInfo> =
internal fun parseVersionInfo(version: Version): Either<GradleException, VersionInfo> =
either {
val value = version.value
val regexString =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ internal class ReleasePluginCompatibilityTest {
val runner =
testKitRunner()
.withGradleVersion("6.1")
.withArguments("release", "-PreleaseType=major")
.withArguments("release", "-PreleaseType=major", "--stacktrace")
.build()

assertThat(runner.task(":release")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
Expand Down
Loading

0 comments on commit 19f1cc2

Please sign in to comment.