How to use dependOn in task register with confui

TL;DR
How to use register api correctly? Basically how to dependOn a registered task to other tasks?

Goal
We’re trying to automatically generate a dependency list for different build types and flavours in an android project.

Unexpected Behaviour
We can’t seem to add newly registered tasks during configuration phase to preBuild task.

Current Behaviour
Currently the tasks are never being executed during ./gradlew clean build

Environment
// gradle.properties
org.gradle.unsafe.configuration-cache=true

./gradlew --version

------------------------------------------------------------
Gradle 7.3.1
------------------------------------------------------------

Build time:   2021-12-01 15:42:20 UTC
Revision:     2c62cec93e0b15a7d2cd68746f3348796d6d42bd

Kotlin:       1.5.31
Groovy:       3.0.9
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          11.0.11 (AdoptOpenJDK 11.0.11+9)
OS:           Mac OS X 11.5 x86_64

Steps to reproduce

Our approach of creating variant based tasks:

what we’ve tried:

  1. adding preBuild.dependsOn after task registration (old way, worked before register api and works with configuration cache disabled)
android.applicationVariants.all { variant ->

    tasks.register("listDependencies${variant.name.capitalize()}") {

        outputs.file(project.rootProject.file("app/src/${variant.name}/assets/dependencies.txt"))

        doLast {

            description = "Depdencies for ${variant.name.capitalize()}"

            outputs.files.singleFile.text = ''

            configurations.getByName("${variant.name}RuntimeClasspath").getResolvedConfiguration().getFirstLevelModuleDependencies().each {
                println "${it.moduleGroup}:${it.moduleName} -> ${it.moduleVersion}"
                outputs.files.singleFile.append("${it.moduleGroup}:${it.moduleName}:${it.moduleVersion}\n")
            }
        }
    }

    preBuild.dependsOn "listDependencies${variant.name.capitalize()}"
}

but it fails with

FAILURE: Build failed with an exception.

* What went wrong:
Configuration cache state could not be cached: field 'actions' from type 'org.gradle.api.DefaultTask': error writing value of type 'java.util.ArrayList'
> Configuration cache state could not be cached: field 'closure' from type 'org.gradle.api.internal.AbstractTask$ClosureTaskAction': error writing value of type 'build_dependencies_overview_cxyg66z7i7ej4xgqaiszqvki5$_run_closure1$_closure2$_closure3'
   > Configuration cache state could not be cached: field 'variant' from type 'build_dependencies_overview_cxyg66z7i7ej4xgqaiszqvki5$_run_closure1$_closure2$_closure3': error writing value of type 'groovy.lang.Reference'
      > Configuration cache state could not be cached: field 'value' from type 'groovy.lang.Reference': error writing value of type 'com.android.build.gradle.internal.api.ApplicationVariantImpl'
         > Configuration cache state could not be cached: field 'testVariant' from type 'com.android.build.gradle.internal.api.ApplicationVariantImpl': error writing value of type 'com.android.build.gradle.internal.api.TestVariantImpl'
            > Configuration cache state could not be cached: field 'variantData' from type 'com.android.build.gradle.internal.api.TestVariantImpl': error writing value of type 'com.android.build.gradle.internal.variant.TestVariantData'
               > Configuration cache state could not be cached: field 'testedVariantData' from type 'com.android.build.gradle.internal.variant.TestVariantData': error writing value of type 'com.android.build.gradle.internal.variant.ApplicationVariantData'
                  > Configuration cache state could not be cached: field 'artifacts' from type 'com.android.build.gradle.internal.variant.ApplicationVariantData': error writing value of type 'com.android.build.api.artifact.impl.ArtifactsImpl'
                     > Configuration cache state could not be cached: field 'storageProvider' from type 'com.android.build.api.artifact.impl.ArtifactsImpl': error writing value of type 'com.android.build.api.artifact.impl.StorageProviderImpl'
                        > Configuration cache state could not be cached: field 'fileStorage' from type 'com.android.build.api.artifact.impl.StorageProviderImpl': error writing value of type 'com.android.build.api.artifact.impl.TypedStorageProvider'
                           > Configuration cache state could not be cached: field 'singleStorage' from type 'com.android.build.api.artifact.impl.TypedStorageProvider': error writing value of type 'java.util.LinkedHashMap'
                              > java.util.ConcurrentModificationException (no error message)
  1. adding dependsOn preBuild in task registration
