Lazzy access of DefaultCopySpec vs. CopySpecWrapper

I want to understand the differences between DefaultCopySpec and CopySpecWrapper. The follow code has a different behavior depending of the parameter instance class.

ext.ikvm = { CopySpec sources ->
    def result = copySpec {
        sources.eachFile { FileCopyDetails details ->
            println "\tIKVM " + details.relativePath
        }
    }
    return result
}

With a DefaultCopySpec_Decorated it will be executed immediate and with a CopySpecWrapper_Decorated it will executed on first access in the execution phase. It is used in a zip task like:

zip {
    into( 'ikvm' ) with( ikvm( libs ) )
}

The problem with the immediate execution is that it will be execute on definition phase also if the task is not executed. If I do the declaration in a doFirst{} then the task is not executed because the zip is empty before. The result is a simple UP-TO-DATE. A workaround is a to use a task “beforeZip” and “zip” depends on it. But this is ugly for hundred of task.

But it work like expected if the “libs” parameter is a CopySpecWrapper_Decorated.

What is the difference between the both instances?
When I receive the one and one the other class?
Is there a secure solution to convert the one to another type?

This is an implementation Detail that shouldn’t make a difference while using Gradle. The only purpose of CopySpecWrapper is to hide some implementation details. From looking at the code project.copySpec always returns a DefaultCopySpec The snippet you listed above should definitely be evaluated lazy using default Gradle.

cheers,
René

Then this is a bug in Gradle?

Can you create a reproducable small example so I can verify? I don’t know what ‘libs’ is in your snippet and how it is created etc. Maybe it is related to the problem you’re describing here. So a small reproducible gist would be great.

cheers,
René

A simple sample is difficult because a small changes has different behavior. I also does not know if I receive a wrapper and when not. That my question here.

ext.ikvm = { CopySpec sources ->
    return copySpec {
        println "************** IKVM start"
        sources.eachFile { FileCopyDetails details ->
            println "************** IKVM " + details.relativePath
            it.include details.toString()
        }
        into 'ikvm'
    }
}
task sample( type: Zip ) {
    def libs = copySpec {
        from( "${buildDir}/libs" )
    }
    println libs.getClass()
    with libs
    with( ikvm( libs ) )
}

This produce the follow output:
************** IKVM start
:sample
************** IKVM pdfc-server-4.0-javadoc.jar
************** IKVM pdfc-server-4.0-sources.jar

The first println is executed in the definition phase. But the println in eachFile is evaluated on execution. If I remove the “width libs” line then the eachFile is never evaluated.

Ok, investigate more time I see now that I have "eachFile misunderstand completely. It is not an iterator over a copySpec. It is an callback event. Then the question is how can i iterate over a copySpec to create a second copySpec. And this lazzy.