Depending on additional artifacts

Short version, I have some projects containing SOAP WSDL definitions and (among other things) a task which generates HTML documentation. And I have another project (a war) into which I want to deploy the generated documentation.

Here the are my build scripts… as much as possible, they’re based on this documentation, page with a little bit mixed in from other sources:

First, the code for the projects which produce the documentation, in the form of a custom plugin. I’ve omitted the implementation of the task itself, but it basically takes a list of WSDL files as input, and produces a file in the output directory for each.

def generateSoapDocs = project.tasks.register('generateSoapDocs', steps.build.soapdocs.tasks.GenerateSOAPDocsTask) {
    group = 'build'
    description = 'Generate SOAP WS docs'
 
    //Default the input files and output directory; no configuration needed.
    inputFiles.convention(fileTree(dir: "$projectDir/src/main/resources/service", include: '*/*.wsdl'))
    outputDirectory.convention(project.layout.buildDirectory.dir("ws-docs"))
}

configurations {
    soapDocsProducer {
        canBeResolved = false
        canBeConsumed = true
        extendsFrom implementation
        attributes {
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, "soap-ws-docs"))
        }
    }
}

artifacts {
    soapDocsProducer (generateSoapDocs) {
        builtBy generateSoapDocs
    }
}

Second, the consumer side. I’m not actually doing anything with the outputs just yet, just printing them out:

configurations {
    soapDocs {
        canBeConsumed = false
        canBeResolved = true
        attributes {
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, "soap-ws-docs"))
        }
    }
}

dependencies {
    soapDocs project(":soap-component1")
    soapDocs project(":soap-component2")
}

tasks.register('packageSoapDocs') {
    doLast {
        //I don't really understand this part... I lifted it from some material on report aggregation.
        configurations.soapDocs.incoming.artifactView { lenient(true) }.files.each { println it }

    }
}

My issue is that when I run the packageSoapDocs task against the war project, it correctly prints the paths to the ws-docs directories for each project included in the soapDocs configuration — but they don’t exist… the generateSoapDocs task isn’t run.

I’m guessing there’s something trivial that I’m missing, but I haven’t been able to figure it out on my own… my expectation was that the builtBy generateSoapDocs instruction on the artifacts block would create some kind of implicit dependency on the task, but that doesn’t seem to be the case.

Simon.

Actually, the builtBy generateSoapDocs should not be needed in your case, as you already use the task and thus its outputs as artifact and thus automatically have that task dependency. The builtBy is only necessary if you only add some of the outputs of a task that do not itself carry the task dependency.

Also the lenient artifact view should not be necessary as you only configure projects that actually produce the output you want.
If you would for example depend on all projects in the build but only some produce the wanted output, you would need a lenient artifact view as the lenient means that it takes what is found but does not fail if any of the dependencies does not produce the wanted artifact.
So in your case, configuring the soap components explicitly, it is imho even bad to use a lenient artifact view as you then do not get an error if one of the configured dependencies does not produce the artifact.
So you should be able to just use configurations.soapDocs.files or similar.

That you miss the task dependency is caused by the fact, that you do not declare that configuration (or artifact view) as input for your packageSoapDocs task, but just try to resolve it at execution time.
If you work with an ad-hoc task there as you do in your example, define inputs.files(configurations.soapDocs) or if it is actually a proper task with an input property with input annotation, wire the configuration as its contents.
Then you should get the implicit task dependency properly.

Perfect… I knew it was going to be something simple. The description makes sense… I’m getting the list of directories because that information is available without running the producer tasks, but because I’m doing it inside the consumer task execution, it’s too later for Gradle to register the implicit dependencies to force the producers to run.

And yes, the code runs fine without the builtBy, and without the lenient. Your explanation on the latter has cleared up a lot of confusion about some of the configuration code I copied from one of the sample projects in the docs… so I probably need to go back and review some of the things I’ve done.

Again, thanks for the help.

1 Like