How may I modify a Copy Task's properties on execution? - gradle

I have a Gradle copy task set-up to publish undecorated JAR file(s) for testing and debugging, viz.
Task definition:
task copyJarToStaging( type: Copy ) {
from jar // shortcut for createJar.outputs.files
into ( "${rootProject.rootDir}/dist/" )
rename( '-.*\\.jar', ".jar" )
}
Which works, to put a JAR file into the one directory. What's really needed is to drop the JAR into one or more different folders under "dist/".
Following many trials (and errors) I found this version worked for me.
Invoke the copy task:
// build.gradle (module)
assemble.dependsOn copyJarToStaging {
println "into ==> ${destinationDir}/support"
into "${destinationDir}/support/"
}
However, it doesn't really smell right.
Is there a cleaner alternative way? I would have liked a closure for instance to just append to the into attribute -- But it didn't go.
If I wanted the same file in different places, it would be better if I can do something like take the into string and yield each value back.
Is part or all of that possible? Or, am I dreaming???

Typically you'd create multiple copy tasks
['dev', 'staging', 'uat', 'prod'].each { String dir ->
Task task = tasks.create("copyJarTo${dir.capitalize()}", type: Copy) {
from jar
into "dist/$dir"
}
assemble.dependsOn task
}

Related

Gradle not running copy task after jar

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.

Dynamically created task of type Copy is always UP-TO-DATE

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

Configuring tasks late in Gradle build lifecycle

I have a multiproject build. Some of the projects in the build produce test results. One project produces an installer, and I want that project's artifacts to include the test results from all the other projects. I attempted to do that like this (in the project that produces an installer):
// Empty test task, so that we can make the gathering of test results here depend on the tests' having
// been run in other projects in the build
task test << {
}
def dependentTestResultsDir = new File( buildDir, 'dependentTestResults' )
task gatherDependentTestResults( type: Zip, dependsOn: test ) {
project.parent.subprojects.each { subproject ->
// Find projects in this build which have a testResults configuration
if( subproject.configurations.find { it.name == 'testResults' } ) {
// Extract the test results (which are in a zip file) into a directory
def tmpDir = new File( dependentTestResultsDir, subproject.name )
subproject.copy {
from zipTree( subproject.configurations['testResults'].artifacts.files.singleFile )
into tmpDir
}
}
}
// Define the output of this task as the contents of that tree
from dependentTestResultsDir
}
The problem is that at the point when this task is configured, the test tasks in the other projects haven't run, so their artifacts don't exist, and I get messages during my build like this:
The specified zip file ZIP 'C:\[path to project]\build\distributions\[artifact].zip' does not exist and will be silently ignored. This behaviour has been deprecated and is scheduled to be removed in Gradle 2.0
So I need to do something that will involve the configuration of my task being delayed until the test artifacts have actually been produced. What is the idiomatic way to achieve this?
I seem to need to address questions of this nature quite frequently about Gradle. I think I'm missing something conceptually.
In that instance, you should just be able to make it an action by adding << as in
task gatherDependentTestResults( type: Zip, dependsOn: test ) << {
// your task code here
}

Gradle create multiple task types in one task

Is there a way to create multiple tasks in one gradle task as shown below,
task zipFiles() {
doLast {
copy{
from( "../licenses" ) {
include '**/*.txt'
}
into "../$releaseFolder/licenses"
}
zip {
from("../$releaseFolder/licenses")
include '*'
into "licenses"
destinationDir = file("../$releaseFolder/")
archiveName = "licenses.zip"
}
}
}
OR i need to create multiple tasks for copy and zip , ( this created large set of tasks in build file ).
There is no zip method in Gradle. You can either use a task of type Zip (preferred), or use the Ant zip task. Also, you wouldn't typically go through a staging directory, but put contents into the zip right from their original locations. If necessary, you would declare another task that creates an exploded zip (and shares much of the Zip task's definition).

Conventional way of copying files in Gradle - use Copy task or copy method?

I'm adding a task to deploy war files to Tomcat .. the only thing that the task needs to do is copy the war file to the TOMCAT location.
There 2 ways that I can think of implementing this .. but being new to gradle, I'm not quite sure what's more conventional/right (or if it even matters).
task myCopy(type: Copy)
myCopy.configure {
from('source')
into('target')
include('*.war')
}
or
task myCopy{
doLast{
copy {
from 'source'
into 'target'
include '*.war'
}
}
}
In most cases (including this one), the Copy task is the better choice. Among other things, it will give you automatic up-to-date checking. The copy method is meant for situations where (for some reason) you have to bolt on to an existing task and cannot use a separate task for copying.
The code for your Copy task can be simplified to:
task myCopy(type: Copy) {
from('source')
into('target')
include('*.war')
}
UP-TO-DATE only verifies the file is in place but not if the files has changed
to avoid being cached with an old file use
outputs.upToDateWhen { false }

Resources