Why checkstyle would depend on files in build output?

I am refactoring an old build config. And while doing so I converted some code to kotlin

    val mockitoExtConfigFiles = listOf(
        mockitoExtensionConfigFile(project, "org.mockito.plugins.MockMaker"),
        mockitoExtensionConfigFile(project, "org.mockito.plugins.MemberAccessor"),
    )

    // This task is used to generate Mockito extensions for testing see ci.yml build matrix.
    val createTestResources by registering {
        outputs.files(mockitoExtConfigFiles)
        doLast {
            // Configure MockMaker from environment (if specified), otherwise use default
            configureMockitoExtensionFromCi(project, "org.mockito.plugins.MockMaker", "MOCK_MAKER")

            // Configure MemberAccessor from environment (if specified), otherwise use default
            configureMockitoExtensionFromCi(project, "org.mockito.plugins.MemberAccessor", "MEMBER_ACCESSOR")
        }
    }
    processTestResources {
        dependsOn(createTestResources)
    }

    val removeTestResources by registering {
        outputs.files(mockitoExtConfigFiles)
        doLast {
            mockitoExtConfigFiles.forEach {
                if (it.exists()) {
                    it.delete()
                }
            }
        }
    }
    test {
        finalizedBy(removeTestResources)
    }
Other methods in build script
fun Task.configureMockitoExtensionFromCi(
    project: Project,
    mockitoExtension: String,
    ciMockitoExtensionEnvVarName: String,
) {
    val mockitoExtConfigFile = mockitoExtensionConfigFile(project, mockitoExtension)
    val mockMaker = project.providers.environmentVariable(ciMockitoExtensionEnvVarName)
    if (mockMaker.isPresent && !mockMaker.get().endsWith("default")) {
        logger.info("Using $mockitoExtension ${mockMaker.get()}")
        mockitoExtConfigFile.run {
            parentFile.mkdirs()
            createNewFile()
            writeText(mockMaker.get())
        }
    } else {
        logger.info("Using default $mockitoExtension")
    }
}

fun mockitoExtensionConfigFile(project: Project, mockitoExtension: String) =
    file("${project.sourceSets.test.get().output.resourcesDir}/mockito-extensions/$mockitoExtension")

This fails because checkstyleTest has a hidden dependency on removeTestResources :

 - Gradle detected a problem with the following location: '/home/runner/work/mockito/mockito/mockito-core/build/resources/test/mockito-extensions/org.mockito.plugins.MockMaker'.
    
    Reason: Task ':mockito-core:checkstyleTest' uses this output of task ':mockito-core:removeTestResources' 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 ':mockito-core:removeTestResources' as an input of ':mockito-core:checkstyleTest'.
      2. Declare an explicit dependency on ':mockito-core:removeTestResources' from ':mockito-core:checkstyleTest' using Task#dependsOn.
      3. Declare an explicit dependency on ':mockito-core:removeTestResources' from ':mockito-core:checkstyleTest' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.10.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.

This looks odd to me because the above config files are only created in the build directory, while checkstyle should operate on the src folders I believe?

Got a reproducer, see point 2.

implicit-dependency-gradle-31353.zip (71.9 KB)

I did not look at your reproducer, just at your post here.
You declare mockitoExtConfigFiles as output files of createTestResources and removeTestResources.
I guess you register the createTestResources task as test source directory so that checkstyle checks its ouputs.
And as you declare the same files as outputs of removeTestResources, Gradle complains that you use the output of that task in the checkstyle task without dependency.

Actually there are some major problems:

  • tasks should never have overlapping outputs if you prefer not to end in hell
  • for removeTestResources it does not even make any sense to declare those as output, as they are not output of that task, that task does not create those files, it destroys them, you should use destroyables instead to register the files the task destroys instead of declaring it as output wrongly
  • practically any explicit dependsOn that does not have a lifecycle-task on the left-hand side is a code smell and usually a sign of you doing something not properly, most often not properly wiring task outputs and inputs together but configuring paths manually or smiliar.