dependency-analysis-gradle-plugin icon indicating copy to clipboard operation
dependency-analysis-gradle-plugin copied to clipboard

Gradle8 + KSP compatibility

Open umpteenthdev opened this issue 1 year ago • 8 comments

Build scan link https://scans.gradle.com/s/5q4mdauav5xri

Plugin version 1.20.0

Gradle version 8.1.1

(Optional) Android Gradle Plugin (AGP) version 8.0.2

reason output for bugs relating to incorrect advice N/A

Describe the bug We use KSP in our Android project. During migration to Gradle 8+ we faced failures caused by buildHealth task. The issue is that Gradle8 requires dependencies between tasks to be defined explicitly. It seems that dependency analysis plugin uses KSP output directories but does not define any relation to KSP tasks.

To Reproduce

  1. Open sample project in Android Studio
  2. Run ./gradlew buildHealth from the root of the project

Expected behavior buildHealth task finished successfully (with or without advice)

Additional context

FAILURE: Build failed with an exception.

* What went wrong:
Some problems were found with the configuration of task ':app:kspDebugKotlin' (type 'KspTaskJvm').
  - Gradle detected a problem with the following location: '/Users/a.erdman/proj/build_health_gradle8_compatibility_2/app/build/generated/ksp/debug'.
    
    Reason: Task ':app:explodeCodeSourceDebug' uses this output of task ':app:kspDebugKotlin' 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 ':app:kspDebugKotlin' as an input of ':app:explodeCodeSourceDebug'.
      2. Declare an explicit dependency on ':app:kspDebugKotlin' from ':app:explodeCodeSourceDebug' using Task#dependsOn.
      3. Declare an explicit dependency on ':app:kspDebugKotlin' from ':app:explodeCodeSourceDebug' using Task#mustRunAfter.
    
    Please refer to https://docs.gradle.org/8.1.1/userguide/validation_problems.html#implicit_dependency for more details about this problem.
  - Gradle detected a problem with the following location: '/Users/a.erdman/proj/build_health_gradle8_compatibility_2/app/build/generated/ksp/debug/kotlin'.
    
    Reason: Task ':app:explodeCodeSourceDebug' uses this output of task ':app:kspDebugKotlin' 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 ':app:kspDebugKotlin' as an input of ':app:explodeCodeSourceDebug'.
      2. Declare an explicit dependency on ':app:kspDebugKotlin' from ':app:explodeCodeSourceDebug' using Task#dependsOn.
      3. Declare an explicit dependency on ':app:kspDebugKotlin' from ':app:explodeCodeSourceDebug' using Task#mustRunAfter.
    
    Please refer to https://docs.gradle.org/8.1.1/userguide/validation_problems.html#implicit_dependency for more details about this problem.

umpteenthdev avatar Jun 02 '23 07:06 umpteenthdev

This affects more than just builds that use KSP. I have a simple task which produces generated source code that JavaCompile steps depend on, and see the same failure.

mako-taco avatar Jun 02 '23 15:06 mako-taco

Thanks for the report.

autonomousapps avatar Jun 13 '23 06:06 autonomousapps

I think this is a bug in Gradle. See https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin/issues/685#issuecomment-1650309499. tl;dr you can manually wire the tasks together.

autonomousapps avatar Aug 07 '23 17:08 autonomousapps

I tried to link tasks before publishing the issue but it didn't work. I have just checked the same implementation you provided in the mentioned issue. It does not work too. You can check it in the sample project. Even after adding the code below

tasks.withType(com.autonomousapps.tasks.CodeSourceExploderTask) {
    dependsOn('kspKotlin', "kspDebugKotlin")
}

running ./gradlew buildHealth twice guarantees the failure.

umpteenthdev avatar Oct 17 '23 07:10 umpteenthdev

https://github.com/gradle/gradle/issues/25885

autonomousapps avatar Nov 02 '23 03:11 autonomousapps

Here is a more fine-grained workaround for android projects. At least this works for us. It doesn't seem to affect JVM modules in our case.

allprojects {
    pluginManager.withPlugin('com.google.devtools.ksp') {
        pluginManager.withPlugin('com.android.library') {
            androidComponents {
                beforeVariants(selector().all()) { variant ->
                    String variantName = variant.name.capitalize()
                    tasks.configureEach { task ->
                        if (task.name == "explodeCodeSource$variantName") {
                            task.dependsOn("ksp${variantName}Kotlin")
                        }
                    }
                }
            }
        }
        pluginManager.withPlugin('com.android.application') {
            androidComponents {
                beforeVariants(selector().all()) { variant ->
                    String variantName = variant.name.capitalize()
                    tasks.configureEach { task ->
                        if (task.name == "explodeCodeSource$variantName") {
                            task.dependsOn("ksp${variantName}Kotlin")
                        }
                    }
                }
            }
        }
    }
}

francescocervone avatar Jul 25 '24 12:07 francescocervone

I've explored this a bit more recently, and while the linked antlr plugin issue is a gradle bug (because antlr is a core gradle plugin), this is, more generally, a bug in plugin implementation. For plugins to correctly contribute generated source, such that task dependency information is carried and end users don't need to think about these workarounds, they should do this:

Correct

// pseudocode
sourceSets.main.<java|kotlin|etc>.srcDir(myGeneratingTask.map { it.outputDirectory })

I haven't explored the ksp code, but I assume it's not doing this, and is instead doing something like

Incorrect

// pseudocode
val outputDirectory = project.layout.buildDirectory.dir("my-dir")
myGeneratingTask.configure { t ->
  t.outputDirectory.set(outputDirectory)
}
sourceSets.main.<java|kotlin|etc>.srcDir(outputDirectory)

That isn't enough information for Gradle to know which task generates code into that directory, unfortunately.

autonomousapps avatar Jul 25 '24 18:07 autonomousapps