Copy task doesn't fail (or log) when source file is missing - gradle

My Copy task copies multiple sources:
For example:
task initConfig(type: Copy) {
from('src/main/config') {
include '**/*.properties'
include '**/*.xml'
filter(ReplaceTokens, tokens: [version: '2.3.1'])
}
from('src/main/config') {
exclude '**/*.properties', '**/*.xml'
}
from('src/main/languages') {
rename 'EN_US_(.*)', '$1'
}
into 'build/target/config'
}
When one of the sources doesn't exist, the task doesn't fail nor log an error.
Using "inputs.sourceFiles.empty" as a solution works just for a single source:
Fail Gradle Copy task if source directory not exist
Using assert to each source...is not an elegant solution since I have a lot of sources.
assert file("bla bla").exists()
How can I fail the task when one of the sources is missing?

Related

Gradle: process files inside a zip inside a tar

I have the following use case: I want to untar a tar file which contains a zip file. I then want to process files in the zip file (let's say I just want to filter out some file).
What I'm trying to do is something like:
task extractTar(type: Copy) {
from(tarTree("../a.tgz")) {
include '**/*.zip'
}
into "${buildDir}/tarOutput"
}
task unzipAndRezipZip(type: Zip, dependsOn: extractTar) {
archiveFileName = "rezipped.zip"
destinationDirectory
from(fileTree("${buildDir}/tarOutput").find { it.name.endsWith("zip") }) {
exclude '**/b.txt'
}
}
I've spent many many hours on this and every way I try to do it, it fails some way or another.
Expected Behavior
I can use the output files from a CopyTask in my ZipTask.
Current Behavior
path may not be null or empty string. path='null'
or
Circular dependency between the following tasks:
:unzipAndRezipZip
\--- :unzipAndRezipZip (*)
Steps to Reproduce
I have made a repo which has three examples of different things I tried: https://github.com/viktornordling/gradle-unzip-zip-from-tar-issue and clearly shows the repro steps for each example.
(I also put this in https://github.com/gradle/gradle/issues/16413, but I'm not sure how much love it will get there. Cross posting here to see if I can get some traction.)
Update: I would prefer not to use doLast in the solution as I feel that should not be needed. Usage of doLast is explicitly discouraged in the docs and not mentioned in the docs of CopyTask.
In example-4 from your repo https://github.com/viktornordling/gradle-unzip-zip-from-tar-issue you state that it almost works, but fails when you run clean unzipAndRezipZip in one go, with the error message Cannot expand ZIP...:
task unzipAndRezipZip(type: Zip) {
archiveFileName = "rezipped.zip"
destinationDir(file("${buildDir}/zipOutput"))
from(zipTree(tarTree("../a.tgz").find { it.name.endsWith("zip") })) {
exclude '**/b.txt'
}
}
You can fix that by changing the call to from() to a closure from { } to delay the execution of zipTree, i.e.:
task unzipAndRezipZip(type: Zip) {
archiveFileName = "rezipped.zip"
destinationDir(file("${buildDir}/zipOutput"))
from { zipTree(tarTree("../a.tgz").find { it.name.endsWith("zip") }) } {
exclude '**/b.txt'
}
}

How do I get a task to execute of type Zip using 'dependsOn'?

I am trying to make my task zipGui execute on build target, but the only way I seem to be able to execute a task is if I remove the (type: Zip) from the task definition.
This snippet fails to execute the zipGui task at all:
task zipGui(type: Zip) {
doFirst {
println "==================== Zipping GUI components"
}
doLast {
FileTree zip = zipTree('assets/htdocs/gui.zip')
from 'assets/htdocs'
}
}
build.dependsOn zipGui
And this executes the zipGui but it doesn't know anything about how to zip files:
task zipGui {
doFirst {
println "==================== Zipping GUI components"
}
doLast {
FileTree zip = zipTree('assets/htdocs/gui.zip')
from 'assets/htdocs'
}
}
build.dependsOn zipGui
This is a stripped down fragment of the overall build.gradle.
How can I get zipGui to execute as a dependency of the build?
EDIT: here is more of the real build.gradle without me stripping things out to simplify the question:
task copyCert(type: Copy) {
from '../../../install'
into 'assets/certs/root'
include 'ca.pem'
doFirst {
println "==================== Copying root cert into assets"
}
}
task copyGui(dependsOn: copyCert, type: Copy) {
from '../../web/gui'
into 'assets/htdocs'
include '**/*.html'
include '**/*.css'
include '**/*.js'
include '**/*.wav'
include '**/*.tmpl'
include '**/*.png'
include '**/*.gif'
include '**/*.jpg'
exclude '**/*.DS_Store'
exclude '**/.gitignore'
exclude '**/.thumb'
exclude '**/build'
doFirst {
println "==================== Copying gui components into assets"
}
}
task zipGui(dependsOn: copyGui, type: Zip) {
FileTree zip = zipTree('assets/htdocs/gui.zip')
from 'assets/htdocs'
doFirst {
println "==================== Zipping GUI components"
}
}
Check details of zip task here: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html
task zipGui(type: Zip) {
archiveName = 'gui.zip'
from("$projectDir/../../../install") {
into 'assets/certs/root'
include 'ca.pem'
}
from("$projectDir/../../web/gui"){
into 'assets/htdocs'
exclude '**/*.DS_Store'
exclude '**/.gitignore'
exclude '**/.thumb'
exclude '**/build'
} // Just include or exclude is enough here as you indicated.
destinationDir(file("$buildDir/libs"))
}
Your task simply has nothing to do.
Gradle tasks are only executed, if they have something to do. If there is nothing to do (e.g. no files to zip), a task is skipped. There are multiple reasons for a task to have nothing to do. The main reason are up-to-date checks to prevent Gradle from doing the same thing what was done during the last invocation, at least as long as the task outputs are still available and the task inputs did not change.
However, in your specific case, the task has nothing to do, because at the time the task executes, no input files are specified at all. Files to include into the zip file can be added via from, which you do use. But, you use it inside a doLast closure, which is executed after the task actions (where the zipping takes place) were executed. Therefor, when running the zip process, the configuration did not take place.
You could use the regular configuration closure of the task or even the doFirst closure to configure your task:
task zipGui(type: Zip) {
from 'assets/htdocs'
}
// OR
task zipGui(type: Zip) {
doFirst {
from 'assets/htdocs'
}
}
Some more remarks:
The line FileTree zip = zipTree('assets/htdocs/gui.zip') does absolutely nothing, because the created file tree is not queried. You need to either access the files of the file tree manually or pass it to a task, e.g. your zip task: from zipTree('assets/htdocs/gui.zip')
In the second example, the task is executed because for normal tasks there are no automatic checks if there is any work to do, since a normal task can basically do everything the user makes it do.
I'm not quite sure, but I think the second example should throw an exception, because a normal task without a type does not provide a from method.
To get the status of each executed task and more insight generally, use the command line parameter --console=plain. The parameters -d and --stacktrace / --full-stacktrace may be used to get more information on errors.

Gradle zip: how to include and rename one file easily?

Create a zip adding file "hello/world.xml" under directory "foo/bar" as "hello/universe.xml"
task myZip(type: Zip) {
from ("foo/bar") {
include "hello/world.xml"
filesMatching("hello/*") {
it.path = "hello/universe.xml"
}
}
}
filesMatching(...) will impact performance obviously.
What is a better way? like:
task myZip(type: Zip) {
from ("foo/bar") {
include ("hello/world.xml") {
rename "hello/universe.xml"
}
}
}
But rename is not supported with include.
I don't get why you are using filesMatching at all. You are only including one single file in your child CopySpec. Simply rename it and everything is fine:
task myZip(type: Zip) {
from ('foo/bar') {
include 'hello/world.xml'
rename { 'hello/universe.xml' }
}
}
If you want to include multiple files (or even copy all), but only want to rename one of them, specify which file(s) to rename with a regular expression as first argument:
task myZip(type: Zip) {
from 'foo/bar'
rename 'hello/world.xml' 'hello/universe.xml'
}
If the last one did not work, try:
rename ('a.java', 'b.java')

Weird behaviour of a simple gradle copy task

I am completely baffled by the gradle behaviour of a Copy task into multiple directories.
I intend to copy all files from src/common into
target/dir1/ps_modules
target/dir2/ps_modules
target/dir3/ps_modules
Below is how my build.gradle looks:
project.ext.dirs = ["dir1", "dir2", "dir3"]
// ensures that ps_modules directory is created before copying
def ensurePsModulesDir() {
dirs.each {
def psModules = file("target/$it/ps_modules")
if (!(psModules.exists())) {
println("Creating ps_modules directory $psModules as it doesn't exist yet")
mkdir psModules
}
}
}
task copyCommons(type: Copy) {
doFirst {
ensurePsModulesDir()
}
from("src/common")
dirs.each {
into "target/$it/ps_modules"
}
}
The result of running the command ./gradlew copyCommons is completely weird.
The folder creation works as expected, however, the contents/files are copied only in the target/dir3/ps_modules directory. The rest two directories remain empty.
Any help would be appreciated.
Below is the screen grab of target directory tree once the job is run:
I think you want to do something like:
task copyCommons(type: Copy) {
dirs.each {
with copySpec {
from "src/common"
into "target/$it/ps_modules"
}
}
}
I think you can get rid of the ensurePsModulesDir() with this change
* edit *
it seems that the copy task is forcing us to set a destination dir. You might think that setting destinationDir = '.' is ok but it's used in up-to-date checking so likely the task will NEVER be considered up-to-date so will always run. I suggest you use project.copy(...) instead of a Copy task. Eg
task copyCommons {
// setup inputs and outputs manually
inputs.dir "src/common"
dirs.each {
outputs.dir "target/$it/ps_modules"
}
doLast {
dirs.each { dir ->
project.copy {
from "src/common"
into "target/$dir/ps_modules"
}
}
}
}
You can configure a single into for a task of type Copy. In this particular example gradle behaves as expected. Since dir3 is the last element on the list it is finally configured as a destination. Please have a look at this question - which you can find helpful. Also this thread might be helpful as well.

Using a wildcard in gradle copy task

I'd like to copy a directory using a wildcard, however the from method of the Gradle copy task doesn't accept a wildcard.
// this doesn't work
task copyDirectory(type: Copy) {
from "/path/to/folder-*/"
into "/target"
}
// this does
task copyDirectory(type: Copy) {
from "/path/to/folder-1.0/"
into "/target"
}
Just use 'include' task property to specify exact files ot directories you need to copy, something like this:
task copyDirectory(type: Copy) {
from "/path/to/"
include 'test-*/'
into "/target"
}
Update: if you want to copy only directories content, then you have to deal with every file separately, something like this:
task copyDirectory(type: Copy) {
from "/path/to/"
include 'test-*/'
into "/target"
eachFile {
def segments = it.getRelativePath().getSegments() as List
it.setPath(segments.tail().join("/"))
return it
}
includeEmptyDirs = false
}

Resources