User Guide: Need elaboration of nested copyspecs or "Using the CopySpec class"

Section 16.6.3, “Using the CopySpec class”, could use some more elaboration. I need to understand it better before I can write some additional information.

The entire section has a short piece of text: “Copy specs form a hierarchy. A copy spec inherits its destination path, include patterns, exclude patterns, copy actions, name mappings and filters.”

And one example labeled “Nested copy specs”:

task nestedSpecs(type: Copy) {
into 'build/explodedWar'
exclude '**/*staging*'
from('src/dist') {
include '**/*.html'
}
into('libs') {
from configurations.runtime
}
}

That’s the entire section.

Is this example even demonstrating an inheritance hierarchy, as alluded to in the text?

What more can be said about “Using the CopySpec class” that makes sense to describe here?

Yes, it’s not terribly clear. What is being portrayed here is that there is a “main” spec configured on the copy task, and then additional specs are added that inherit from that spec. In the example, the first two statements configure the main spec:

into 'build/explodedWar'
exclude '**/*staging*'

Then the other statements set up additional specs that inherit the configuration of that main spec (i.e. destination directory and excludes). So for instance, the second “into” statement specifies “libs” as the destination, which because of the spec inheritance, is relative to the main spec (ie “build/explodedWar/libs”).

Note that inheritance refers to how specs inherit configuration from their parent specs - not class inheritance.

Perhaps a better way to present this would be to nest it a little more - something like:

into('libs') {
  into('runtime') {
    from configurations.runtime
  }
  into('compile') {
    from configurations.compile
  }
}

Wow. I find the semantics of successive “into” calls very surprising. I never would have guessed that. Your additional example is understandable, and those semantics make sense in that case. It’s surprising to see nested semantics for things that aren’t even physically nested (as the two “into” calls are visually peers, not nested).

On the other hand, I suppose if peer “into” calls had any semantics at all, I guess nesting them makes more sense than anything else. I suppose you could treat them as “additive”, which simply means that you’re copying the files to multiple places.

Actually, perhaps you could clarify what your example is doing. Is that copying from “configurations.runtime” to “libs/runtime” and also “configurations.compile” to “libs/compile”? Logically, that would make sense. If that’s what it’s doing, then that makes the comparison with the first example even more confusing, as now we have two peer “into” calls that are NOT nested.

1 Like

Maybe what’s not clear is the difference between the first into statement:

into 'build/explodedWar'

And the second nested into:

into('libs') { ... }

The first configures the “main” spec for the copy spec. The second creates a new copy spec as a child of the main spec and configures it according to the closure. It’s not because they are successive - it is because the first does not provide a closure with a spec configuration, therefore the copy task treats it as configuration of the main spec (i.e. configuring the destination dir) rather than creating a child spec. If you added another into statement without a closure, it would just configure the destination dir of the main spec again.

So, the second into statement in the example more explicitly demonstrates the nested nature of copy specs by visually nesting them (yes, compile and runtime are peers under the lib directory).

Does that make more sense?

I still want additional clarification of the semantics of your example. Is that copying from “configurations.runtime” to “libs/runtime” AND also from “configurations.compile” to “libs/compile”, but NOT from “configurations.compile” to “libs/runtime” or from “configurations.runtime” to libs/compile"?

If I had the following:

into('libs') {
  into('runtime')
  into('compile') {
    from configurations.compile
  }
}

Would this copy to “libs/runtime” and also from “configurations.compile” into "libs/compile?

The key part is the nested “into” without a closure. Based on your explanation, I’m not sure exactly what that will do.

Yes to your question - it would put configurations.compile in compile and configurations.runtime in runtime and nothing else.

In your hypothetical example, the first into(‘libs’) would create a new child spec named “libs”, then the second into(‘runtime’) would reset the output dir of the “libs” spec to “runtime” (because it does not provide a spec configuration as a closure argument). Then the third into would create a new child spec (under the “libs-turned-runtime” spec) with a destination dir of “compile”. You would end up with configurations.compile in “runtime/compile” under whatever the destination dir of the main spec of the copy task was.

So truly, copyspec inheritance seems to have no relation to whether the copyspec is visually nested within another one. A non-nested one can inherit the one that comes before it, and a nested one can replace the one it is nested within. Very curious.