autonomousapps / dependency-analysis-gradle-plugin

Gradle plugin for JVM projects written in Java, Kotlin, Groovy, or Scala; and Android projects written in Java or Kotlin. Provides advice for managing dependencies and other applied plugins
Apache License 2.0
1.82k stars 121 forks source link

Gradle8 + KSP compatibility #912

Open umpteenthdev opened 1 year ago

umpteenthdev commented 1 year ago

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.
mako-taco commented 1 year ago

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.

autonomousapps commented 1 year ago

Thanks for the report.

autonomousapps commented 1 year ago

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.

umpteenthdev commented 1 year ago

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.

autonomousapps commented 1 year ago

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

francescocervone commented 4 months ago

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")
                        }
                    }
                }
            }
        }
    }
}
autonomousapps commented 4 months ago

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.

eygraber commented 2 months ago

I started seeing this after adding Compose Multiplatform Resources. Should I be filing something with Jetbrains and pointing them here?