Gradle: any difference between task configuration approaches - gradle

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) {
}

Related

How to define gradle task dependencies - inputs outputs OR dependsOn?

To get the incremental build support running correctly in gradle it is required to define the "inputs" and "outputs" in each (custom-) tasks.
That's a very neat way of gradle to check if a task can be skipped cause it is up-to-date or not. Sample:
task myTaskA {
def someOutputDir = file("$buildDir/gen")
// define task outputs (input not required here)
outputs.dir someOutputDir
doLast{
// generate something into the defined output directory
new File(someOutputDir, "dummy.txt").text = "Gradle sample"
}
}
task myTaskB {
// Input of this task is dependent on the output of myTaskA
inputs.files myTaskA
doLast{
// do something
}
}
Yes, that is pretty nice and additionally the good thing is, we do not need to declare a explicit dependency instruction (dependsOn) in task "myTaskB".
dependsOn myTaskA
This directive is not necessary cause we have a implicit dependency defined by the inputs-declaration.
I think it is a good style to provide always a inputs/outputs definition in custom tasks to support incremental builds.
BUT: This also means we can completely ignore the dependsOn.
SO: When should we prefer dependsOn over inputs/outputs?
Maybe if there is no inputs or outputs. Yes, but are there other use cases conceivable? I was always working with dependsOn, and this seams to me as obsolete now. What do you think?
Bye have a great day, Tony

How to avoid copy'n'paste in gradle?

I have a build.gradle file for my spring-boot application. I have a few environment details I wish to change for some of the gradle tasks. Specifically for the gradle tasks 'test', 'runSmokeTest' and 'bootRun'. In all the tasks I have to make the same calls, so I would prefer if I could extract a method out of that. Or a task. But whenever I do that, suddendly gradle no longer finds the functions that I require.
These are the calls I need to make:
systemProperties System.properties
systemProperty "spring.cloud.config.failFast", "false"
if (project.hasProperty("TEAM_ENCRYPT_KEY"))
environment "ENCRYPT_KEY", "$TEAM_ENCRYPT_KEY"
The code works perfectly fine when included directly in the bootRun task, the test task and the runSmokeTest task via copy'n'paste. I would prefer not to duplicate the code. I tried the following approach to extract them from the bootRun task, but Gradle keeps complaining that he does not find the functions systemProperty and environment. Similarly if I use the Intellij integrated feature 'extract Method':
task specialConfiguration() {
systemProperties System.properties
systemProperty "spring.cloud.config.failFast", "false"
if (project.hasProperty("TEAM_ENCRYPT_KEY"))
environment "ENCRYPT_KEY", "$TEAM_ENCRYPT_KEY"
}
bootRun {
dependsOn 'specialConfiguration'
}
How can I extract this short piece of code from the 3 tasks to avoid duplicate code?
Gradle keeps complaining that he does not find the functions systemProperty and environment
This is a prime example where the Kotlin DSL would shine. You would know exactly what methods/properties are available at any given time because it's a strongly type language unlike Groovy.
With that said, when you do the following:
task specialConfiguration() {
systemProperties System.properties
systemProperty "spring.cloud.config.failFast", "false"
if (project.hasProperty("TEAM_ENCRYPT_KEY"))
environment "ENCRYPT_KEY", "$TEAM_ENCRYPT_KEY"
}
bootRun {
dependsOn 'specialConfiguration'
}
You are:
Declaring a task named specialConfiguration.
No type was specified so the type is DefaultTask.
Configure bootRun task to depend on specialConfiguration
I think you are assuming that dependsOn is like "configuring" a task when really it's just adding a dependency to the task. See Adding dependencies to a task.
I am assuming that runSmokeTest is of type Test. So tasks test, runSmokeTest, and bootRun all implement the JavaForkOptions interface which is where the systemProperties(..), systemProperty(.., ..) and environment(.., ..) methods come from.
With that said, since you know the three tasks you want to configure, and they all implement JavaForkOptions and in some fashion, you can do (Kotlin DSL):
import org.springframework.boot.gradle.tasks.run.BootRun
// Assuming this is a Test task type
tasks.register("runSmokeTest", Test::class)
// Define a new Action (configuration)
val taskConfig = Action<JavaForkOptions> {
systemProperties(System.getProperties() as Map<String, Any>)
systemProperty("spring.cloud.config.failFast", false)
if (project.hasProperty("TEAM_ENCRYPT_KEY")) {
environment("ENCRYPT_KEY", project.property("TEAM_ENCRYPT_KEY")!!)
}
}
// Configure all three tasks
tasks.named("test", Test::class, taskConfig)
tasks.named("runSmokeTest", Test::class, taskConfig)
tasks.named("bootRun", BootRun::class, taskConfig)
The Groovy version of Francisco Mateo's answer, in case anyone needs that.:
Closure<JavaForkOptions> configAction = {
systemProperties System.properties
systemProperty "spring.cloud.config.failFast", "false"
if (project.hasProperty("MOBTECH_ENCRYPT_KEY"))
it.environment "ENCRYPT_KEY", "$MOBTECH_ENCRYPT_KEY"
}
# Configure the tasks with it
bootRun (configAction)

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

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)

