Gradle filetree lazy flat copy - gradle

I have a task that needs to copy files that are created during the execution phase (they're products of a gcov unit-testing phase) into another directory. At the moment, my code will only execute properly the second time it is executed (i.e. when files in the directory structure have been created). The first time round, however, I get a "skipping task, no source files" debug message.
task copyGcovObj(type: Copy, dependsOn: 'test') {
description "Copies gcov files into build/testOutput directory."
from fileTree(dir: "$buildDir/objectFiles", includes: ["**/*.gcno", "**/*.gcda"]).files
into ("$buildDir/testOutput")
}
The code is taken from here: Flat copy. The task 'test' referred to is a task that executes the unit tests.
I think the problem is that during the configuration phase, there are no files to copy, so Gradle skips the task. If it is executed a second time, Gradle sees there are files, so executes the copy. How can I make it so that Gradle executes the copy, but the files to be copied are determined at the execution phase?

from and into accept closures to defer evaluation of arguments, so this should help:
from { fileTree(...).files }

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.

copying behavior in "configuration closure" happens during "execution phase", not "configuration phase"

I'm currently studying gradle.
I have the following code
task simpleCopy(type: Copy){
from 'source.xml'
into 'destinationFolder'
}
My understanding is that the code inside {} is the configuration closure, and it is executed during the configuration phase, to prepare the task for execution during execution phase. So I'm expecting the source.xml to be copied into destinationFolder during the configuration phase (in other words, the copying will happen when I simply run gradle, and I dont have to specifically run gradle simpleCopy for the copying behavior to happen).
But what I have found is that the copying does NOT happen when I run gradle at command line. The copying only happens when I explicitly execute the simpleTask task (i.e. by running gradle simpleTask at command line). So the code above actually behave the same as
task simpleCopy(type: Copy){
doLast {
from 'source.xml'
into 'destinationFolder'
}
}
Is my understanding of configuration phase and configuration closure incorrect? Or am I missing some information?
During the configuration phase the copy task is configured, i.e. the source and destination locations are set (this is all the configuration closure does) but the copy itself is not yet done.
The copy only happens when the task is executed.

Basic Gradle copy-task not working (until directly called)

I have a simple Gradle script:
task copyall(){
println 'starting...'
task copyA(type: Copy) {
mkdir 'web'
from 'src'
into 'web'
}
}
It behaves strangely: if I call "gradle copyall" from command line - it creates directory but doesn't copy files from 'src' to 'web' folders.
If I call directly task.copyA ("gradle copyA") from command line - it does both makes dir and copying files (with subfolders).
Why task.copyA is only partially executed as subtask of task.copyall?
Well, I understand why your Gradle script behaves the way you describe, but it behaves this way for various reasons.
First of all, there is no such thing as a subtask in Gradle. You can create tasks and you can define dependencies between tasks, that's it. So your task copyA is exactly the same thing if you just define it on its own (the recommended way):
task copyAll {
println 'starting...'
}
task copyA(type: Copy) {
mkdir 'web'
from 'src'
into 'web'
}
Now you have two tasks, but the task copyAll does nothing at all. Even your println statement is not, what the task does, but how it is configured:
Gradle has two major phases when executing build scripts: configuration phase and execution phase. The whole build script will be evaluated (executed) during configuration phase and only the task actions (e.g. copying for a Copy task), doFirst and doLast closures of selected tasks will be executed during execution phase. The selected tasks are determined from the command line parameters and task dependencies.
As a conclusion, the 'starting ...' output is printed when task copyAll is configured, not when it is started!
Last but not least, the mkdir command is causing confusion, because it is not part of the Copy task, but a method of the Project instance. It executed directly when called, so in your case directly during the configuration phase, completely independent from any task execution. It will be executed every time you execute Gradle.
But, to hear some good news, you simply don't need it. A Copy task will create all required target directories on its own.
Now, to summarize all points from above, we come up with the following build script:
task copyAll {
dependsOn 'copyA'
}
task copyA(type: Copy) {
from 'src'
into 'web'
}

Gradle - Error Copying Files To Project Root

I’m getting the following error whenever I attempt to use a Copy task to copy a file into the root of a project (the same folder I’m running gradle from):
Failed to create MD5 hash for file content.
I thought this was related to the artifacts I was pulling from Artifactory, but that seems to be unrelated. I was able to get the same results with a minimal script.
Is there something obviously wrong with what I’m doing, or does Gradle intentionally disallow such things?
task fails(type:Copy) {
from 'build/someFile.txt'
into new File('.').absolutePath
}
task works(type:Copy) {
from 'build/someFile.txt'
into new File('.').absolutePath + '/output'
}
Short Answer: Don't copy into the project directory, you are best to use into "$buildDir/someFolder" so that the folder is isolated to this single task, and also so that it will be cleaned by gradle clean
Long Answer: At it's core, Gradle has the concept of an "UP-TO-DATE" check for every single task. If Gradle sees that nothing has changed since last time a task was executed it will use the old result instead of executing again.
UP-TO-DATE checking is implemented by taking a "hash" of the task inputs and task outputs. Since you are using into '.' that means that the entire contents of the project directory is considered a task output (bad)
Gradle uses the .gradle folder for temp files (eg task hashes) It's likely some of these files are locked for writing as Gradle is trying to also read the same files (to calculate the "hash" of the task outputs) causing the error you are seeing
* EDIT *
If you need to copy into the project directory for legacy reasons, you might use Project.copy(...) directly instead of a Copy task. You could manually manage the task inputs/outputs in this case
Eg
task customCopy {
inputs.file "$buildDir/someFile.txt"
outputs.file 'someFile.txt'
doLast {
copy {
from "$buildDir/someFile.txt"
into '.'
}
}
}
Can you believe it, the following works
task myCopy(type: Copy) {
from "$rootDir/app1/src/main/resources/db"
into "$rootDir/app2/src/test/resources/db"
}
test.dependsOn myCopy
and the following doesn't 🤦
task myCopy(type: Copy) {
from '$rootDir/app1/src/main/resources'
into '$rootDir/app2/src/test/resources'
}
test.dependsOn myCopy

Zip task marked as up-to-date

Hi I try to collect plugins from sub-folder, zip them and copy to my export folder.
task buildPlugin {
dependsOn ':empty-plugin:build'
}
task exportPlugin(type: Zip) {
dependsOn buildPlugin
// create new export folder as destination for nightly build
def folder = '/export';
def file = "${project.name}-sdk-${project.version}";
// collect all plugins into cwc-sdk zip file
baseName = file
fileTree("cwc-plugin").each({
if (it.name.endsWith(".zip")) {
from it.absolutePath
}
})
// move cwc-sdk zip file into export destination folder
copy { into folder from file }
delete file
}
I run clean task first. The gradle logs:
:api:compileJava
:api:processResources
:api:classes
:api:jar
:empty-plugin:compileJava
:empty-plugin:processResources
:empty-plugin:classes
:empty-plugin:jar
:empty-plugin:assemble
:empty-plugin:compileTestJava UP-TO-DATE
:empty-plugin:processTestResources UP-TO-DATE
:empty-plugin:testClasses UP-TO-DATE
:empty-plugin:test UP-TO-DATE
:empty-plugin:check UP-TO-DATE
:api:javadoc
:empty-plugin:zip
:empty-plugin:build
:buildPlugin
:exportPlugin UP-TO-DATE
BUILD SUCCESSFUL
Total time: 2.097 secs
While first run :exportPlugin is marked as UP-TO-DATE and I don't get the zipped file from build. When I run :exportPlugin again everything is fine. It's also fine when I chain both tasks manually (rungradle clean, next run gradle buildPlugin, run gradle exportPlugin by doublclick to tasks at IDEA)
I think the order of tasks are still ok. I don't need to work with mustRunAfter.
I also played around with copySpec, buildplugin.outputs.files. But nothing helps.
Can anybody help me to solve this issue for initial build run?
Thanks!
Update:
A Zip task is an abstracted Copy task
AbstractCopyTask is the base class for all copy tasks. (Docu)
I found this comment from Peter Niederwieser
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.
How do I change from it.absolutePath code line inside fileTree loop to be part during configuration phase?

Resources