Passing A Complex Object as an Input to a Custom Task

Hi,

Anyone know of a good example for passing complex objects to a task via a closure or any other method?

Thanks

I’m trying to pass a complex object to a custom task I’m writing and I’m not sure how to handle it.

Goal: create BuildValidationTask that takes a collection of expected output and fails the build if its not present.

I don’t quite know how to write my task so I can pass a single or collection of validation Objects which contain a boolean and two fileTrees each.

Imagined Task Config

task checkBuildOutput(type:BuildValidationTask) {
artifacts {
test { failOnError: false, expected: fileTree(dir: “.”, includes ["/target/surefire-reports/xml"]) }
installer { failOnError: true, expected: fileTree(dir: “.”, includes ["
/target/myProduct.tar.gz"]) }
internalLicense {failOnError: true, prohibited: fileTree(dir: “.”, includes ["
/target/licenseGenerator.exe"]) }
}

complex object to pass to task artifactValidationSpec

class ArtifactValidationSpec {
String name
private FaultType fType
private FileTree expected = null
private FileTree prohibited = null

public ArtifactValidationSpec(String name, boolean failOnError, expected = null, prohibited = null) {
    this.name = name
    if (failOnError) { fType = FaultType.FAIL }
    else             { fType = FaultType.WARN}
    this.expected = expected
    this.prohibited = prohibited
}
public ArtifactValidationSpec(String name, FaultType fType, expected = null, prohibited = null) {
    this.name = name
    this.fType = fType
    this.expected = expected
    this.prohibited = prohibited
}
public String toString() {
    return """${name}[${fType.desc}]

Expected: ${expected.toString()} : ${expected.getFiles()}
Prohibited: ${prohibited.toString()} : ${prohibited.getFiles()}"""
}

/**
 *
 * @return true if expected criteria found
 */
public boolean foundExpected() {
    return expected.getFiles().size() > 0
}
/**
 *
 * @return true if not prohibited criteria found
 */
public boolean foundProhibited() {
    return prohibited.getFiles().size() > 0
}
/**
 *
 * @return true we find expected and not prohibited content
 */
public boolean isValid() {
    return foundExpected() && ! foundProhibited()
}

}

Task

class BuildValidationTask extends MyTask {

@Input
public ArtifactValidationSpec artifact
@TaskAction
public void checkOutput() {
    if (! artifact.foundExpected()) {
        logger.lifecycle("Error: ${artifact.name} missing (spec: ${artifact.toString()}")
    }
    if (! artifact.foundProhibited()) {
        logger.lifecycle("Error: ${artifact.name} prohibited content found (spec: ${artifact.toString()}")
    }
}

}

Ok, this seems to work.

  • Allow construction via closure
  • Create method expecting closure in Task and pass to constructor

Build.Gradle Example

task complexPassTest(type:CheckBuildOutputTask) {
artifact {
description=“installer"
failOnError=true
expected=fileTree(dir: “./build”, includes: [”**/target/dist/myinstall"])
}

Task Code


void artifact(Closure config) {
ArtifactValidationSpec artifact = new ArtifactValidationSpec(config)
logger.lifecycle("*** Here’s the Object = " + artifact.toString())
}

Object To Pass
class ArtifactValidationSpec {
String description
FaultType fType
FileTree expected = null
FileTree prohibited = null
boolean failOnError = false
public ArtifactValidationSpec(Closure config) {
config.delegate = this
config()
def problems = []

    if (null == config.description) {
        problems.add("artifact lacks description configuration")
    }
    this.description = config.description

    if (config.expected != null) {
        this.expected = config.expected
    }

    if (config.prohibited != null) {
        this.prohibited = config.prohibited
    }

    // file Trees - require at least one
    if ( (null == this.expected) && (null == this.prohibited) ) {
        problems.add("""artifact lacks both expected and prohibited filetrees.  We need at least one.""")
    }

    // Convert boolean into FaultType
    if (config.failOnError) {
        this.fType = FaultType.FAIL
    } else {
        this.fType = FaultType.WARN
    }

    // Throw on problems found
    if (problems.size() > 0) {
        throw new GradleException("""Misconfigured Artifact:

Artifact:
${this.toString()}

Example:
artifact {
description = “installer"
expected= fileTree(dir: “./build”, includes: [”**/target/dist/AIEinstall"])
failOnError = true /* Defaults to false */
}

“”" + problems.join("\n\t"))
}

}

You can use project.configure() to accomplish the same thing. With the solution above a subsequent artifact { } closure would overwrite existing configuration. It would usually only make sense to have the artifact(Closure) method create a new object if that object was being appended to a collection (ex. calling from() on the Copy task).

def artifact = new ArtifactValidationSpec()

void artifact(Closure config) {
    project.configure(artifact, config)    
}
1 Like

Thanks Mark, that’s what I’m trying to do in the above example. To have multiple artifact clauses each which creates an artifact validation rule that gets added to a collection.

Alternatively, you could also annotate the class ArtifactValidationSpec with input and output annotation.

class ArtifactValidationSpec {
    @Input
    String description

    ...
}

Then in the task annotate the instance of ArtifactValidationSpec with the Nested annotation.

class CheckBuildOutputTask extends DefaultTask {
    @Nested
    ArtifactValidationSpec artifact = new ArtifactValidationSpec()

    def artifact(Closure config) {
        project.configure(artifact, config)    
    }
}

The benefit of this approach is that you won’t have to implement the validation logic you have in place. It’s already built-in.

2 Likes