gradlegradle-dependenciesgradle-task

How to use custom task as artifact


I have a simple custom task in a project's (lets call it projectA) build script that creates a file. The task looks like this:

task createFile() {
  def outputFile = rootProject.file("${buildDir}/tmp/outputFile.txt")
  outputs.file(outputFile)
  outputs.upToDateWhen { false }
  doLast {
    outputFile.parentFile.mkdirs()
    outputFile.text = "Hello World"
  }
}

The project is part of a multiproject build and I need the file generated by this task as input for a Copy task in projectB. To achieve this I created a configuration in projectB and a corresponding dependency:

configurations {
  mySourceConfig
}

dependencies {
  mySourceConfig project(path: ':projectA', configuration: 'myDistConfig')
}

task copySourceDependencies(type: Copy) {
   from configurations.mySourceConfig 
   into contextDir
}

In projectA I got a myDistConfig configuration and want to use the output file of the createFile task as artifact for this configruation:

configurations {
  myDistConfig
}

artifacts {
  myDistConfig createFile
}

If I do this, gradle tells me that a task cannot be converted to a ConfigurablePublishArtifact:

> Cannot convert the provided notation to an object of type ConfigurablePublishArtifact: task ':projectA:createFile'.   The
  following types/formats are supported:
     - Instances of ConfigurablePublishArtifact.
     - Instances of PublishArtifact.
     - Instances of AbstractArchiveTask, for example jar.
     - Instances of Provider<RegularFile>.
     - Instances of Provider<Directory>.
     - Instances of Provider<File>.
     - Instances of RegularFile.
     - Instances of Directory.
     - Instances of File.
     - Maps with 'file' key

So I tried createFile.outputs.files.singleFile as artifact. This stops gradle from complaining, but fails to setup the dependency between :projectB:copySourceDependencies and :projectA:createFile and if projectB:copySourceDependencies is executed on a clean workspace it is simply skipped with status NO-SOURCE.

Is it somehow possible to use a custom task as artifact similar to a Zip task, to make gradle aware of the dependency?!


Update 2020-02-28:

Based on the very good answer by @BjørnVester I implemented the following task in a separate gradle file util.gradle:

class CreateFile extends DefaultTask {

  @OutputFile
  RegularFileProperty outputFile = project.objects.fileProperty()

  @TaskAction
  void createFile() {
    def tOutFile = outputFile.get().asFile
    tOutFile.parentFile.mkdirs()
    tOutFile.text = "Hello World"
  }
}

rootProject.ext.CreateFile = CreateFile

In projectA's build.gradle this looks like this:

apply from 'util.gradle'

task createFile(type: CreateFile) {
  outputFile = rootProject.file("${buildDir}/tmp/outputFile.txt")
}

artifacts {
  myDistConfig createFile.outputFile
}

Now gradle is aware of the correct dependencies and it works like a charm!


Solution

  • When using a task as the artifact notation, it has to be of type AbstractArchiveTask. So for other types of tasks, you will have to done something else.

    The approach of using createFile.outputs.files.singleFile is good as it is a supported type (as you can see from the error output). But the reason Gradle doesn't create a dependency to the producing task is because a File does not carry that information. For that, it would have to be a Provider. But a quick way to fix it is to just explicitly configure what task produces the artifact:

    artifacts.add("myDistConfig", createFile.outputs.files.singleFile) {
        builtBy("createFile")
    }
    

    Alternatively, you can also change the outputFile to a Provider<File> or RegularFileProperty using an ObjectFactory as those carry the task producer information. I've never used them as part of the DSL, and it might be more suited for working with classes. (And while that looks like more work, it makes the code more readable once it grows to a certain level.) Here is an example in Groovy:

    class MyFileCreator extends DefaultTask {
      @OutputFile
      RegularFileProperty outputFile = project.objects.fileProperty().convention(project.layout.buildDirectory.file("tmp/outputFile.txt"))
    
      @TaskAction
      void createFile() {
        File outFile = outputFile.get().asFile
        outFile.parentFile.mkdirs()
        outFile.text = "Hello World"
      }
    }
    
    MyFileCreator createFileTask = tasks.create("createFile", MyFileCreator)
    
    configurations {
      myDistConfig
    }
    
    artifacts {
      myDistConfig createFileTask.outputFile
    }