Git based automatic versioning in Gradle - java

I'd like to implement automatic versioning for java-projects in Gradle via Git tags. Currently I'm using the git describe command to generate a version string. The version string has to be used in a manifest-file and also be programmatically retrievable. This is why I implemented a method to save it in a properties file that get's included in the build and can be read by Java code.
What I currently have is this small Gradle plugin (I'm really new to gradle):
apply plugin: 'java'
apply plugin: VersioningPlugin
class VersioningPlugin implements Plugin<Project> {
private Project project;
private String getVersion() {
def stdout = new ByteArrayOutputStream()
project.exec {
commandLine 'git', 'describe', '--tags'
standardOutput = stdout
}
stdout.toString().trim()
}
#Override
void apply(Project project) {
this.project = project
project.extensions.create('versioning', VersioningPluginExtension)
project.versioning.version = getVersion()
project.task('writeVersionFile') {
doLast {
def versionPropsFile = project.versioning.versionFile
def versionProps = new Properties()
versionProps.put('version', getVersion())
versionProps.store(versionPropsFile.newWriter(), null)
}
}
}
}
class VersioningPluginExtension {
File versionFile
String version
}
compileJava.dependsOn writeVersionFile
I use it in my build scrips like this:
apply from: 'versioning.gradle'
versioning {
versionFile = file('my/ressource/folder/version.properties')
}
println(versioning.version)
Everything works OK. The only problem I have is the following: I use gradle to build and test my code, then commit the code and create a git tag. If I now ask gradle to package a jar file without cleaning first, it doesn't copy the new version.properties into the jar file because it didn't notice that the file changed. So my application shows the old version.
Is there anything I can do about this? How can I get gradle to notice the version change and create a jar with the current version.properties file?

What you need to do is to configure a TaskOutputs (or from) for Jar task so that it can recognize if something has changed and be run without calling clean explicitly. This can be done in the following way:
jar {
from versioning.versionFile
}
Secondly. You can configure writeVersionFile in a similar way. If you configure both inputs and outputs the task will be cacheable. How it has changed can been seen here, a demo can be found here.
Also, mind that your task is created now in afterEvaluate closure. Why is that? Because this is after versioning {} extension is executed and versionFile property is set and can be configured as an output.
You can also configure jar input in versioning.gradle file.

Related

`gradlew jar` is not producing a jar

I'm building a Java command line application using gradle and have it running when I use gradlew run, however I would like to generate a jar -- which I would assume I would then have users download to invoke the CLI.
However, when I run gradlew jar, nothing is produced (build/lib dir doesn't even exist) even though the build runs with no errors and finishes with BUILD_SUCCESSFUL.
Two questions:
Why is no jar being produced?
Is having users download a jar the best way to ship a CLI for Java?
Below is my full build.gradle.kts
plugins {
// Apply the application plugin to add support for building a CLI application in Java.
application
id("com.diffplug.spotless") version "6.12.0"
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit test framework.
testImplementation("junit:junit:4.13.2")
// This dependency is used by the application.
implementation("com.google.guava:guava:30.1-jre")
implementation("info.picocli:picocli:4.7.0")
annotationProcessor("info.picocli:picocli-codegen:4.7.0")
implementation("io.vavr:vavr:0.10.4")
}
application {
// Define the main class for the application.
mainClass.set("testlauncher.command.Runner")
}
subprojects {
apply {
plugin("com.diffplug.spotless")
}
}
spotless {
java {
importOrder()
removeUnusedImports()
googleJavaFormat()
}
}
project.tasks.findByName("build")?.dependsOn(project.tasks.findByName("spotlessApply"))
I'm dumb.
I thought the jar would be in ./build/libs but it's actually in ./app/build/libs.

How to shared gradle tasks across many projects in different git repositories

We have lots of libraries for which we have the following snippets to configure the respective plugins. We would want to avoid code duplication and rather want to pull these definitions from a base repository which can be shared across all projects. How should this be configured?
checkstyle {
showViolations = false
ignoreFailures = true
..
}
pmd {
..
}
license {
..
}
spotless {
..
}
artifactory {
..
}
With an easy approach you can import build script plugins into other build scripts even if they are located in a different location / different repository. The only requirement that I think is necessary that on the machine where you will build the project at the end you have access to those repositories.
Imagine you have GitHub repository that contains main configuration.
https://github.com/user/shared-gradle-config/blob/master/scriptPlugin.gradle
That contains:
checkstyle {
showViolations = false
ignoreFailures = true
..
}
pmd {
..
}
...
Now be sure that you use raw resources in other scripts. Every repository service will have different kind of link. Below example for GitHub:
https://raw.githubusercontent.com/user/shared-gradle-config/master/scriptPlugin.gradle
At the beginning of the Gradle build script for the actual projects you have to include application of the remote script plugin:
// Application of shared remote Gradle script plguin with common configuration
// With this line all the checkStyle, PMD configuration and whatever you have declared in the script will be applied to current project
apply from: "https://raw.githubusercontent.com/user/shared-gradle-config/master/scriptPlugin.gradle"
// Whatever custom logic below
wrapper {
version = "7.0.0"
distributionType = Wrapper.DistributionType.ALL
}
...
I made a prototype project miself as I tried to understand how to share build configuration this way. Maybe it can help you, https://github.com/rivancic/gradle/tree/master/script-plugin. Note its not in final version, still improving it..

Gradle task not running as demanded (before compiling)

INTRODUCTION
The project is in Kotlin and builds using Gradle. I'm trying to generate a basic data class with some build info, let's say for now that I need it [re]generated every time before running.
Here's the Gradle task I have now:
def generatedDir = "$buildDir/generated"
// noinspection GroovyAssignabilityCheck
task generateBuildInfo {
inputs.property "version", rootProject.version.toString()
inputs.property "name", rootProject.name.toString()
outputs.dir generatedDir
outputs.upToDateWhen { false }
doFirst {
def buildInfoFile = file("$generatedDir/BuildInfo.kt")
buildInfoFile.parentFile.mkdirs()
buildInfoFile.text = """
internal data class BuildInfo(
val version: String = "${project.version.toString() ?: "unspecified"}",
val name: String = "${project.name.toString() ?: "unspecified"}"
)
""".replace(" ", "").trim()
}
}
To be able to resolve this from IntelliJ IDEA, I added my new folder to project sources, and obviously wired up the dependencies, like so:
sourceSets.main.kotlin.srcDirs += generatedDir
project.afterEvaluate {
compileJava.dependsOn generateBuildInfo
compileKotlin.dependsOn generateBuildInfo
}
This is all done in a separate file (to avoid polluting my main scripts). Due to this organization, after applying plugins, I just include the generator in my main script, like this:
apply from: "gradle/scripts/build-info-generator.gradle"
THE PROBLEM
It looks like the generator code is executed only once, after running assemble when I first ran clean on this module. This is not what I want, because when I change some of the project properties (like version), the source does not get updated... as if compileJava/compileKotlin and my custom task are not executed.
They do not appear in build logs as executed.
Is there any way I can run this task every time I want to run my module's launcher? Sure, I can do some smart file comparison to see if generation is needed, but for now I just want it done each time. Am I missing something?
IDEA has its own build system, indepenant from Gradle.
You can configure it to run a Gradle task before its own build task.
You can also configure it to delegate all the build/run tasks to Gradle. But that's not the default.

How to specify "sources" JAR for local JAR dependency?

I have a *.jar file in my Gradle / Buildship project that resides in a lib folder. I include it in my build.gradle via:
compile files('libs/local-lib.jar')
I also have a correspondinglocal-lib-sources.jar file that I would like to attach to it. In Eclipse, for manually managed dependencies this works via context menu of build path entry -> Properties -> Java Source Attachment. However, for gradle-managed dependencies, that option is not available.
Does anybody know how what the gradle/buildship way to do this looks like? My dependency is in no repository, so I'm stuck with compile files for now.
If you want to use Buildship with Eclipse then you are out of luck since this is not currently supported by gradle (see https://discuss.gradle.org/t/add-sources-manually-for-a-dependency-which-lacks-of-them/11456/8).
If you are ok with not using Buildship and manually generating the Eclipse dot files you can do something like this in your build.gradle:
apply plugin: 'eclipse'
eclipse.classpath.file {
withXml {
xml ->
def node = xml.asNode()
node.classpathentry.forEach {
if(it.#kind == 'lib') {
def sourcePath = it.#path.replace('.jar', '-sources.jar')
if(file(sourcePath).exists()) {
it.#sourcepath = sourcePath
}
}
}
}
}
You would then run gradle eclipse from the command line and import the project into Eclipse using Import -> "Existing Projects into Workspace"
Another (possibly better) option would be to use a flat file repository like this:
repositories {
flatDir {
dirs 'lib'
}
see https://docs.gradle.org/current/userguide/dependency_management.html#sec:flat_dir_resolver
You would then just include your dependency like any other; in your case:
compile ':local-lib'
This way Buildship will automatically find the -sources.jar files since flatDir acts like a regular repository for the most part.
Use an extra folder called lib or similar on the same directory level as src or you build script.
dependencies {
//local file
compile files('lib/local-lib-sources.jar')
// others local or remote file
}

How do I generate the class path for a Gradle project?

I have a gradle project with multiple packages. After the build, each package generates its jar files in build/libs. The external jar dependencies are pulled into ~/.gradle. I would now like to run the service locally from the commandline with the appropriate classpath. For this purpose, I am writing a script that constructs the classpath. The problem is that the script does not understand all the external dependencies and hence cannot construct the classpath. Is there a way for gradle to help with this? Ideally, I would like to dump all the dependencies into a folder at the end of the build.
Firstly, i would suggest using the application plugin if you can, since it takes care of this already.
If you want to dump the classpath to a file yourself, the simplest way is something like:
task writeClasspath << {
buildDir.mkdirs()
new File(buildDir, "classpath.txt").text = configurations.runtime.asPath + "\n"
}
If you want to actually copy all the libraries on the classpath into a directory, you can do:
task copyDependencies(type: Copy) {
from configurations.runtime
into new File(buildDir, "dependencies")
}
You could try something like this in your build script:
// add an action to the build task that creates a startup shell script
build << {
File script = file('start.sh')
script.withPrintWriter {
it.println '#!/bin/sh'
it.println "java -cp ${getRuntimeClasspath()} com.example.Main \"\$#\""
}
// make it executable
ant.chmod(file: script.absolutePath, perm: 'u+x')
}
String getRuntimeClasspath() {
sourceSets.main.runtimeClasspath.collect { it.absolutePath }.join(':')
}

Categories