main.name.remal.gradle_plugins.plugins.vcs.AutoVcsVersionPlugin.kt Maven / Gradle / Ivy
package name.remal.gradle_plugins.plugins.vcs
import name.remal.KotlinAllOpen
import name.remal.asObservable
import name.remal.concurrentMapOf
import name.remal.default
import name.remal.gradle_plugins.dsl.ApplyPluginClasses
import name.remal.gradle_plugins.dsl.BaseReflectiveProjectPlugin
import name.remal.gradle_plugins.dsl.CreateExtensionsPluginAction
import name.remal.gradle_plugins.dsl.Extension
import name.remal.gradle_plugins.dsl.Plugin
import name.remal.gradle_plugins.dsl.PluginAction
import name.remal.gradle_plugins.dsl.WithPluginClasses
import name.remal.gradle_plugins.dsl.extensions.afterEvaluateOrNow
import name.remal.gradle_plugins.dsl.extensions.atTheEndOfAfterEvaluationOrNow
import name.remal.gradle_plugins.dsl.extensions.createWithAutoName
import name.remal.gradle_plugins.dsl.extensions.get
import name.remal.gradle_plugins.dsl.extensions.isPluginAppliedAndNotDisabled
import name.remal.gradle_plugins.dsl.extensions.setAllprojectsVersion
import name.remal.gradle_plugins.dsl.extensions.shouldRunAfter
import name.remal.gradle_plugins.dsl.plugins.DisplayProjectVersionPlugin
import name.remal.gradle_plugins.dsl.utils.getGradleLogger
import name.remal.gradle_plugins.plugins.ci.CommonCIPlugin
import name.remal.max
import name.remal.version.Version
import name.remal.version.VersionParsingException
import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionContainer
import org.gradle.api.tasks.TaskContainer
import java.io.File
import java.time.LocalDateTime
import java.time.ZoneOffset.UTC
import kotlin.text.RegexOption.IGNORE_CASE
@Plugin(
id = "name.remal.vcs-auto-version",
description = "Plugin that sets project version based on VCS history",
tags = ["vcs"]
)
@ApplyPluginClasses(VcsOperationsPlugin::class, DisplayProjectVersionPlugin::class)
class AutoVcsVersionPlugin : BaseReflectiveProjectPlugin() {
@CreateExtensionsPluginAction
fun ExtensionContainer.`Create 'autoVcsVersion' extension`(project: Project) {
create("autoVcsVersion", AutoVcsVersionExtension::class.java, project)
}
@PluginAction(order = -10000)
fun TaskContainer.`Create 'writeAutoVcsVersionInFile' task`() {
create("writeAutoVcsVersionInFile", WriteAutoVcsVersionInFileTask::class.java)
}
@PluginAction(isHidden = false, order = -100)
fun Project.calculateAutoVcsVersion() {
calculateAutoVcsVersion(false)
}
@PluginAction
fun TaskContainer.`Create 'createAutoVcsVersionTag' task`() {
create("createAutoVcsVersionTag", CreateProjectVersionTag::class.java) { task ->
task.project.afterEvaluateOrNow(Int.MAX_VALUE) { project ->
val autoVcsVersion = project[AutoVcsVersionExtension::class.java]
task.tagNamePrefix = autoVcsVersion.versionTagPrefixes.firstOrNull().default()
}
task.shouldRunAfter {
task.project.rootProject.allprojects.asSequence()
.map(Project::getTasks)
.filterIsInstance(BaseCreateTagTask::class.java)
.filter { it != task }
.toList()
}
}
}
@PluginAction(order = Int.MAX_VALUE)
@WithPluginClasses(CommonCIPlugin::class)
fun Project.`Log current auto VCS version info`() {
atTheEndOfAfterEvaluationOrNow(Int.MAX_VALUE) { _ ->
val autoVcsVersion = this[AutoVcsVersionExtension::class.java]
val vcsOperations = this[VcsOperationsExtension::class.java]
autoVcsVersion.currentMutable?.let {
logger.lifecycle("VCS: current commit: {}", vcsOperations.getCurrentCommit())
logger.lifecycle("VCS: current branch: {}", vcsOperations.currentBranch)
logger.lifecycle("VCS: the latest base VCS version: {}", it.latestBaseVersion)
logger.lifecycle("VCS: the latest VCS version: {}", it.latestVersion)
if (it.useLatestVersion) {
logger.lifecycle("VCS: the latest VCS version should be used as a current version")
} else if (autoVcsVersion.incrementVersionByCommitsCount && it.commitsAfterLatestVersion >= 1) {
logger.lifecycle("VCS: commits after the latest VCS version: {}", it.commitsAfterLatestVersion)
}
if (it.increment >= 1) {
logger.lifecycle("VCS: increment VCS version by: {}", it.increment)
}
logger.lifecycle("VCS: calculated VCS version: {}", it.version)
}
}
}
}
@Extension
class AutoVcsVersionExtension(private val project: Project) {
private var isCalculationEnabled: Boolean = true
private inline fun changeSettings(action: () -> Unit) {
isCalculationEnabled = false
try {
action()
project.calculateAutoVcsVersion()
} finally {
isCalculationEnabled = true
}
}
private fun Project.calculateAutoVcsVersion() {
if (isCalculationEnabled) {
calculateAutoVcsVersion(true)
}
}
val versionTagPrefixes: MutableList = mutableListOf().asObservable().apply {
addAll(listOf("ver-", "version-"))
registerElementAddedHandler { project.calculateAutoVcsVersion() }
}
fun versionTagPrefix(versionTagPrefix: String) = changeSettings {
versionTagPrefixes.add(versionTagPrefix)
}
fun versionTagPrefixes(versionTagPrefixes: Iterable) = changeSettings {
this.versionTagPrefixes.addAll(versionTagPrefixes)
}
fun versionTagPrefixes(vararg versionTagPrefixes: String) = changeSettings {
this.versionTagPrefixes.addAll(versionTagPrefixes)
}
fun setVersionTagPrefixes(versionTagPrefixes: Collection) = changeSettings {
if (versionTagPrefixes.isEmpty()) throw IllegalArgumentException("versionTagPrefixes can't be empty")
this.versionTagPrefixes.apply {
clear()
addAll(versionTagPrefixes)
}
}
fun setVersionTagPrefixes(vararg versionTagPrefixes: String) = setVersionTagPrefixes(versionTagPrefixes.toList())
val baseVersionTagPrefixes: MutableList = mutableListOf().asObservable().apply {
addAll(listOf("ver-base-", "version-base-"))
registerElementAddedHandler { project.calculateAutoVcsVersion() }
}
fun baseVersionTagPrefix(baseVersionTagPrefix: String) = changeSettings {
baseVersionTagPrefixes.add(baseVersionTagPrefix)
}
fun baseVersionTagPrefixes(baseVersionTagPrefixes: Iterable) = changeSettings {
this.baseVersionTagPrefixes.addAll(versionTagPrefixes)
}
fun baseVersionTagPrefixes(vararg baseVersionTagPrefixes: String) = changeSettings {
this.baseVersionTagPrefixes.addAll(baseVersionTagPrefixes)
}
fun setBaseVersionTagPrefixes(baseVersionTagPrefixes: Collection) = changeSettings {
if (baseVersionTagPrefixes.isEmpty()) throw IllegalArgumentException("baseVersionTagPrefixes can't be empty")
this.baseVersionTagPrefixes.apply {
clear()
addAll(baseVersionTagPrefixes)
}
}
fun setBaseVersionTagPrefixes(vararg baseVersionTagPrefixes: String) = setBaseVersionTagPrefixes(baseVersionTagPrefixes.toList())
var incrementVersionByCommitsCount: Boolean = false
set(value) {
if (field != value) {
field = value
project.calculateAutoVcsVersion()
}
}
var incrementVersionBy: Int = 0
set(value) {
if (field != value) {
field = value
project.calculateAutoVcsVersion()
}
}
var useLatestVersion: Boolean = false
set(value) {
if (field != value) {
field = value
project.calculateAutoVcsVersion()
}
}
internal var currentMutable: CurrentAutoVcsVersionInfo? = null
val current: CurrentAutoVcsVersionInfo
get() {
currentMutable?.let { return it }
currentMutable = CurrentAutoVcsVersionInfo()
project.calculateAutoVcsVersion()
return currentMutable!!
}
}
class CurrentAutoVcsVersionInfo {
var version: Version = Version.create(0, 0, 0)
internal set
var latestVersion: Version = Version.create(0, 0, 0)
internal set
var commitsAfterLatestVersion: Int = 0
internal set
var increment: Int = 0
internal set
var latestBaseVersion: Version? = null
internal set
var useLatestVersion: Boolean = false
internal set
}
fun Project.calculateAutoVcsVersion() = calculateAutoVcsVersion(true)
private fun Project.calculateAutoVcsVersion(checkIfAutoVcsVersionPluginApplied: Boolean) {
if (!checkIfAutoVcsVersionPluginApplied || isPluginAppliedAndNotDisabled(AutoVcsVersionPlugin::class.java)) {
this.extensions
.let { it.findByType(AutoVcsVersionChanger::class.java) ?: it.createWithAutoName(AutoVcsVersionChanger::class.java, this) }
.invoke()
}
}
@KotlinAllOpen
class AutoVcsVersionChanger(private val project: Project) : () -> Unit {
companion object {
private val logger = getGradleLogger(AutoVcsVersionChanger::class.java)
private val REPEATED_SNAPSHOT_REGEX = Regex("-SNAPSHOT(.*-SNAPSHOT)", IGNORE_CASE)
}
private val autoVcsVersion = project[AutoVcsVersionExtension::class.java]
private val vcsOperations = project[VcsOperationsExtension::class.java]
private val latestVersionTagWithDepthCache: MutableMap>, TagsWithDepth> = concurrentMapOf()
private val latestBaseVersionTagNameCache: MutableMap>, Set> = concurrentMapOf()
@Suppress("ComplexMethod", "LongMethod")
override fun invoke() {
val vcsCacheKey = vcsOperations.calculateCacheKey()
val versionTagPrefixes = autoVcsVersion.versionTagPrefixes.sortedDescending()
val latestVersionTagWithDepth = latestVersionTagWithDepthCache.computeIfAbsent(Pair(vcsCacheKey, versionTagPrefixes)) {
vcsOperations.findTagWithDepth { tagName -> it.second.any { tagName.startsWith(it) && null != Version.parseOrNull(tagName.substring(it.length)) } }
?: TagsWithDepth(emptySet(), 0)
}
val latestVersion: Version = latestVersionTagWithDepth.tagNames.stream()
.map { tagName ->
val prefix = versionTagPrefixes.first { tagName.startsWith(it) && null != Version.parseOrNull(tagName.substring(it.length)) }
return@map Version.parse(tagName.substring(prefix.length)).withoutSuffix()
}
.max()
?: Version.create(0, 0, 0)
val commitsAfterLatestVersion = latestVersionTagWithDepth.depth
val baseVersionTagPrefixes = autoVcsVersion.baseVersionTagPrefixes.sortedDescending()
val latestBaseVersionTagNames = latestBaseVersionTagNameCache.computeIfAbsent(Pair(vcsCacheKey, baseVersionTagPrefixes)) {
vcsOperations.getAllTagNames().filterTo(mutableSetOf()) { tagName -> it.second.any { tagName.startsWith(it) && null != Version.parseOrNull(tagName.substring(it.length)) } }
}
val latestBaseVersion: Version?
if (latestBaseVersionTagNames.isNotEmpty()) {
latestBaseVersion = latestBaseVersionTagNames.stream()
.map { tagName ->
val prefix = baseVersionTagPrefixes.first { tagName.startsWith(it) && null != Version.parseOrNull(tagName.substring(it.length)) }
return@map Version.parse(tagName.substring(prefix.length)).withoutSuffix()
}
.max()
} else {
latestBaseVersion = null
}
val useLatestVersion = autoVcsVersion.useLatestVersion
val incrementVersionByCommitsCount = autoVcsVersion.incrementVersionByCommitsCount
var version: Version = run {
if (latestBaseVersion == null || latestBaseVersion <= latestVersion) {
return@run latestVersion.incrementNumber(
latestVersion.numbersCount - 1,
commitsAfterLatestVersion.let { commits ->
if (useLatestVersion) {
0
} else if (commits <= 1 || incrementVersionByCommitsCount) {
commits
} else {
1
}
}
)
} else {
return@run latestBaseVersion.withNumber(latestBaseVersion.numbersCount, 0)
}
}
val incrementVersionBy = autoVcsVersion.incrementVersionBy
if (incrementVersionBy > 0) {
version = version.incrementNumber(latestVersion.numbersCount - 1, incrementVersionBy)
}
var isSnapshot = false
vcsOperations.currentBranch?.let { currentBranch ->
if (vcsOperations.masterBranch != currentBranch) {
isSnapshot = true
val branchSlug = VcsOperations.createBranchSlug(currentBranch)
if (autoVcsVersion.incrementVersionByCommitsCount) {
val time = LocalDateTime.now().toEpochSecond(UTC)
version = version.appendSuffix("$branchSlug-$time", "-")
} else {
version = version.appendSuffix(branchSlug, "-")
}
}
}
if (isSnapshot) {
version = version.appendSuffix("SNAPSHOT", "-")
}
while (REPEATED_SNAPSHOT_REGEX.containsMatchIn(version.suffix)) {
version = version.withSuffix(REPEATED_SNAPSHOT_REGEX.replace(version.suffix, "\$1"))
}
project.rootProject.allprojects.asSequence()
.flatMap { it.tasks.asSequence() }
.filterIsInstance(WriteAutoVcsVersionInFileTask::class.java)
.map(WriteAutoVcsVersionInFileTask::file)
.map(File::getAbsoluteFile)
.filter(File::isFile)
.firstOrNull()
?.let { versionFile ->
val versionInFile = try {
Version.parse(versionFile.readText().trim())
} catch (e: VersionParsingException) {
logger.warn(e.toString())
return@let
}
logger.lifecycle("Version '{}' is read from file: {}", versionInFile, versionFile)
version = versionInFile
}
project[AutoVcsVersionExtension::class.java].current.also {
it.version = version
it.latestVersion = latestVersion
it.commitsAfterLatestVersion = commitsAfterLatestVersion
it.increment = incrementVersionBy
it.latestBaseVersion = latestBaseVersion
it.useLatestVersion = useLatestVersion
}
project.setAllprojectsVersion(version.toString())
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy