Publishing dynamic task outputs

Hi,
I’m trying to publish an artifact generated by a custom task that produces an RPM file, please see the code below. The artifact should get its file based on a matching file under a directory where the file name is computed dynamically, but the provider is fetched immediately and is not lazily computed.

The code below describes two attempts at doing this:

  • rpmArtifactFromTask - uses a task output file property
  • rpmArtifactManual - defines the file property outside of the task

The task fails for most tasks, including gradle dependencies, with the following error message:

Expected directory 'rpm-output/RPMS/x86_64/' to contain exactly one file, however, it contains no files.

When running rpmArtifactFromTask alone and manually placing the RPM file in the proper place the publishing plugin produces a NullPointerException without any explanation why this happens:

Execution failed for task ':publishRpmPublicationToRepoRepository'.
> Failed to publish publication 'rpm' to repository 'repo'
   > java.lang.NullPointerException (no error message)

I’m familiar with Nebula Gradle ospackage plugin, I’m trying to learn how to use providers and the publishing plugin.

I have several questions:

  1. How can this be achieved using providers? both for tasks and manually as in rpmArtifactManual
  2. Can this be done only by implementing a task and not through the task runtime API?
  3. Why does publish plugin produce a NullPointerException in this case?

Thanks,
Jon

apply plugin: 'base'
apply plugin: 'maven-publish'
group 'sample'
version '0.0.1'

configurations {
    archives
}

tasks.register('prepare') {
    doLast {
        mkdir("${buildDir}/rpm-buildroot")
        mkdir("${buildDir}/rpm-output")
        // prepare directory ${buildDir}/rpm-buildroot for rpmbuild
    }
}

tasks.register('rpm', Exec) {
    dependsOn('prepare')
    inputs.dir("${buildDir}/rpm-buildroot")

    ext.outputFile = layout.buildDirectory.file(project.getProviders().provider({
        fileTree("${buildDir}/rpm-output/RPMS/x86_64").matching({ include '*.rpm' }).getSingleFile().getAbsolutePath()
    }))
    outputs.file(ext.outputFile)

    executable = 'ls' // for testing only, real task runs 'rpmbuild' and outputs to rpm-output/RPMS/x86_64
    args = []
}

def rpmArtifactFromTask = artifacts.add('archives', rpm.outputFile) {
    type 'rpm'
    extension 'rpm'
    classifier 'rpm'
    builtBy(tasks.named('rpm'))
}

def rpmFile = project.objects.fileProperty()
rpmFile.set(layout.buildDirectory.file(project.getProviders().provider({
    fileTree("${buildDir}/rpm-output/RPMS/x86_64/").matching({ include '*.rpm' }).getSingleFile().getAbsolutePath()
})))

def rpmArtifactManual = artifacts.add('archives', rpmFile) {
    type 'rpm'
    builtBy(tasks.named('rpm'))
}

publishing {
    repositories {
        maven {
            name = 'repo'
            url '../repo'
        }
    }

    publications {
        rpm(MavenPublication) {
            artifact(rpmArtifactManual) {
                builtBy(tasks.named('rpm'))
            }
            artifact(rpmArtifactFromTask) {
                builtBy(tasks.named('rpm'))
            }
        }
    }
}