Task dependency struggle with code generating plugin

I wrote a plugin generating Kotlin code (using KotlinPoet) and wanted to include the output of that plugin in the build path.

The Kotlin compilation works fine, though when calling gradle sourcJar build, it tells me

A problem was found with the configuration of task ':generateClasses' (type 'GenerateClassesTask').
  - Gradle detected a problem with the following location: '/Users/.../build/generated/sources/kotlin'.

    Reason: Task ':sourcesJar' uses this output of task ':generateClasses' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.

    Possible solutions:
      1. Declare task ':generateClasses' as an input of ':sourcesJar'.
      2. Declare an explicit dependency on ':generateClasses' from ':sourcesJar' using Task#dependsOn.
      3. Declare an explicit dependency on ':generateClasses' from ':sourcesJar' using Task#mustRunAfter.

    For more information, please refer to https://docs.gradle.org/8.13/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.

After some debugging I found out, that the :sourcesJar task is not yet registered when the plugin is applied. Additionally gradle sourcesJar succeeds (and the jar does not contain the generated sources) while gradle compileKotlin sourcesJar fails with the error above, as if the kotlinCompile task would add the generated sources to the input of the sourceJar task.

Can someone tell me, how I should configure the task dependencies to avoid the error correctly?

(Any yes the workaround is probably to exclude the generated folder from sources Jar, but I would like to understand where the cause of the problem is and to solve it.


Here the frame for the plugin and the task

class SomeGeneratorPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val task = project.tasks.register("generateClasses", GenerateClassesTask::class.java) 

        project.getTasksByName("compileKotlin", false).forEach {
            it.dependsOn(task)
        }

        project.getTasksByName("sourcesJar", false).forEach {
            it.dependsOn(task)
        }

        project.extensions
            .findByType<JavaPluginExtension>()
            ?.sourceSets
            ?.get("main")
            ?.java
            ?.srcDir("build/generated/sources/kotlin")
    }
}

@CacheableTask
open class GenerateClassesTask : DefaultTask() {
    @TaskAction
    fun generate() {
       // writes Kotlin code to outputDir
    }

    @OutputDirectory
    val outputDir: java.io.File =
        project.layout.buildDirectory
            .dir("generated/sources/kotlin")
            .get()
            .asFile

}

Whenever you do an explicit dependsOn where on the left-hand side is not a lifecycle task, you do something wrong.

The suggestions in the error message are actually almost always the wrong thing to do and are just symptom treatment like calling Platform.runLater to “fix” a GUI problem.

You should not manually configure paths, but wire task outputs to task inputs.
In your case you would not do srcDir("path/to/generated/files"), but you would ensure the outputs of your generation task are configured properly and then do srcDir(referenceToTheGenerationTaskOrAProviderThereof), then the outputs are automatically considered the source files and any task that properly consumes sources files including source jar, compilation, static code analysis, and so on all have the necessary task dependency automatically implicitly.

Hey @Vampire, thanks, that worked.

Just for everyone running into the same issue, what I had to change was:

open class GenerateClassesTask : DefaultTask() {
    @TaskAction
    fun generate() {
       // writes Kotlin code to outputDir
    }

    @OutputDirectory
    val outputDir: Provider<Directory> =
        project.layout.buildDirectory
            .dir("generated/sources/kotlin")


}

// and in the Plugin appy()

        project.extensions
            .findByType<JavaPluginExtension>()
            ?.sourceSets
            ?.get("main")
            ?.java
            ?.srcDir(task.map{ it.outputDir)

and then I could remove all these useless dependency declarations.