I'm still new to Gradle and I'm struggling to understand how task configurations for plugin tasks work. Consider the Jar task from the Java plugin: if I overwrite the doFirst and doLast methods why does the plugin's original set of actions still occur but if I change the description of the Jar task then my new description is used? The contents of my build script are:
plugins{
id 'java'
}
jar {
description "The new description"
doFirst {
println "this happened first"
}
doLast {
println "This happened last"
}
}
I ask this more to solidify my understanding of how gradle works and not necessarily because I would like to do this yet.
A Gradle script is just a Groovy script (with some extras) that operates on the Gradle API written in Java. You can checkout the API documentation and most of your questions will be solved:
Consider the Jar task from the Java plugin
Let's be pedantic on this one: There is no Jar task from the Java plugin, there is a task named jar of type Jar. It is very important to distinguish between task types and task instances in this context. Now let's check out the documentation of the Jar task type in Gradle. As you can see, it inherits from the type DefaultTask. DefaultTask defines all those properties and methods that are common for Gradle tasks:
doFirst(Closure action) - Adds the given closure to the beginning of this task's
action list.
doLast(Closure action) - Adds the given closure to the end of this task's action list.
As you can see, whenever you use doFirst or doLast, it will only append (or prepend) task actions to the list of existing task actions, nothing will be overriden.
Thanks to Groovy, assigning a value to the property description using = will actually call the method setDescription:
setDescription(String description) - Sets a description for this task.
This is a classical property setter known from the Java world. It will simply set the new value and the old value will be overridden.
Related
I am trying to do something like this:
jar {
doLast{
from "build/libs/TheJar.jar"
into "."
}
}
So far, I have tried various tutorials including all forms from this answer but non have worked. The only thing that works is calling a separate task but I'd like to know why my construction is wrong and why can't I run something after the jar or shadowJar tasks.
It looks like you took some parts of the answers in the linked post and somehow mixed them without knowing what your final code actually does.
Tasks in Gradle may have a type (e.g. Copy, Jar, ...). This type defines what the task will do once it gets executed (so called task actions). A task without a type won't do anything when its executed, unless you add task actions manually. Using doFirst will add the passed action (also called closure) to the start of the list of task actions, using doLast will add it to the end of the list.
Everything outside of doFirst and doLast closures is not part of the execution of the task, but can be used to configure the task:
task example {
doLast {
println "second action"
}
doFirst {
println "first action"
}
println "configuration"
}
Run the code above with gradle example and you will see the order of the log messages as configuration, first action, second action.
Task configuration will run, even if the task won't be executed later on. Even if you call gradle (without any task names as arguments), the console will still print configuration. This was the actual problem in the linked question.
Lets come to the real question: How to copy a file?
Well, Gradle offers two ways to copy a file. The first one is the task type Copy. You can create a task based on this type, apply your configuration and then either call it directly from the command line or define task dependencies using dependsOn or finalizedBy:
task copySomeFiles(type: Copy) {
from '...'
into '...'
}
However, you don't want to create an additional task. Gradle also has a method called copy that may be called anywhere in your script and will instantly copy the files:
copy {
from '...'
into '...'
}
The code above will copy your files, but it will copy your files every time Gradle executes (which may be often, e.g. when using an IDE). To solve this problem, you may move your code with copy to a task action, e.g. inside a doFirst or doLast closure:
jar {
doLast {
copy {
from "build/libs/TheJar.jar"
into "."
}
}
}
As you can see, your code was missing the copy statement. Now, whenever your task jar gets executed, its last task action will copy the files as intended.
Bonus question: Why is there no error?
The "problem" in your case is that your code is perfectly valid Gradle code. The task jar is of type Jar. Every task of type Jar may be configured using methods called from and into. The method from adds files to the JAR file and the method into sets the destination directory inside the JAR. So instead of copying, your code configures the underlying task jar. However, this has no negative consequences, as this configuration gets applied inside doLast, which only runs once the JAR file has already been created. Something that already happened cannot be configured.
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) {
}
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.
I want to write a script for following task in gradle. In IDE I run the task following way. When I want to add a custom task in the build.gradle file for the same. I wrote following script but it is giving me no such property: Classes for class: org.gradle.api.Project error . How to write the task I have in the screenshot using a custom script in gradle? Thank you.
task h2Continuous(type: Classes) {
args "--continuous"
}
There is not a Task type Classes. classes (lowercase) is a ad hoc declared task of the Java plugin, but not a unique Task type. It is an ad hoc task which wraps (via dependsOn) compileJava and processResources tasks. compileJava is a unique task type: JavaCompile.
If you are truly concerned with only java compilation, you could type your task as a JavaCompile task. There are multiple ways you could add in the processResources, if you really wanted your task to emulate the Java plugin's classes task.
I've prepared a very simple script, that illustrates the problem I see using Gradle 1.7 (need to stick with it because of some plugins not yet supporting newer versions).
I'm trying to dynamically create tasks each of which corresponds to a file in the project directory. This works fine, but the tasks I create never get executed as soon as I assign them type 'Copy'.
Here is my problem build.gradle:
file('templates').listFiles().each { File f ->
// THIS LINE DOES NOT WORK
task "myDist-${f.name}" (type: Copy) {
// NEXT LINE WORKS
//task "myDist-${f.name}" {
doLast {
println "MYDIST-" + f.name
}
}
}
task distAll(dependsOn: tasks.matching { Task task -> task.name.startsWith("myDist")}) {
println "MYDISTALL"
}
defaultTasks 'distAll'
in this way my tasks do not get executed when I call default task calling simply gradle:
MYDISTALL
:myDist-template1 UP-TO-DATE
:myDist-template2 UP-TO-DATE
:distAll UP-TO-DATE
BUILD SUCCESSFUL
If I remove type Copy from my dynamic task (uncommenting the line above), my tasks get executed:
MYDISTALL
:myDist-template1
MYDIST-template1
:myDist-template2
MYDIST-template2
:distAll
BUILD SUCCESSFUL
(You'll need to create a folder name templates in the same directory where build.gradle is located and put couple of empty files into there in order to run the test)
According to the debug output:
Skipping task ':myDist-template1' as it has no source files.
Skipping task ':myDist-template2' as it has no source files.
So how can I specify source files and make my Copy tasks execute?
I've tried adding
from( '/absolute/path/to/existing/file' ) {
into 'myfolder'
}
to the task body, I've tried assigning task's inputs.source file('/my/existing/file') with no success.
Could you please advise on how to modify my simple script leaving dynamic task creation and keeping my dynamic tasks of type Copy?
Thank you!
Edit:
All right, this way the task gets called:
file('templates').listFiles().each { File f ->
task "myDist-${f.name}" (type: Copy) {
from f
into 'dist'
doLast {
println "MYDIST-" + f.name
}
}
}
but it looks I must always specify from/into. It doesn't suffice to do that in the doLast{} body.
A Copy task only gets executed if it has something to copy. Telling it what to copy is part of configuring the task, and therefore needs to be done in the configuration phase, rather than the execution phase. These are very important concepts to understand, and you can read up on them in the Gradle User Guide or on the Gradle Forums.
doFirst and doLast blocks get executed in the execution phase, as part of executing the task. Both are too late to tell the task what to copy: doFirst gets executed immediately before the main task action (which in this case is the copying), but (shortly) after the skipped and up-to-date checks (which are based on the task's configuration). doLast gets executed after the main task action, and is therefore clearly too late.
I think the following Gradle User Guide quote answers my question the best:
Secondly, the copy() method can not honor task dependencies when a task is used as a copy source (i.e. as an argument to from()) because it's a method and not a task. As such, if you are using the copy() method as part of a task action, you must explicitly declare all inputs and outputs in order to get the correct behavior.
Having read most of the answers to "UP-TO-DATE" Copy tasks in gradle, it appears that the missing part is 'include' keyword:
task copy3rdPartyLibs(type: Copy) {
from 'src/main/jni/libs/'
into 'src/main/libs/armeabi/'
include '**/*.so'
}
Putting from and into as part of the doLast section does not work. An example of a working task definitions is:
task copyMyFile(type: Copy) {
def dockerFile = 'src/main/docker/Dockerfile'
def copyTo = 'build/docker'
from dockerFile
into copyTo
doLast {
println "Copied Docker file [$dockerFile] to [$copyTo]"
}
}
Not the behavior I was expecting.
Using gradle 3.2.1