Does Gradle automatically infer dependency between tasks? If so, when? - gradle

In my build script, when I configure the downloadAndUnzipFile task, I am explicitly querying output of downloadZipFile task. I expected this is sufficient for Gradle to infer a dependency between the tasks, but it apparently is not, because I get an error when invoking downloadAndUnzipFile`.
Execution failed for task ':downloadAndUnzipFile'.
> Cannot expand ZIP '/home/jdanek/repos/testing/gradle-infer-deps/build/1.0.zip' as it does not exist.
My build script build.gradle.kts is
import de.undercouch.gradle.tasks.download.Download
group = "org.example"
version = "1.0-SNAPSHOT"
plugins {
id("de.undercouch.download").version("4.0.4")
}
tasks {
val downloadZipFile by registering(Download::class) {
src("https://github.com/michel-kraemer/gradle-download-task/archive/1.0.zip")
dest(File(buildDir, "1.0.zip"))
}
val downloadAndUnzipFile by registering(Copy::class) {
from(zipTree(downloadZipFile.get().outputFiles.first()))
into(buildDir)
}
}
I also tried
from(zipTree(downloadZipFile.get().outputFiles.first()))
and that does not define a dependency either.
My Gradle is the latest 6.2.2.

In order for Gradle to discover task dependencies, they have to use specific types for their inputs and outputs so that Gradle can track the dependencies for you. See this documentation page on the topic.
In your use case, the de.undercouch.download plugin seems to expose a simple List<File> which is not a rich type, so Gradle cannot figure out the link. In that case you have be explicit about the task dependency, using dependsOn(downloadZipFile)

Related

Gradle: any difference between task configuration approaches

Is there any difference between two following pieces of code?
May be there is some difference about eager initialization of a task?
tasks.bootJar {
archiveFileName = "some-name"
}
and
bootJar {
archiveFileName = "some-code"
}
They are the effectively the same just different syntax. Both cause the task to be realized or eager initialized. You can confirm this by simply running with debug output: ./gradlew -d
You will see the following line logged:
[DEBUG] [org.gradle.internal.operations.DefaultBuildOperationRunner] Build operation 'Realize task :bootJar' started
For the Groovy DSL, the Gradle team takes advantage of the metaprogramming offered by the Groovy language to dynamically make tasks, extensions, properties, and more "magically" available.
So for this example:
tasks.bootJar {
archiveFileName = "some-name"
}
tasks is of type TaskContainer. If you were to examine all available methods and properties for TaskContainer, you will see that bootJar is not one of them. So Gradle hooks into the Groovy metaprogramming to search for something named bootJar. Since it's on the TaskContainer type, it can be assumed that bootJar is a task.
So if you were to desugar the DSL, the first example is effectively:
tasks.getByName("bootJar") {
}
Now for the second example:
bootJar {
archiveFileName = "some-code"
}
The default scope or this in a Gradle build file is Project. Again, just like before, if you were to examine all available methods and properties, you will not see bootJar as one of them.
Groovy's 'magic' is at play here, but this time around, Gradle will search (my understanding) practically everywhere because there is a lot more available on a Project.
project.extensions.named("bootJar") // exists?
project.hasProperty("bootJar") // exists?
project.tasks.getByName("bootJar") // found it!
To summarize, no there is no difference between the two since both cause the bootJar task to be realized. Use task configuration avoidance wherever possible to make your Gradle builds more efficient:
import org.springframework.boot.gradle.tasks.bundling.BootJar
tasks.named("bootJar", BootJar) {
}

WIthin nebula/gradle, how can I inject the version being released into the jar being published?

We have a tool that runs from the command line. One of the commands is -version.
Before we converted to the nebula release plugin, the version was in the gradle.properties file, and as part of the build we copied it from there to a src/main/resources/version.txt file, that was later read by the tool to output the version.
But now the version is never in a file that's checked into git. Instead, it is only known during the nebula release process.
We want to obtain that version during the nebula release process and inject it into the jar that nebula is about to publish. For example, it could be added to the manifest.
We've tried to figure out how to do this, but don't see any examples online, and nothing about it in the documentation.
Simply create a task that caches the version that is dynamically inferred by Nebula.
Since you originally copied/created src/main/resources/version.txt, we'll use that that model our task.
Assuming a simple/standard Java project, using the Kotlin DSL:
val cacheNebulaVersion by tasks.registering {
mustRunAfter(tasks.named("release"))
doLast {
val sourceSets = project.extensions.getByName("sourceSets") as SourceSetContainer
sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).output.resourcesDir?.let {
// If there are not existing resources in your project then you must create
// the resources dir otherwise a FileNotFoundException will be thrown.
if (!it.exists()) {
it.mkdirs()
}
File(it, "version.txt").printWriter().use { out ->
out.println(project.version)
}
}
}
}
When I invoke ./gradlew clean build snapshot cacheNebulaVersion, the version produced by Nebula is cached/created at src/main/resources/version.txt in the build output. The task above does not bundle it with the jar.
Hopefully that gives you an idea what to do.

Create separate shadowJars for code and dependencies

Using the gradle shadowJar plugin version 4.0.2 and gradle 4.10, I wanted to create two separate shadowJars, one for my source code and another for my dependencies (since dependencies are large and rarely changes I don’t want to repackage them every time I change my source code). What I have in mind is to have a gradle plugin that adds two separate tasks and which takes the same configurations supplied by user for shadowJar and overrides the configurations/sources used to create the shadowJar.
Below is what I have got so far, still trying to figure out a clean way to pass the shadow configs only once and whether there are other gotchas I need to worry about (ex: having two mergeServiceFiles will break etc)
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
task dependencyShadowJar(type: ShadowJar) {
mergeServiceFiles()
zip64 = true
relocate 'com.google.common', 'com.shadow.com.google.common'
classifier = 'dependencies'
configurations = [project.configurations.runtime]
}
task userCodeShadowJar(type: ShadowJar) {
mergeServiceFiles()
zip64 = true
relocate 'com.google.common', 'com.shadow.com.google.common'
classifier = 'mycode'
from sourceSets.main.output
}
task splitShadowJar {
doLast {
println "Building separate src and dependency shadowJars"
}
}
splitShadowJar.dependsOn dependencyShadowJar
dependencyShadowJar.dependsOn userCodeShadowJar
Ideally I would like to have a shadowJar settings specified once and the tasks copies the same settings, does that require creating a custom Plugin task in groovy ?
Can I copy the settings from existing shadowJar that user specifies and just overrides the from or configurations part alone for my purpose
Anybody has attempted something similar ?

dependencies task not found with project.tasks.findByName

When I run gradle tasks it always lists dependencies task; But when I run the following code in my build.gradle it always return null : project.tasks.findByName('dependencies')
My requirement is to print the output of dependencies task into the log for reference as part of build.
I am on gradle version 1.11 (not authorized to upgrade)
Please help:
You may try to use a custom task of org.gradle.api.tasks.diagnostics.DependencyReportTask type, and configured to print just sepecified configuration dependencies, as:
task printDeps(type: org.gradle.api.tasks.diagnostics.DependencyReportTask) {
configurations = project.buildscript.configurations + project.configurations
}
If you wish, you may exclude buildscript dependencies, if you don't need them.

Creating a post build copy task with Gradle

I am struggling with the Gradle build lifecycle; specifically with the split between the configuration and execution phases. I have read a number of sections in the Gradle manual and have seen a number of ideas online, but have not found a solution to the following problem:
I want to run a specific task to produce an artifact at the end of my java-library-distribution build that is a flattened version of the runtime configuration jars. That is, I only want to produce the artifact when I run the specific task to create the artifact.
I have created the following task:
task packageSamplerTask(type: Tar, dependsOn: distTar) {
description "Packages the build jars including dependencies as a flattened tar file. Artifact: ${distsDir}/${archivesBaseName}-${version}.tar"
from tarTree("${distsDir}/${archivesBaseName}-${version}.tar").files
classifier = 'dist'
into "${distsDir}/${archivesBaseName}-dist-${version}.tar"
}
Although this task does produce the required artifact, the task runs during gradle's configuration phase. This behavior has the following consequences:
Irrespective of which task I run from the command line, this packageSamplerTask task is always run, often unnecessarily; and
If I clean the project, then the build fails on the next run because $distsDir doesn't exist during the configuration phase (obviously).
It appears that if I extend the Copy task in this manner I'm always going to get this kind of premature behavior.
Is there a way to use the << closure / doLast declarations to get what I want? Or is there something else I'm missing / should be doing?
Update
After further work I have clarified my requirements, and resolved my question as follows (specifically):
"I want to package my code and my code's dependencies as a flat archive of jars that can be deployed as a jMeter plugin. The package can then be installed by unpacking into the jMeter lib/ext directory, as is. The package, therefore, must not include the jMeter jars (and their dependencies) which are used for building and testing"
Because Gradle doesn't appear to support the Maven-like provided dependency management, I created a new configuration for my package which excludes the jMeter jars.
configurations {
jmpackage {
extendsFrom runtime
exclude group: 'org.apache.jmeter', name: 'ApacheJMeter_core', version: '2.11'
exclude group: 'org.apache.jmeter', name: 'ApacheJMeter_java', version: '2.11'
}
}
And then created the following task (using the closure recommendation from Peter Niederwieser):
task packageSamplerTask(type: Tar, dependsOn: assemble) {
from { libsDir }
from { configurations.jmpackage.getAsFileTree() }
classifier = 'dist'
}
This solution appears to work, and it allows me to use just theGradle java plugin, too.
The task declaration is fine, but the flattening needs to be deferred too:
...
from { tarTree("${distsDir}/${archivesBaseName}-${version}.tar").files }
Also, the Tar file should be referred to in a more abstract way. For example:
from { tarTree(distTar.archivePath).files }
First your task isn't executed in the configuration phase but like EVERY task it is configured in that phase. And your closure is just a configuration of your task (a Configuration closure, not an Action closure). That is why your code is "executed" in the configuration phase".
If you want your code to be executed in the execution phase have to write it in a doLastclosure or doFirst. But in your case it is better to keep it in a configuration closure, because you are configuring your task.
To make sure your build doesn't fail because of the missing folder, you can create it with distsDir.mkdirs().

Resources