Indirection / lazy evaluation for copy spec filter properties

Is there a way to achieve lazy evaluation for a CopySpec’s filter method’s properties?

The value I’m using there is the runtime configuration’s resolved files, which are missing the transitive dependencies of a project dependency, if it is resolved during configuration phase.

So I’d be thankful for any hint that tackles this problem by the lazy evaluation of the properties, or by making early resolving of the configuration possible.

Can you provide some code that illustrates the problem. Just want to be sure.

There may be a less drastic way, but a technique to be aware of is to use a “configuration task”

task configureSomeCopy << {
  someCopy {
    with someCopySpec
  }
}
  task someCopy(type: Copy, dependsOn: configureSomeCopy)

That’s a general technique for delaying configuration until execution time. It has some caveats though, the most significant of which is that it defeats task dependency inference.

Luke, thanks for that technique, that works fine. If there’s a more elegant method, I’d still be interested though. Here’s my original code, it is the classpath()-Method that resolves the configuration.

task osxCopy(type:Copy) {

from("…/install/client/install/osx"){

include “**/Info.plist”

filter(ReplaceTokens,tokens:[

CLASSPATH:macClassPath()

])

} }

def classpath() {

configurations.runtime.files.collect {File file-> stripVersion(file.name) } }

def macClassPath() {

def classpath = classpath()

classpath << stripVersion(project.jar.outputs.files.getSingleFile().name)

StringWriter writer = new StringWriter()

MarkupBuilder xml = new MarkupBuilder(writer)

classpath.sort().each() { val -> xml.string(’$JAVAROOT/lib/’ + val ) }

writer.toString() }

well actually I didn’t get it to work yet, I still don’t know to delay reading FilterReader properties (or add the filter later, this is actually a more complex archive task, with several nested CopySpecs.

Ok, now I understand a little more.

The best thing to do would be to subclass ‘ReplaceTokens’ and allow it to take a type of factory for the tokens, so the generation can be delayed until execution time.

Thanks, sounds like the way to do this properly. I did succeed with the configuration task by now (adding the whole child spec, not just the filter), but it is quite hacky and blows up the build script.

Unfortunately ReplaceTokens is final. Is there any other way to accomplish this?

I came up with a DeferredReplaceTokens class to solve this issue:

import org.apache.tools.ant.filters.ReplaceTokens
  /**
 * Like ReplaceTokens but with a deferred evaluation of the tokens Hashset to allow
 * for generation after dynamic properties like project version have been evaluated.
 * Use it like this:
 * filter(DeferredReplaceTokens, tokenGenerator: { return createHashSetFromProperties() })
 * The tokenGenerator closure will be invoked as late as possible (when the filter runs)
 */
class DeferredReplaceTokens extends FilterReader {
      /**
     * This is the closure that is expected to return something that can be converted to a Hashset
     */
    def tokenGenerator
    FilterReader actualReader
      public DeferredReplaceTokens(Reader reader) {
        super(reader)
    }
      /**
     * On-demand creation of the actual ReplaceToken instance
     * @return The reader we delegate to
     */
    FilterReader reader() {
        if (actualReader == null) {
            actualReader = new ReplaceTokens(this.in)
            Hashtable tokens = tokenGenerator()
            // setTokens is really private, but all gradle example code
            // use it like it's public so I will as well
            actualReader.tokens = tokens
        }
          return actualReader
    }
      @Override
    int read(char[] cbuf, int off, int len) throws IOException {
        return reader().read(cbuf, off, len)
    }
      @Override
    int read() throws IOException {
        return reader().read()
    }
      @Override
    void close() throws IOException {
        reader().close()
    }
  }

Use like this:

from(project.file('build-info.txt', PathValidation.FILE)) {
                filter(DeferredReplaceTokens, tokenGenerator: { someCodeThatGeneratesAHash() })
            }
1 Like

Why don’t you just use ‘doFirst {}’ to invoke ‘filter()’?