How to resolve warning: Execution optimizations have been disabled for task 'xyz' to ensure correctness

Hello,

we want to upgrade one of our plugins that defines a custom Gradle task for Gradle 7. At runtime, we are currently getting these warnings:

Execution optimizations have been disabled for task ':xyz' to ensure correctness due to the following reasons:
  - Property 'taskConfig.$0.basePackage' was loaded with an unknown classloader (class 'java.lang.String'). Reason: Gradle cannot track the implementation for classes loaded with an unknown classloader. Please refer to https://docs.gradle.org/7.5.1/userguide/validation_problems.html#implementation_unknown for more details about this problem.

The Gradle task defines taskConfig as follows:

@Nested
final ListProperty<Map> taskConfig = project.objects.listProperty(Map)

I have read the documentation referred to by the warning but that wasn’t helpful either. It just makes this rather generic statement:

For the case where Gradle cannot track the implementation because it was loaded by a classloader unknown to Gradle, you can use Gradle’s built-in ways to load the class.

What are Gradle’s built-in ways to load a class and how can we make sure that Map and String are loaded by a class loader that Gradle monitors?

I made an attempt at reproducing what you are seeing but was unable to. Could you create a complete, minimal example that demonstrates it?

For reference, here’s what I did based on what I think you’re doing.
Sorry if you’re not experienced with kotlin/kotlin Gradle DSL.

interface Greeting {
    @get: Input
    val stringOne: Property<String>

    @get: Input
    val stringTwo: Property<String>
}

abstract class Greet : DefaultTask() {
    @get: Nested
    abstract val greetings: ListProperty<Greeting>

    @get: OutputFile
    abstract val outFile: RegularFileProperty

    @TaskAction
    fun go() {
        outFile.get().asFile.writeText(greetings.get().joinToString(separator = "\n") {
            "${it.stringOne.get()} ${it.stringTwo.get()}"
        }, StandardCharsets.UTF_8)
    }
}

val qwe by tasks.registering(Greet::class) {
    val newInstance = objects.newInstance(Greeting::class)
    newInstance.stringOne.set("hello")
    newInstance.stringTwo.set("world")

    val newInstance2 = objects.newInstance(Greeting::class)
    newInstance2.stringOne.set("hello")
    newInstance2.stringTwo.set(providers.fileContents(layout.projectDirectory.file("hello.txt")).asText)

    greetings.add(newInstance)
    greetings.add(newInstance2)

    outFile.set(layout.buildDirectory.file("greet.txt"))
}

The following is an extraction of the relevant parts of our custom plugin/task. Not sure whether this still makes up a minimal working example (I didn’t had the time to test it):

class OurExtension {
	final ListProperty<Map> models

	OurExtension(Project project) {
		models = project.objects.listProperty(Map)
	}
}

class OurTask extends DefaultTask {
	@Nested
	final ListProperty<Map> taskConfig = project.objects.listProperty(Map)
}

class OurPlugin implements Plugin<Project> {
	@Override
	void apply(final Project project)
	{
		def extension = project.extensions.create("ourExt", OurExtension, project)

		project.task('ourTask', group: 'Build', type: OurTask, description: '...') {
			taskConfig = extension.models
		}
	}
}

I guess it has to do with how the Map instances are created. We are nowhere doing this explicitly in our code. It must be done by Gradle itself based on what is specified by a build script.

Side note, this is the runtime environment:

------------------------------------------------------------
Gradle 7.5.1
------------------------------------------------------------

Build time:   2022-08-05 21:17:56 UTC
Revision:     d1daa0cbf1a0103000b71484e1dbfe096e095918

Kotlin:       1.6.21
Groovy:       3.0.10
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          17.0.4.1 (Oracle Corporation 17.0.4.1+1-LTS-2)
OS:           Mac OS X 12.6.1 x86_64

Sorry, when I saw it complaining about property “taskConfig.$0.basePackage” I thought Map in your code wasn’t actually a “Map” but instead your own type with properties annotated with inputs (like I did with Greeting).

I can reproduce what you’re seeing using an actual map (I used Map<String,String>). @Nested should not be needed in this scenario since the list does not contain maps with values that are “gradle types” with appropriately annotated input properties.

However, if the map is something like Map<String, Greeting>, then using @Nested can be used and Gradle will process the annotated properties of the map values.

Here’s my revised test that uses @Nested:

interface Greeting {
    @get: Input
    val stringOne: Property<String>

    @get: Input
    val stringTwo: Property<String>
}

abstract class Greet : DefaultTask() {
    @get: Nested
    abstract val greetings: ListProperty<Map<String, Greeting>>

    @get: OutputFile
    abstract val outFile: RegularFileProperty

    @TaskAction
    fun go() {
        outFile.get().asFile.writeText(greetings.get().joinToString(separator = "\n") { m ->
            m.map { e ->
                "${e.key}=>${e.value.stringOne.get()} ${e.value.stringTwo.get()}"
            }.joinToString(separator = " | ")
        }, StandardCharsets.UTF_8)
    }
}

val greet by tasks.registering(Greet::class) {
    val newInstance = objects.newInstance(Greeting::class)
    newInstance.stringOne.set("hello")
    newInstance.stringTwo.set("world")

    val newInstance2 = objects.newInstance(Greeting::class)
    newInstance2.stringOne.set("hello")
    newInstance2.stringTwo.set(providers.fileContents(layout.projectDirectory.file("hello.txt")).asText)

    val m = HashMap<String, Greeting>()
    m2["first"] = newInstance
    m2["second"] = newInstance2
    greetings.add(m)

    outFile.set(layout.buildDirectory.file("greet.txt"))
}

When running ./gradlew greet -i a second time after changing hello.txt, the task is out of date because:

Task ':greet' is not up-to-date because:
  Value of input property 'greetings.$0.second.stringTwo' has changed for task ':greet'

I’m not sure how you could be populating the list with maps that are never created. How are you configuring ourExt.models?

I can reproduce what you’re seeing using an actual map (I used Map<String,String> ). @Nested should not be needed in this scenario since the list does not contain maps with values that are “gradle types” with appropriately annotated input properties.

I will try without the @Nested annotation. Let’s see if the warning disappears.

I’m not sure how you could be populating the list with maps that are never created. How are you configuring ourExt.models ?

Like so:

ourExt {
  models = [
      [
          package: 'a.b.c',
          path: 'build/generated/foo.blah',
          versionSymbol: 'foo'
      ],
      [
          package: 'a.b.c',
          path: 'build/generated/bar.blah',
          versionSymbol: 'baz'
      ]
  ]
}