Wiring two Gradle tasks together via a property

The question:
In Gradle, how do I make the output of one task be a property and the input of another task be that same property? Especially in the context of that property being needed at configuration time.
What I'm trying to accomplish:
I'm attempting to write a Tar task that depends on user input. I'm having trouble with the need for lazy configuration given that the "baseName" is not known at configuration time.
Code
Here's what I would like to work but it doesn't.
task saveDb(type: Tar, dependsOn: getTarBaseName) {
// Next line doesn't work but does if I surround 2nd param with '{}'
inputs.property("baseName", getTarBaseName.baseName) // Doesn't work
from file("$dbsDir/data")
destinationDir = file(project.dbsBackupDir)
baseName = getTarBaseName.baseName // Doesn't work
extension = 'tar'
compression = Compression.NONE
}
task getTarBaseName() {
doFirst {
def result = BuildUtil.promptForName() // Uses Swing to prompt for a name
getTarBaseName.ext.baseName = result
}
}
As you can see I'm using ext to try to pass information between tasks, but that is just incidental not a requirement. Also I'm using 2 tasks, I'm completely willing to use only 1, however, that wouldn't really answer the general question which is one I hit against fairly often when attempting to use Gradle as a cross platform bash replacement for project related tasks.
To solve your specific problem (if I did not miss something), you don't need a second task. Just add a doFirst closure to your Tar task and set the baseName property there to whatever you want:
task saveDb(type: Tar) {
// static configuration
doFirst {
baseName = BuildUtil.promptForName()
// or for another task (don't forget to depend on that task)
baseName = otherTask.myProperty
}
}
task otherTask {
doFirst {
ext.myProperty = BuildUtil.promptForName()
}
}
However, your question boils down a general difficulty in Gradle: when to apply a specific piece of configuration.
Gradle just introduced a rather new feature for lazy configuration: Provider and Property
Gradle provides lazy properties, which delay the calculation of a property’s value until it’s absolutely required.
Before Gradle 4.0, only files could be lazy evaluated (via ConfigurableFileCollection), as an example:
task myZip(type: Zip) {
// zip some files
}
task copyMyZip(type: Copy) {
from myZip
}
myZip.baseName = 'myZip'
Even if the name of the zip file is defined after the Zip task is added to the Copy task configuration, its correct path will be used for the copy operation.
Now, with Gradle 4.0 and up, all configuration parameters that implement Property or Provider can be bound easily (check out the link above) and you can also lazily read a configuration parameter by wrapping it into a Provider, but it's difficult to put a provider value into an old configuration parameter. You still need to specify the moment when to evaluate (inside the task action, in a doFirst closure or an afterEvaluate handler). This problem is the topic of this discussion on GitHub.

Exclude file from Gradle test dependency

I have rather expensive tests in my gradle java project, so I would like to avoid running them too often. Unfortunately, gradle reruns the tests on every build, since some log file in the resource-folder is changing.
Is there any way to exclude log-files from the dependency checks of :processTestResources and :test? I tried to include a exclude command in my test task, but this doesn't seem to do anything. My test task is
test {
maxHeapSize = "2048m"
workingDir = "src/test/resources/test-instance"
environment "LD_LIBRARY_PATH", "xpressmp/lib:/opt/gurobi/linux64/lib"
environment "XPRESS", "xpressmp/bin"
environment "XPRESSDIR", "xpressmp"
exclude("*.log")
exclude("*.lp")
}
I think what you are after is
sourceSets {
test {
resources {
exclude '*.log'
}
}
}
Excluding in the task would only exclude the test class from running, not which files are considered input for the task.
Btw. you can also use JUnit Categories to separate your tests into Short-Running and Long-Running tests and then make different tasks or a project property to only run the fast tests or all tests or only the slow tests. Or you can split the tests in different sourcesets and make separate tasks for it.
Define a new Test task:
task notGenericNotFT( type: Test, dependsOn: testClasses ){
filter { excludeTestsMatching 'generic.*' }
// excludes a whole package, "generic". NB this is not a regex:
// '*' is simply "wildcard" and dot means dot ... other more
// sophisticated "ANT-style" patterns are available in class Test
filter { excludeTestsMatching '*_FT' }
// also exclude all test classes ending in "_FT" (e.g. for "functional test")
}
To understand where these things come from, examine class Test and class TestFilter.
Also be aware that the gradle command line parser is quite intelligent and permissive with case-sensitivity (even in Linux!), so you can do this:
...$ ./gradlew notgen
... and it will run (as long as "notgen" designates this task unambiguously).

Resources