// https://gist.github.com/kibotu/a2bc6f154518fba633a87273cf024dcd
android.applicationVariants.all { variant ->

    tasks.register("listDependencies${variant.name.capitalize()}") {

        outputs.file(project.rootProject.file("app/src/${variant.name}/assets/dependencies.txt"))

        dependsOn preBuild

        doLast {

            description = "Depdencies for ${variant.name.capitalize()}"

            outputs.files.singleFile.text = ''

            configurations.getByName("${variant.name}RuntimeClasspath").getResolvedConfiguration().getFirstLevelModuleDependencies().each {
                println "${it.moduleGroup}:${it.moduleName} -> ${it.moduleVersion}"
                outputs.files.singleFile.append("${it.moduleGroup}:${it.moduleName}:${it.moduleVersion}\n")
            }
        }
    }
}

but task is not being executed when running ./gradle clean build

  1. running tasks manually

works as expected (e.g.):

./gradlew listDebugDependencies

however our goal was to use the new register task api and it’s way to use output files api

Any help is highly appreciated.

Let’s do it reverse.

Your third approach confuses me, as in the other approaches you register listDependenciesDebug, but you call listDebugDependencies.

Your second approach has at least two flaws, first one is that you define that listDependenciesDebug depends on preBuild but what you want as far as I understood is exactly the other way around, so what you would need is preBuild.dependsOn this. But even that would be suboptimal, because then this configuration is only done if listDebugDependencies is actually configured as you add this dependency in its configuration part and as you use register it is lazy.

Your first approach is probably almost the right one.
I’m not sure why you get that error, especially with a ConcurrentModificationException, but actually I’d not use a String task dependency. tasks.register returns you a TaskProvider on which you can directly depend on. So either do it like preBuild.dependsOn tasks.register... or def listDependencies = tasks.register... and then preBuild.dependsOn listDependencies.

Firstly thanks a lot for you feedback. With your help and a little shotgun debugging (commenting things out and back in) we figured things out.

Turns out accessing variant.name the way we did caused the ConcurrentModificationException.

Leaving a working solution here for future references.

/**
 * Generates a text file containing dependencies for different build variants 
 * and saves them into the respective variant asset folder. 
 * Also deletes files on clean. 
 * 
 * Can be safely used with <code>org.gradle.unsafe.configuration-cache=true</code>
 * https://gist.github.com/kibotu/a2bc6f154518fba633a87273cf024dcd
 */
android.applicationVariants.all { variant ->

    def name = variant.name
    def nameCapitalized = variant.name.capitalize()

    preBuild.dependsOn tasks.register("list${nameCapitalized}Dependencies") {

        outputs.file(project.rootProject.file("app/src/$name/assets/dependencies.txt"))

        doLast {

            description = "Depdencies for $nameCapitalized"

            outputs.files.singleFile.text = ''

            configurations.getByName("${name}RuntimeClasspath").getResolvedConfiguration().getFirstLevelModuleDependencies().each {
                println "${it.moduleGroup}:${it.moduleName} -> ${it.moduleVersion}"
                outputs.files.singleFile.append("${it.moduleGroup}:${it.moduleName}:${it.moduleVersion}\n")
            }
        }
    }

    clean {
        delete project.rootProject.file("app/src/$name/assets/dependencies.txt")
    }
}

Note: Regarding 3rd point, it’s a typo.