I have the following code. Its purpose is to copy the rpm built by the task to the parent build directory. I want all the rpms to exist there.
doLast {
println ("copying $archiveName to $project.parent.buildDir")
Set<File> fs = outputs.getFiles().getFiles()
println "outputs: $fs"
project.copy {
into project.parent.buildDir {
from this.outputs
}
}
}
Both print statements work and show me exactly what I expected them to:
copying gvp008-1.0.0-as.noarch.rpm to /home/sc1478/gvp008/build
outputs: [/home/sc1478/gvp008/audio/build/distributions/gvp008-1.0.0-as.noarch.rpm]
Yet the execution of the copy statement fails with:
aused by: java.lang.NullPointerException
at org.gradle.api.internal.file.IdentityFileResolver.doResolve(IdentityFileResolver.java:39)
at org.gradle.api.internal.file.AbstractFileResolver.resolve(AbstractFileResolver.java:81)
at org.gradle.api.internal.file.AbstractFileResolver.resolve(AbstractFileResolver.java:63)
at org.gradle.api.internal.file.AbstractFileResolver.withBaseDir(AbstractFileResolver.java:59)
at org.gradle.api.internal.file.DefaultFileLookup.getFileResolver(DefaultFileLookup.java:41)
at org.gradle.api.internal.file.copy.FileCopier.getCopyVisitor(FileCopier.java:59)
at org.gradle.api.internal.file.copy.FileCopier.copy(FileCopier.java:49)
at org.gradle.api.internal.file.DefaultFileOperations.copy(DefaultFileOperations.java:134)
at org.gradle.api.internal.project.AbstractProject.copy(AbstractProject.java:776)
at org.gradle.api.internal.project.AbstractProject.copy(AbstractProject.java:772)
at org.gradle.api.Project$copy$5.call(Unknown Source)
at com.att.voicetone.gradle.plugins.rpm.AttVtRpmTask$_applyConventions_closure1.doCall(AttVtRpmTask.groovy:141)
Ok. @David_Karr provided the solution outside the forum channel. Thanks, David!
The solution is simply to un-nest the from clause from the into clause:
doLast {
project.copy {
into project.parent.buildDir
from this.outputs
}
}
as opposed to
doLast {
project.copy {
into project.parent.buildDir {
from this.outputs
}
}
}
Which raises the question of what the difference is? Why does the nested syntax often fail? (I have found it to be temperamental). And yet it seems to more clearly represent intent, especially when there are two “into’s” representing different destination directory trees.
Can someone explain or point to an explanation of the differences?
I think this is most easily explained with an example:
copy {
into('target') // the base dir
into('sub') { // a nested copy spec
from 'foo.txt'
}
}
will result in `target/sub/foo.txt’
The problem you were seeing is caused by the copy spec not having a base dir. The error message leaves a lot to be desired though. I’ve opened GRADLE-3406 for that.
Yes. The behavior of the other syntax is undefined at best.
Alternatively you can use a comma to separate method argument to get the same result. The end goal being that we are passing two arguments to the into() method, a string and a closure.
I’m not certain this would have fixed your problem because as @st_oehme mentioned, the copy spec would still be missing a base target directory. We explicitly validate this when using a Copy task but not when calling project.copy() for some reason.
I confirm that it doesn’t help. Only the non-nested form seems to work here.
But I’m not grokking what you’re both trying to tell me. Are you saying that two into’s are required, the first to set the base dir and the second some directory within it? My poor newbie brain wants to think that project.parent.baseDir IS the base directory and the files the from spec matches go there.
Quite simply, every copy operation needs a call to into() w/o a closure parameter. This is how the base directory get set. Passing a closure parameter creates a child copy spec of the root spec, leaving he base directory of the root spec null. When using the Copy task you get the following error instead of the NPE you get when using Project.copy(). We should fix project.copy so that it gives a similar message.
> No value has been specified for property 'destinationDir'.
doLast {
project.copy {
into (project.parent.buildDir)
into ('.') {
from this.outputs
}
}
}
Besides fixing the error message, may I make another suggestion? This overloaded “into” is quite confusing. Why not call the first “baseDir” to eliminate the confusion. Or simply assume that the first “into” sets the base directory? If into('.') is necessary and effective, it says to me that something could be improved somewhere.
which is what I had back in post 2 above. Yes, I know that works. I am trying to understand why there’s such a big difference between nested and un-nested copy specs.
Why does gradle know in this
project.copy {
into project.parent.buildDir
from this.outputs
}
that project.parent.buildDir is the base directory but in this
project.copy {
into project.parent.buildDir {
from this.outputs
}
}
or even this
project.copy {
into (project.parent.buildDir) {
from this.outputs
}
}
Gradle thinks there is no base directory. This seeming inconsistency is quite illogical. Is there a document that explains this in depth? I haven’t found it.
I explained the point of a sub-copy-spec in my first answer, please have another look at that. Sub-specs are used to define the structure below the basedir so you don’t have to repeat the base dir over and over again. This way you can even define reusable pieces of copy-logic.
Yes, I saw that explanation but still find it confusing. It may make perfect sense to you that one “into” sets the base directory and the other sets a subdirectory within it, but it’s still confusing. I understand how it does work and you don’t need to explain that further, but my point is that the existing syntax is inherently confusing and ought at the very least to be better documented if not improved.
The very first example in the user guide is what you needed. So the question is probably: Is the whole section on copying not prominent enough? Or is there some other way how it could be improved?
On looking it over, the documentation is better than I thought. But I would still improve it.
Ok, here’s how I would improve the JavaDoc (and DSL, which is similar), which now says:
CopySpec
into(Object destPath)
Specifies the destination directory for a copy.
CopySpec
into(Object destPath,
Closure configureClosure)
Creates and configures a child CopySpec with the given destination path.
I might say as the description of the second method
Creates and configures a child CopySpec with the given destination path RELATIVE TO the base directory created by a previous call to into(Object)
Some questions still apply. Does the base directory into() have to come first or can it come later? My wording assumes the base dir into() has to come first.
But the more I try to write the documentation the more firmly my opinion becomes that the two into’s should not share the same name. That is the source of my confusion, anyway. They define two different operations.
This is very unlikely to change as it would have huge backward compatibility consequences. There is likely room for improvement in the documentation here. We encourage contributions in this area.
Understood. But would it be a problem to create a “synonym” for into? I would call it perhaps “based” or something of that nature. Could simply call other into(Object).
At any rate, I have gotten over that hump now but others may face it.