Gradle: Automatic Task dependency resolution for `DirectoryProperty` & `RegularFileProperty` fails in weird cases

Created on 13 Aug 2018  路  3Comments  路  Source: gradle/gradle

The new RegularFileProperty & DirectoryProperty should preserve a dependency relationship from the task that they are outputs on to the tasks they are inputs on. This works fine (in most cases (the exception being the dependencyFails1 case)), but once file or dir is invoked on DirectoryProperty this dependency relationship is lost.

Context

Since DirectoryProperty and RegularFileProperty expose no API mechanism for a user to explicitly declare what task created them (see #6336), I'd expect that the DirectoryProperty::dir and DirectoryProperty::file would keep the relationship from the source task to the task consuming the property.

Expected Behavior

I would expect all of the below cases to create dependencies between out1 and the respective tasks using out1's outputs.

/**
 * Some example task that has two outputs.
 */
@Suppress("LeakingThis")
open class OutputTest : DefaultTask() {

    @get:OutputFile
    val outputFile: RegularFileProperty = newOutputFile()

    @get:OutputDirectory
    @get:PathSensitive(PathSensitivity.RELATIVE)
    val outputDirectory: DirectoryProperty = newOutputDirectory()
}

/**
 * Some example task that has two inputs.
 */
@Suppress("LeakingThis")
open class InputTest : DefaultTask() {

    @get:InputDirectory
    @get:PathSensitive(PathSensitivity.RELATIVE)
    val inputDir: DirectoryProperty = newInputDirectory()

    @get:InputFile
    @get:PathSensitive(PathSensitivity.RELATIVE)
    val inputFile: RegularFileProperty = newInputFile()
}

val out1 =
    tasks.register<OutputTest>("output1")

val dependencyWorks1 =
    tasks.register<InputTest>("dependencyWorks1") {
        inputFile.set(out1.map { it.outputFile }.get())
    }
/*
./gradlew --dry-run dependencyWorks1
:output1 SKIPPED
:dependencyWorks1 SKIPPED

BUILD SUCCESSFUL in 0s
 */

val dependencyWorks2 =
    tasks.register<InputTest>("dependencyWorks2") {
        inputDir.set(out1.map { it.outputDirectory }.get())
    }
/*
./gradlew --dry-run dependencyWorks2
:output1 SKIPPED
:dependencyWorks2 SKIPPED
 */

val dependencyFails1 =
    tasks.register<InputTest>("dependencyFails1") {
        // Notice the subtle shift in where the `get` is here.
        inputDir.set(out1.map { it.outputDirectory.get() })
    }
/*
./gradlew --dry-run dependencyFails1
:dependencyFails1 SKIPPED

BUILD SUCCESSFUL in 0s
 */

val dependencyFails2 =
    tasks.register<InputTest>("dependencyFails2") {
        // Calling `file` on a `DirectoryProperty` does not retain the dependency
        inputFile.set(
            out1
                .map { it.outputDirectory.file("someFile.txt") }
                .get()
        )
    }
/*
./gradlew --dry-run dependencyFails2
:dependencyFails2 SKIPPED

BUILD SUCCESSFUL in 0s
 */

val dependencyFails3 =
    tasks.register<InputTest>("dependencyFails3") {
        inputDir.set(
            out1
                .map { it.outputDirectory.dir("/someDir") }
                .get()
        )
    }
/*
./gradlew --dry-run dependencyFails3
:dependencyFails3 SKIPPED

BUILD SUCCESSFUL in 0s
 */

/* Helper method for Gradle 4.9. Won't be needed in 4.10 */
inline fun <reified T : Task> TaskContainer.register(name: String, noinline configure: T.() -> Unit = {}) =
    register(name, T::class.java, configure)

Current Behavior

In my opinion, all of the above dependency* tasks should have a dependency upon the output1 task but many do not.

Your Environment

Gradle 4.9
------------------------------------------------------------

Build time:   2018-07-16 08:14:03 UTC
Revision:     efcf8c1cf533b03c70f394f270f46a174c738efc

Kotlin DSL:   0.18.4
Kotlin:       1.2.41
Groovy:       2.4.12
Ant:          Apache Ant(TM) version 1.9.11 compiled on March 23 2018
JVM:          1.8.0_92 (Oracle Corporation 25.92-b14)
OS:           Mac OS X 10.13.5 x86_64
member configuration-model

Most helpful comment

Thanks for the feedback, this is very useful. We'll try to get these edge cases fixed soon.

All 3 comments

This is probably my biggest gripe so far with the Provider/Property types. I reported some of the inconsistencies in https://github.com/gradle/gradle/issues/3811 and this issue has some more good examples. It also looks sort of similar to https://github.com/gradle/gradle/issues/5706 with the different map calls.

@mkobit These problems make this API really hard to utilize in the way that I think the architect intended it to be used.

In #6336 I offered the suggestion of just adding the ability to specify a builtBy explicitly on these types but I think that the team wants to hide that implementation detail.

Thanks for the feedback, this is very useful. We'll try to get these edge cases fixed soon.

Was this page helpful?
0 / 5 - 0 ratings