Custom gradle task that accesses top-level fails with "The constructor ... should be annotated with @Inject"

Viewed 1508

I'm trying to create a re-usable gradle task that accesses something from the top level. This works:

val foo = 42

tasks.register("foosimple") {
    println(foo)
}

But using a modified example from Developing Custom Gradle Task Types does not:

open class Foo : DefaultTask() {
    @TaskAction
    fun foo() {
        println(foo)
    }
}
tasks.register<Foo>("foo")

which fails with:

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'keycloak-spi'.
> Could not create task ':foo'.
   > Could not create task of type 'Foo'.
      > The constructor for type Build_gradle.Foo should be annotated with @Inject.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

Can somebody shed some light on what I'm doing wrong here? If I replace foo()'s content with println(42) it works, of course. But I actually want to access something from the top level...

Further Details

--stacktrace wasn't helpful to me:

* Exception is:
org.gradle.api.ProjectConfigurationException: A problem occurred configuring root project 'keycloak-spi'.
        at org.gradle.execution.TaskNameResolver.getExistingTask(TaskNameResolver.java:116)
        at org.gradle.execution.TaskNameResolver.access$000(TaskNameResolver.java:32)
        at org.gradle.execution.TaskNameResolver$MultiProjectTaskSelectionResult.collect(TaskNameResolver.java:177)
        at org.gradle.execution.TaskNameResolver$MultiProjectTaskSelectionResult.collectTasks(TaskNameResolver.java:169)
        at org.gradle.execution.TaskNameResolver.selectWithName(TaskNameResolver.java:55)
        at org.gradle.execution.TaskSelector.getSelection(TaskSelector.java:104)
        at org.gradle.execution.TaskSelector.getSelection(TaskSelector.java:82)
        at org.gradle.execution.commandline.CommandLineTaskParser.parseTasks(CommandLineTaskParser.java:42)

I therefore tried to add an @Inject annotation on the constructor a instructed, but apparently, just like me, gradle doesn't know @Inject:

open class Foo @Inject constructor()  : DefaultTask() {
    @TaskAction
    fun foo() {
        println(foo)
    }
}

which fails with:

Line 70: class Foo @Inject constructor() {}
                  ^ Unresolved reference: Inject

This was the only suggestion by Intellij:

open class Foo @javax.inject.Inject constructor()  : DefaultTask() {
    @TaskAction
    fun foo() {
        println(foo)
    }
}

Now it fails with:

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'keycloak-spi'.
> Could not create task ':foo'.
   > Could not create task of type 'Foo'.
      > 0

So now it accepts the @Inject annotation, but still doesn't work. I now give up and ask you guys...

1 Answers

Kotlin compiler adds secretly a parameter to generated Foo constructor every time you use variables from the build script. That parameter is a reference to the enclosing build script.

As a result the task class has no parameter-less constructors and Gradle assumes then that dependency injection should kick in. But it cannot as generated constructor Foo(BuildScript_XXX) has no @Inject annotation.

If you want to use script variables in your task you should use input properties, for example:

val foo = 42

open class Foo : DefaultTask() {
    @Input
    var fooToPrint: Int = 0

    @TaskAction
    fun foo() {
        println(fooToPrint)
    }
}
tasks.register<Foo>("foo") {
    fooToPrint = foo
}
Related