5 Commits
10 changed files with 387 additions and 147 deletions

No files matched your search

+26 -12
View File
@@ -1,27 +1,37 @@
import java.time.Instant import java.time.Instant
/*
* This file was generated by the Gradle 'init' task.
*
* This is a general purpose Gradle build.
* To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.6/samples
*/
plugins { plugins {
`kotlin-dsl` `kotlin-dsl`
`maven-publish` `maven-publish`
} }
repositories { repositories {
mavenCentral() maven("https://nexus.keinekekse.net/repository/maven-public/")
} }
group = "net.keinekekse" group = "net.keinekekse"
version = libs.versions.versionplugin.get() version = masterLibs.versions.versionPluginVersion.get()
description = "Adds version information to the project." description = "Adds version information to the project."
project.ext.set("displayname", "Version-Plugin") extra["displayname"] = "Version-Plugin"
project.ext.set("copyright", "(c) 2024 Hartmut Bauer") extra["copyright"] = "Copyright (c) 2024-2026 Hartmut Bauer"
project.ext.set("buildTimestamp", Instant.now().toString()) extra["buildTimestamp"] = Instant.now().toString()
dependencies {
implementation(masterLibs.semver)
compileOnly(masterLibs.kotlinGradlePlugin)
}
publishing {
repositories {
maven {
credentials {
username = "admin"
password = "admin123"
}
url = uri("https://nexus.keinekekse.net/repository/maven-releases/")
}
}
}
gradlePlugin { gradlePlugin {
plugins { plugins {
create("version-plugin") { create("version-plugin") {
@@ -31,8 +41,12 @@ gradlePlugin {
} }
} }
kotlin {
jvmToolchain(masterLibs.versions.javaVersion.get().toInt())
}
java { java {
toolchain { toolchain {
languageVersion.set(JavaLanguageVersion.of(17)) languageVersion.set(JavaLanguageVersion.of(masterLibs.versions.javaVersion.get()))
} }
} }
+1
View File
@@ -0,0 +1 @@
kotlin.code.style=official
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
Vendored
+7 -8
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright © 2015 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -112,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -170,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -203,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped. # and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line. # treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.
Vendored
+3 -2
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -68,11 +70,10 @@ goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
+6 -9
View File
@@ -1,16 +1,13 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
* For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.6/userguide/multi_project_builds.html in the Gradle documentation.
*/
rootProject.name = "version-plugin" rootProject.name = "version-plugin"
dependencyResolutionManagement { dependencyResolutionManagement {
repositories {
maven("https://nexus.keinekekse.net/repository/maven-public/")
}
versionCatalogs { versionCatalogs {
create("libs") { create("masterLibs") {
from(files("../../gradle-deps/libs.versions.toml")) from("net.keinekekse:version-catalog:141")
} }
} }
} }
@@ -1,12 +1,191 @@
package net.keinekekse.version package net.keinekekse.version
import org.gradle.api.tasks.Copy import io.github.z4kn4fein.semver.Version
import org.gradle.api.tasks.TaskAction import io.github.z4kn4fein.semver.withoutSuffixes
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import java.util.*
abstract class AddVersionTask : Copy() { @CacheableTask
abstract class AddVersionTask : DefaultTask() {
// 1. Define what triggers the task (Source Files)
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val sourceFiles: ConfigurableFileCollection
// 2. Define the output (The properties file)
@get:OutputFile
abstract val buildNumberFile: RegularFileProperty
@get:OutputFile
abstract val versionClassFile: RegularFileProperty
@get:Input
abstract val projectVersion: Property<String>
@get:Input
abstract val projectName: Property<String>
@get:Input
abstract val projectDescription: Property<String>
@get:Input
abstract val displayNameExtra: Property<String>
@get:Input
abstract val copyrightExtra: Property<String>
@get:Input
abstract val versionPackageName: Property<String>
@get:Input
abstract val modules: MapProperty<String, Map<String, String>>
@TaskAction @TaskAction
fun updateVersion() { fun execute() {
// action is defined in plugin val buildNumber = incrementAndGetBuildNumber()
writeClassFile(buildNumber)
}
private fun writeClassFile(buildNumber: String) {
var fileContent =
"""
package ${versionPackageName.get()}
import net.keinekekse.version.Version
""".trimIndent() +
buildVersionObject(
"",
projectVersion.get(),
buildNumber,
displayNameExtra.get(),
copyrightExtra.get(),
projectName.get(),
projectDescription.get()
)
if (modules.isPresent) {
modules.get().forEach { (moduleName, moduleInfo) ->
fileContent +=
buildVersionObject(
moduleName,
moduleInfo["version"] ?: "",
buildNumber,
moduleInfo["displayName"] ?: moduleName,
moduleInfo["copyright"] ?: copyrightExtra.get(),
moduleName,
moduleInfo["description"] ?: ""
)
} }
} }
val file = versionClassFile.get().asFile
file.parentFile.mkdirs()
file.writeText(fileContent)
}
private fun buildVersionObject(
moduleName: String,
versionString: String,
buildNumber: String,
longName: String,
copy: String,
shortName: String,
desc: String,
): String {
val ver = getVersionInfo(versionString, buildNumber, longName, copy, shortName, desc)
val module = if (moduleName.isBlank()) {
""
} else {
"_$moduleName"
}
return """
object VERSION$module: Version {
override val version = "${ver.version}"
override val fullVersion = "${ver.fullVersion}"
override val major = ${ver.major}
override val minor = ${ver.minor}
override val patch = ${ver.patch}
override val preRelease = "${ver.preRelease}"
override val buildMetadata = "${ver.buildMetadata}"
override val buildDate = "${ver.buildDate}"
override val displayName = "${ver.displayName}"
override val copyright = "${ver.copyright}"
override val name = "${ver.name}"
override val description = "${ver.description}"
}
""".trimIndent()
}
private fun getVersionInfo(
versionString: String,
buildNumber: String,
longName: String,
copy: String,
shortName: String,
desc: String,
): VersionInfo {
val version =
if (Version.parse(versionString, strict = false).buildMetadata.isNullOrEmpty())
Version.parse("$versionString+build.$buildNumber", strict = false)
else Version.parse(versionString, strict = false)
println("Source files changed. Setting version to $version")
return VersionInfo(
major = version.major,
minor = version.minor,
patch = version.patch,
preRelease = version.preRelease ?: "",
buildMetadata = version.buildMetadata ?: "",
fullVersion = version.toString(),
version = if (version.isPreRelease)
"${version.major}.${version.minor}.${version.patch}-${version.preRelease}" else
version.withoutSuffixes().toString(),
buildDate = java.time.Instant.now().toString(),
displayName = longName,
copyright = copy,
name = shortName,
description = desc
)
}
private fun incrementAndGetBuildNumber(): String {
val file = buildNumberFile.get().asFile
val props = Properties()
if (file.exists()) {
file.inputStream().use { props.load(it) }
}
val current = props.getProperty("build.number")?.toLongOrNull() ?: 0
val actual = (current + 1).toString()
props.setProperty("build.number", actual)
file.outputStream().use {
props.store(it, "auto generated")
}
return actual
}
data class VersionInfo(
val version: String,
val fullVersion: String,
val major: Int,
val minor: Int,
val patch: Int,
val preRelease: String,
val buildMetadata: String,
val buildDate: String,
val displayName: String,
val copyright: String,
val name: String,
val description: String,
)
}
@@ -1,129 +1,180 @@
package net.keinekekse.version package net.keinekekse.version
import org.gradle.api.InvalidUserDataException
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.file.RegularFile import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.bundling.Jar import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.gradle.api.tasks.bundling.War
import org.gradle.kotlin.dsl.extra
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.named
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.OutputStreamWriter
import java.util.*
/**
* Versions-Resource im Projekt anlegen und bei jedem Build eine Build-Nummer hochzählen
*
* Ohne weitere Konfiguration wird die Datei Version.kt mit der class VERSION im Verzeichnis
* src/main/kotlin/<project.group>/<project.name> erzeugt, für KMP in src/commonMain/kotlin.
* Die class wird mit Daten aus build.gradle.kts befüllt (s.u.).
*
* Für die Build-Nummer wird eine Datei buildnumber.properties im Projektverzeichnis erzeugt.
*
* Die class-Datei und der Package-Name können ggf. in build.gradle.kts konfiguriert werden:
*
* ```
* plugins {
* // Plugin einbinden
* id("net.keinekekse.version") version "3.0.0"
* }
*
* // Werte, die für die Version-Datei verwendet werden
* // in settings.gradle.kt: rootProject.name = "program-name"
* group = "org.example"
* version = "0.0.1-beta.1"
* description = "Program description"
* extra["displayname"] = "Program"
* extra["copyright"] = "Copyright (c) 2025 Hartmut Bauer"
*
*
* // Optional: Konfiguration der Version-Datei und des Packages
* versionConfig {
* versionClassFile.set(layout.projectDirectory.file("src/main/kotlin/Version.kt"))
* versionPackageName.set("net.keinekekse")
*
* // Optional Versionen von Teilbereichen des Programms
* modules.set(
* mapOf(
* "books" to mapOf( // -> object VERSION_books
* "version" to libs.versions.books.get(),
* "displayName" to "Books-DB",
* "copyright" to "Copyright (c) 2026 Hartmut Bauer", // null => extra["copyright]
* "description" to "Books + Comics Database" // null => ""
* )
* )
* )
* }
* ```
*
* VERSION-class:
*
* ```
* import net.keinekekse.version.Version // -> util-Lib
*
* object VERSION: Version {
* override val version = "1.0.0-beta.1 // für Release-Ver: major.minor.patch
* override val fullVersion = "1.0.0-beta.1+build.13"
* override val major = 1
* override val minor = 0
* override val patch = 0
* override val preRelease = "beta.1"
* override val buildMetadata = "build.13"
* override val buildDate = "2026-01-10T18:54:42.227942Z"
* override val displayName = "Program"
* override val copyright = "Copyright (c) 2026 Hartmut Bauer"
* override val name = "program-name"
* override val description = "Program description"
* }
* object VERSION_books: Version {
* override val version = "0.1.0-alpha.1"
* override val fullVersion = "0.1.0-alpha.1+build.11"
* override val major = 0
* override val minor = 1
* override val patch = 0
* override val preRelease = "alpha.1"
* override val buildMetadata = "build.11"
* override val buildDate = "2026-09-19T00:18:40.584742Z"
* override val displayName = "Books-DB"
* override val copyright = "Copyright (c) 2026 Hartmut Bauer"
* override val name = "books"
* override val description = "Books + Comics Database"
* }
* ```
*
*
*/
class VersionPlugin : Plugin<Project> { class VersionPlugin : Plugin<Project> {
private lateinit var versionFileName: String
private val buildNumProp = "buildNumber"
private val addTaskName = "addVersion"
private var isWar = false
override fun apply(project: Project) { override fun apply(project: Project) {
// println("Version Plugin") val extension =
val extension = project.extensions.create("version", VersionPluginExtension::class.java) project.extensions.create("versionConfig", VersionPluginExtension::class.java)
versionFileName = extension.propertiesFilename.orNull ?: "${project.name}.properties"
val warSource = val versionTask =
project.layout.projectDirectory.file("src/main/webapp/WEB-INF/classes/${versionFileName}") project.tasks.register("incrementBuildNumber", AddVersionTask::class.java) {
val jarSource = if (!extension.versionClassFile.isPresent) {
project.layout.projectDirectory.file("src/main/properties/${versionFileName}") val packageName =
val targetDir = project.layout.buildDirectory.dir("resources/main") project.group.toString().replace("-", ".") +
val countFile = project.layout.projectDirectory.file("buildnumber.properties") "." + project.name.replace("-", ".")
if (warSource.asFile.parentFile.parentFile.exists()) { val versionFileName = packageName.replace(".", "/") + "/Version.kt"
isWar = true
var targ = "src/main/kotlin/"
if (!project.layout.projectDirectory.file(targ).asFile.exists()) {
targ = "src/commonMain/kotlin/"
if (!project.layout.projectDirectory.file(targ).asFile.exists()) {
throw InvalidUserDataException(
"Version-Plugin: 'versionClassFile' and 'versionPackageName' must " +
"be set in a versionConfig-block in build.gradle.kts, " +
"as both 'src/main/kotlin' and 'src/commonMain/kotlin' do not exist!"
)
} }
if (isWar) { }
if (!warSource.asFile.parentFile.exists()) extension.versionClassFile.convention(project.layout.projectDirectory.file(targ + versionFileName))
warSource.asFile.parentFile.mkdir() extension.versionPackageName.convention(packageName)
checkVersionFile(warSource)
} else {
checkVersionFile(jarSource)
} }
if (isWar) { // Point to the source sets
try { val files = project.files(
project.tasks.named<War>("war") { "src/main/kotlin", "src/main/java",
filesMatching("**/${versionFileName}") { "src/commonMain/kotlin", "src/commonMain/java",
val num = buildNumber(countFile) "src/jvmMain/kotlin", "src/jvmMain/java"
project.extra[buildNumProp] = num )
expand(project.properties) // Filter lazily using a provider
} sourceFiles.from(extension.versionClassFile.map { classFile ->
} files.asFileTree.filter { it.name != classFile.asFile.name }
} catch (e: Exception) { })
println("\n*** Falsche Reihenfolge ***\n\nWAR-Plugin muss vor dem Versions-Plugin eingetragen werden!\n")
throw e
}
} else {
project.tasks.named<Jar>("jar") {
dependsOn(project.tasks.named(addTaskName))
}
project.tasks.register(addTaskName, AddVersionTask::class.java) { // Point to the properties file
group = "net.keinekekse" buildNumberFile.set(project.layout.projectDirectory.file("buildnumber.properties"))
description = "Adds version information to the project."
outputs.upToDateWhen { false }
mustRunAfter(project.tasks.named("classes")) // Link properties directly (this is lazy)
versionClassFile.set(extension.versionClassFile)
versionPackageName.set(extension.versionPackageName)
onlyIf { projectVersion.set(project.provider { project.version.toString() })
val cj by project.tasks.named("compileJava") projectName.set(project.provider { project.name })
val pr by project.tasks.named("processResources") projectDescription.set(project.provider { project.description ?: "" })
// (cj.state.didWork || pr.didWork)
((cj.state.didWork && !cj.state.upToDate) || (pr.didWork && !pr.state.upToDate)) displayNameExtra.set(project.provider {
project.extensions.extraProperties.let {
if (it.has("displayname")) it.get("displayname")?.toString() else ""
} }
})
from(jarSource) copyrightExtra.set(project.provider {
into(targetDir) project.extensions.extraProperties.let {
filesMatching("*") { if (it.has("copyright")) it.get("copyright")?.toString() else ""
val num = buildNumber(countFile)
project.extra[buildNumProp] = num
expand(project.properties)
} }
})
if (extension.modules.isPresent) {
modules.set(extension.modules)
extension.modules.get().forEach { (moduleName, moduleInfo) ->
// project.extensions.create("${moduleName}Version", VersionPluginExtension::class.java).apply {
// versionClassFile.set(project.layout.buildDirectory.file("generated/sources/version/${moduleName}/kotlin/${project.name}-version.kt"))
// modules.set(moduleInfo)
// }
} }
} }
} }
/** // 1. Handle Kotlin/JVM (Standard Project)
* Increment build number project.plugins.withId("org.jetbrains.kotlin.jvm") {
*/ project.extensions.getByType(SourceSetContainer::class.java).named("main") {
private fun buildNumber(propFile: RegularFile): String { // This tells Gradle that this source set includes the directory containing the generated file
val propName = "build.number" this.java.srcDir(versionTask.map { it.versionClassFile.get().asFile.parentFile })
val props = Properties()
try {
FileInputStream(propFile.asFile).use { props.load(it) }
} catch (e: Exception) {
// noop
} }
val numStr: String? = props.getProperty(propName)
var num = numStr?.toLongOrNull() ?: 0
num++
println("build number ${num}")
props.setProperty(propName, num.toString())
props.store(FileOutputStream(propFile.asFile, false), "auto generated")
return num.toString()
} }
/** // 2. Handle Kotlin Multiplatform
* Create version properties file if it doesn't exist project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
*/ val kotlinExt = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
private fun checkVersionFile(file: RegularFile) { kotlinExt.sourceSets.named("commonMain") {
if (!file.asFile.exists()) { this.kotlin.srcDir(versionTask.map { it.versionClassFile.get().asFile.parentFile })
val content = """
|name=${'$'}{name}
|displayname=${'$'}{displayname}
|description=${'$'}{description}
|copyright=${'$'}{copyright}
|version=${'$'}{version}
|build.number=${'$'}{${buildNumProp}}
|build.timestamp=${'$'}{buildTimestamp}
""".trimMargin()
val outfile = OutputStreamWriter(FileOutputStream(file.asFile))
outfile.write(content)
outfile.close()
} }
} }
}
} }
@@ -1,15 +1,13 @@
package net.keinekekse.version package net.keinekekse.version
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
interface VersionPluginExtension { interface VersionPluginExtension {
val versionPackageName: Property<String>
/** val versionClassFile: RegularFileProperty
* Name for the version properties file
* (JAR: src/main/properties, WAR: src/main/webapp/WEB-INF/classes) val modules: MapProperty<String, Map<String, String>>
* Will be copied to the classpath root.
*
* Default: <project.name>.properties
*/
val propertiesFilename: Property<String>
} }