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.67k stars 115 forks source link

java.lang.NoClassDefFoundError: org/gradle/kotlin/dsl/PluginAwareExtensionsKt when applying plugin to a Project with a sub Project during tests #1123

Open sihutch opened 4 months ago

sihutch commented 4 months ago

Build scan link https://scans.gradle.com/s/segppkfrzjmca/

Plugin version 1.29.0

Gradle version 7.6.1 (Same bug exhibits with Gradle 8.0)

JDK version 11.0.16 (Amazon.com Inc. 11.0.16+8-LTS)

Describe the bug

We have tests that build Projects using org.gradle.testfixtures.ProjectBuilder. We apply the com.autonomousapps.dependency-analysis plugin on the Project during our test. If the test Project does not have a sub Project then plugin application works. However, if the test Project does have a sub Project, then a java.lang.NoClassDefFoundError: org/gradle/kotlin/dsl/PluginAwareExtensionsKt exception is thrown when the com.autonomousapps.subplugin.RootPlugin attempts to conditionallyApplyToSubprojects as shown in the snippet from the stack trace below (The full stack trace can be seen on the build scan [here](https://scans.gradle.com/s/segppkfrzjmca/tests/task/:test/details/org.example.ProjectBuilderTest/canApplyPluginOnProjectWithSubProject()?expanded-stacktrace=WyIwIiwiMC0xIl0&focused-exception-line=0-1-0&top-execution=1))

Caused by: java.lang.NoClassDefFoundError: org/gradle/kotlin/dsl/PluginAwareExtensionsKt
com.autonomousapps.subplugin.RootPlugin$conditionallyApplyToSubprojects$1.execute(RootPlugin.kt:65) |  
com.autonomousapps.subplugin.RootPlugin$conditionallyApplyToSubprojects$1.execute(RootPlugin.kt:63)

To Reproduce Steps to reproduce the behavior:

  1. Build a org.gradle.api.Project using the org.gradle.testfixtures.ProjectBuilder
  2. Build a second org.gradle.api.Project using the org.gradle.testfixtures.ProjectBuilder setting the Project from step 1 as its parent
  3. Apply the com.autonomousapps.dependency-analysis plugin to the root project

I created a Junit 5 test to help me diagnose and have included it below (note this test fails as the NoClassDefFoundError exception is thrown)

Also note that if we remove the line ProjectBuilder.builder().withParent(rootProject).build(); from the code, then the test passes.

package org.example;

import org.gradle.api.Project;
import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

public class ProjectBuilderTest {

    @DisplayName("Ensure that the dependency-analysis plugin can be applied on a textfixtures project with a subproject")
    @Test
    void canApplyPluginOnProjectWithSubProject() {
        Project rootProject = ProjectBuilder.builder().build();
        ProjectBuilder.builder().withParent(rootProject).build();
        assertDoesNotThrow(() ->
                rootProject.getPluginManager().apply("com.autonomousapps.dependency-analysis"));
    }
}

Expected behavior We expect the plugin application to work on a Project containing a sub Project in the same way as it does on a Project without any sub Project.

Additional context I'm not sure that is related, but I noticed this line [Fix] Don't leak Kotlin stdlib from shaded dependencies. from the 1.29.0 release notes.

I tested against the previous plugin version, i.e. 1.28.0. On this version, a Kotlin java.lang.NoClassDefFoundError is thrown when we apply the plugin on any Project in our test, i.e. even if the Project does not have any sub Project.

Notice from the stack trace below that for version 1.28.0, the exception was thrown by a call to com.autonomousapps.DependencyAnalysisPlugin.applyForProject as opposed to version 1.29.0 where the exception was thrown by a call to com.autonomousapps.subplugin.RootPlugin$conditionallyApplyToSubprojects

Caused by: java.lang.NoClassDefFoundError: org/gradle/kotlin/dsl/NamedDomainObjectCollectionExtensionsKt
    at com.autonomousapps.subplugin.ProjectPlugin.<clinit>(ProjectPlugin.kt:55)
    at com.autonomousapps.DependencyAnalysisPlugin.applyForProject(DependencyAnalysisPlugin.kt:83)
    at com.autonomousapps.DependencyAnalysisPlugin.apply(DependencyAnalysisPlugin.kt:28)
    at com.autonomousapps.DependencyAnalysisPlugin.apply(DependencyAnalysisPlugin.kt:22)

I included this context as it appears that something related was fixed in the 1.29.0 release but that it somehow wasn't fixed for the sub Project case.

autonomousapps commented 4 months ago

Thanks for the issue. Looks like some kind of classpath issue. It reminds me of a case in a real (non-test) project that uses Kotlin but wasn't applying any Kotlin plugin to the root project. I am not entirely sure how the classpath works in the context of a unit test like you have, but as I stare at the error and the build scan you shared, it occurs to me that my plugin uses Kotlin DSL, and that missing class is from the Kotlin DSL -- that is, the Gradle jar itself. Maybe try adding testRuntimeOnly(gradleApi()) to your build script. That may not work though because I'm not certain that dependencies also contains the Kotlin DSL classes. My IDE tells me that class comes from gradle-kotlin-dsl-8.5.jar.

More generally, I'm not sure it makes a lot of sense to try to unit test this. I recommend writing functional tests instead. I have a plugin and companion libraries for that here if you're interested.