Skip to content

Commit a9b7e59

Browse files
authored
Only apply 'Adapter' plugins once, and group PluginIds (#4362)
The AndroidAdapter, JavaAdapter, and KotlinAdapters apply their configuration multiple times. Currently, this is acceptable because a project cannot have multiple Android/Kotlin/Java plugins applied. But the situation gets more complicated with AGP9 and kotlin-built-in. The kotlin-built-in plugin uses an implementation of `KotlinBasePlugin`, so the Android and Kotlin adapters could get applied multiple times, and some of their operations (registering DokkaSourceSets) aren't idempotent. I also updated the JavaAdapter to use the same approach for consistency (it's not strictly necessary). --- add 'test' and 'dynamic-feature' Android plugins (Adding them is not strictly related to this PR. They're added as part of the larger initiative of AGP9 support. We're not adding tests for them because I can't find any up-to-date examples, but they should just work. --- AndroidAdapter: remove unnecessary `project.plugins.withType<DokkaBasePlugin>().all { }`§
1 parent 80ab7e9 commit a9b7e59

File tree

7 files changed

+189
-82
lines changed

7 files changed

+189
-82
lines changed

dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/AndroidAdapter.kt

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@ import org.gradle.api.logging.Logging
1616
import org.gradle.api.model.ObjectFactory
1717
import org.gradle.api.provider.Provider
1818
import org.gradle.api.provider.ProviderFactory
19+
import org.gradle.kotlin.dsl.apply
1920
import org.gradle.kotlin.dsl.getByType
20-
import org.gradle.kotlin.dsl.withType
21-
import org.jetbrains.dokka.gradle.DokkaBasePlugin
2221
import org.jetbrains.dokka.gradle.DokkaExtension
2322
import org.jetbrains.dokka.gradle.engine.parameters.KotlinPlatform
2423
import org.jetbrains.dokka.gradle.internal.InternalDokkaGradlePluginApi
25-
import org.jetbrains.dokka.gradle.internal.PluginId
24+
import org.jetbrains.dokka.gradle.internal.PluginIds
2625
import org.jetbrains.dokka.gradle.internal.artifactType
2726
import java.io.File
2827
import javax.inject.Inject
@@ -41,16 +40,6 @@ abstract class AndroidAdapter @Inject constructor(
4140
override fun apply(project: Project) {
4241
logger.info("applied ${this::class} to ${project.path}")
4342

44-
project.plugins.withType<DokkaBasePlugin>().configureEach {
45-
project.pluginManager.apply {
46-
withPlugin(PluginId.AndroidBase) { configure(project) }
47-
withPlugin(PluginId.AndroidApplication) { configure(project) }
48-
withPlugin(PluginId.AndroidLibrary) { configure(project) }
49-
}
50-
}
51-
}
52-
53-
protected fun configure(project: Project) {
5443
val dokkaExtension = project.extensions.getByType<DokkaExtension>()
5544

5645
val androidExt = AndroidExtensionWrapper(project) ?: return
@@ -79,7 +68,19 @@ abstract class AndroidAdapter @Inject constructor(
7968
}
8069

8170
@InternalDokkaGradlePluginApi
82-
companion object
71+
companion object {
72+
73+
/**
74+
* Apply [AndroidAdapter] a single time to [project], regardless of how many AGP plugins are applied.
75+
*/
76+
internal fun applyTo(project: Project) {
77+
PluginIds.android.forEach { pluginId ->
78+
project.pluginManager.withPlugin(pluginId) {
79+
project.pluginManager.apply(AndroidAdapter::class)
80+
}
81+
}
82+
}
83+
}
8384
}
8485

8586

dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/JavaAdapter.kt

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ import org.gradle.api.tasks.SourceSet
1515
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
1616
import org.gradle.api.tasks.SourceSet.TEST_SOURCE_SET_NAME
1717
import org.gradle.api.tasks.SourceSetContainer
18+
import org.gradle.kotlin.dsl.apply
1819
import org.gradle.kotlin.dsl.getByType
1920
import org.gradle.kotlin.dsl.withType
2021
import org.jetbrains.dokka.gradle.DokkaExtension
2122
import org.jetbrains.dokka.gradle.engine.parameters.DokkaSourceSetSpec
2223
import org.jetbrains.dokka.gradle.engine.parameters.KotlinPlatform
2324
import org.jetbrains.dokka.gradle.internal.InternalDokkaGradlePluginApi
24-
import org.jetbrains.dokka.gradle.internal.PluginId
25+
import org.jetbrains.dokka.gradle.internal.PluginIds
2526
import org.jetbrains.dokka.gradle.internal.or
2627
import org.jetbrains.dokka.gradle.internal.uppercaseFirstChar
2728
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
@@ -45,16 +46,13 @@ abstract class JavaAdapter @Inject constructor(
4546

4647
val dokkaExtension = project.extensions.getByType<DokkaExtension>()
4748

48-
// wait for the Java plugin to be applied
49-
project.plugins.withType<JavaBasePlugin>().configureEach {
50-
val java = project.extensions.getByType<JavaPluginExtension>()
51-
val sourceSets = project.extensions.getByType<SourceSetContainer>()
49+
val java = project.extensions.getByType<JavaPluginExtension>()
50+
val sourceSets = project.extensions.getByType<SourceSetContainer>()
5251

53-
detectJavaToolchainVersion(dokkaExtension, java)
52+
detectJavaToolchainVersion(dokkaExtension, java)
5453

55-
val isConflictingPluginPresent = isConflictingPluginPresent(project)
56-
registerDokkaSourceSets(dokkaExtension, sourceSets, isConflictingPluginPresent)
57-
}
54+
val isConflictingPluginPresent = isConflictingPluginPresent(project)
55+
registerDokkaSourceSets(dokkaExtension, sourceSets, isConflictingPluginPresent)
5856
}
5957

6058
/** Fetch the toolchain, and use the language version as Dokka's jdkVersion */
@@ -118,16 +116,11 @@ abstract class JavaAdapter @Inject constructor(
118116
): Provider<Boolean> {
119117

120118
val projectHasKotlinPlugin = providers.provider {
121-
project.pluginManager.hasPlugin(PluginId.KotlinAndroid)
122-
|| project.pluginManager.hasPlugin(PluginId.KotlinJs)
123-
|| project.pluginManager.hasPlugin(PluginId.KotlinJvm)
124-
|| project.pluginManager.hasPlugin(PluginId.KotlinMultiplatform)
119+
PluginIds.kotlin.any { project.pluginManager.hasPlugin(it) }
125120
}
126121

127122
val projectHasAndroidPlugin = providers.provider {
128-
project.pluginManager.hasPlugin(PluginId.AndroidBase)
129-
|| project.pluginManager.hasPlugin(PluginId.AndroidApplication)
130-
|| project.pluginManager.hasPlugin(PluginId.AndroidLibrary)
123+
PluginIds.android.any { project.pluginManager.hasPlugin(it) }
131124
}
132125

133126
return projectHasKotlinPlugin or projectHasAndroidPlugin
@@ -147,5 +140,11 @@ abstract class JavaAdapter @Inject constructor(
147140
fun SourceSet.isPublished(): Boolean =
148141
name != TEST_SOURCE_SET_NAME
149142
&& name.startsWith(MAIN_SOURCE_SET_NAME)
143+
144+
internal fun applyTo(project: Project) {
145+
project.plugins.withType<JavaBasePlugin>().all {
146+
project.pluginManager.apply(type = JavaAdapter::class)
147+
}
148+
}
150149
}
151150
}

dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt

Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ package org.jetbrains.dokka.gradle.adapters
55

66
import com.android.build.api.variant.AndroidComponentsExtension
77
import com.android.build.api.variant.Variant
8-
import org.gradle.api.Named
9-
import org.gradle.api.NamedDomainObjectContainer
10-
import org.gradle.api.Plugin
11-
import org.gradle.api.Project
8+
import org.gradle.api.*
129
import org.gradle.api.file.ConfigurableFileCollection
1310
import org.gradle.api.file.FileCollection
1411
import org.gradle.api.logging.Logger
@@ -22,6 +19,7 @@ import org.gradle.kotlin.dsl.*
2219
import org.jetbrains.dokka.gradle.DokkaBasePlugin
2320
import org.jetbrains.dokka.gradle.DokkaExtension
2421
import org.jetbrains.dokka.gradle.adapters.KotlinAdapter.Companion.currentKotlinToolingVersion
22+
import org.jetbrains.dokka.gradle.adapters.KotlinAdapter.Companion.logKgpClassNotFoundWarning
2523
import org.jetbrains.dokka.gradle.engine.parameters.DokkaSourceSetSpec
2624
import org.jetbrains.dokka.gradle.engine.parameters.KotlinPlatform
2725
import org.jetbrains.dokka.gradle.engine.parameters.SourceSetIdSpec
@@ -33,12 +31,9 @@ import org.jetbrains.kotlin.commonizer.stdlib
3331
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
3432
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
3533
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
36-
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
34+
import org.jetbrains.kotlin.gradle.plugin.*
3735
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
38-
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
3936
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.androidJvm
40-
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
41-
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
4237
import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinNativeCompilation
4338
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
4439
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataCompilation
@@ -62,17 +57,6 @@ abstract class KotlinAdapter @Inject constructor(
6257
override fun apply(project: Project) {
6358
logger.info("Applying $dkaName to ${project.path}")
6459

65-
project.plugins.withType<DokkaBasePlugin>().configureEach {
66-
project.pluginManager.apply {
67-
withPlugin(PluginId.KotlinAndroid) { exec(project) }
68-
withPlugin(PluginId.KotlinJs) { exec(project) }
69-
withPlugin(PluginId.KotlinJvm) { exec(project) }
70-
withPlugin(PluginId.KotlinMultiplatform) { exec(project) }
71-
}
72-
}
73-
}
74-
75-
private fun exec(project: Project) {
7660
val kotlinExtension = project.findKotlinExtension()
7761
if (kotlinExtension == null) {
7862
logger.info("Skipping applying $dkaName in ${project.path} - could not find KotlinProjectExtension")
@@ -210,6 +194,109 @@ abstract class KotlinAdapter @Inject constructor(
210194
val kgpVersion = getKotlinPluginVersion(logger)
211195
KotlinToolingVersion(kgpVersion)
212196
}
197+
198+
/**
199+
* Applies [KotlinAdapter] to the current project when any plugin of type [KotlinBasePlugin]
200+
* is applied.
201+
*
202+
* [KotlinBasePlugin] is the parent type for the Kotlin/JVM, Kotlin/Multiplatform, Kotlin/JS plugins,
203+
* as well as AGP's kotlin-built-in plugin.
204+
*/
205+
internal fun applyTo(project: Project) {
206+
findKotlinBasePlugins(project)?.all {
207+
project.pluginManager.apply(KotlinAdapter::class)
208+
}
209+
}
210+
211+
/**
212+
* Tries fetching all plugins with type [KotlinBasePlugin],
213+
* returning `null` if the class is not available in the current classloader.
214+
*
215+
* (The class might not be available if the current project is a Java or Android project,
216+
* or the buildscripts have an inconsistent classpath https://github.com/gradle/gradle/issues/27218)
217+
*/
218+
private fun findKotlinBasePlugins(project: Project): DomainObjectCollection<KotlinBasePlugin>? {
219+
return try {
220+
project.plugins.withType<KotlinBasePlugin>()
221+
} catch (ex: Throwable) {
222+
when (ex) {
223+
is ClassNotFoundException,
224+
is NoClassDefFoundError -> {
225+
logKgpClassNotFoundWarning(
226+
project,
227+
kotlinBasePluginNotFoundException = ex,
228+
)
229+
null
230+
}
231+
232+
else -> throw ex
233+
}
234+
}
235+
}
236+
237+
/**
238+
* Check all plugins to see if they are a subtype of [KotlinBasePlugin].
239+
* If any are, log a warning.
240+
*
241+
* Also, log an info message with the stacktrace of [kotlinBasePluginNotFoundException].
242+
*
243+
* ##### Motivation
244+
*
245+
* If the buildscript classpath is inconsistent, it might not be possible for DGP
246+
* to react to KGP because the [KotlinBasePlugin] class can't be loaded.
247+
* If so, DGP will be lenient and not cause errors,
248+
* but it must display a prominent warning to help users find the problem.
249+
*
250+
* @param[kotlinBasePluginNotFoundException] The exception thrown when [KotlinBasePlugin] is not available.
251+
*/
252+
private fun logKgpClassNotFoundWarning(
253+
project: Project,
254+
kotlinBasePluginNotFoundException: Throwable,
255+
) {
256+
// hide the stacktrace at `--info` log level, to avoid flooding the log
257+
logger.info(
258+
"Dokka Gradle Plugin could not load KotlinBasePlugin in ${project.displayName}",
259+
kotlinBasePluginNotFoundException,
260+
)
261+
262+
/**
263+
* Keep track of which projects have been warned by [logKgpClassNotFoundWarning],
264+
* otherwise it'll log the same warning multiple times for the same project, which is annoying.
265+
*
266+
* The warning can be logged multiple times if a project has both
267+
* `org.jetbrains.dokka` and `org.jetbrains.dokka-javadoc` applied.
268+
*/
269+
fun checkIfAlreadyWarned(): Boolean {
270+
val key = "DOKKA INTERNAL - projectsWithKgpClassNotFoundWarningApplied"
271+
if (project.extra.has(key)) {
272+
return true
273+
} else {
274+
project.extra.set(key, true)
275+
return false
276+
}
277+
}
278+
279+
PluginIds.kotlin.forEach { pluginId ->
280+
project.pluginManager.withPlugin(pluginId) {
281+
if (checkIfAlreadyWarned()) return@withPlugin
282+
logger.warn(
283+
"""
284+
|warning: Dokka could not load KotlinBasePlugin in ${project.displayName}, even though plugin $pluginId is applied.
285+
|The most common cause is a Gradle limitation: the plugins applied to subprojects should be consistent.
286+
|Please try the following:
287+
|1. Apply the Dokka and Kotlin plugins to the root project using the `plugins {}` DSL.
288+
| (If the root project does not need the plugins, use 'apply false')
289+
|2. Remove the Dokka and Kotlin plugins versions in the subprojects.
290+
|For more information see:
291+
| - https://docs.gradle.org/current/userguide/plugins_intermediate.html#sec:plugins_apply
292+
| - https://github.com/gradle/gradle/issues/25616
293+
| - https://github.com/gradle/gradle/issues/35117
294+
|Please report any feedback or problems https://kotl.in/dokka-issues
295+
|""".trimMargin()
296+
)
297+
}
298+
}
299+
}
213300
}
214301
}
215302

@@ -298,10 +385,10 @@ private class KotlinCompilationDetailsBuilder(
298385
): Provider<Set<AndroidVariantInfo>> {
299386
val androidVariants = objects.setProperty(AndroidVariantInfo::class)
300387

301-
project.pluginManager.apply {
302-
withPlugin(PluginId.AndroidBase) { collectAndroidVariants(project, androidVariants) }
303-
withPlugin(PluginId.AndroidApplication) { collectAndroidVariants(project, androidVariants) }
304-
withPlugin(PluginId.AndroidLibrary) { collectAndroidVariants(project, androidVariants) }
388+
PluginIds.android.forEach { pluginId ->
389+
project.pluginManager.withPlugin(pluginId) {
390+
collectAndroidVariants(project, androidVariants)
391+
}
305392
}
306393

307394
return androidVariants

dokka-runners/dokka-gradle-plugin/src/main/kotlin/formats/DokkaFormatPlugin.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ abstract class DokkaFormatPlugin(
6565
target.pluginManager.apply(DokkaBasePlugin::class)
6666

6767
// apply the plugin that will autoconfigure Dokka to use the sources of a Kotlin project
68-
target.pluginManager.apply(type = KotlinAdapter::class)
69-
target.pluginManager.apply(type = JavaAdapter::class)
70-
target.pluginManager.apply(type = AndroidAdapter::class)
68+
KotlinAdapter.applyTo(target)
69+
AndroidAdapter.applyTo(target)
70+
JavaAdapter.applyTo(target)
7171

7272
target.plugins.withType<DokkaBasePlugin>().configureEach {
7373
val dokkaExtension = target.extensions.getByType(DokkaExtension::class)

dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/PluginId.kt

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
package org.jetbrains.dokka.gradle.internal
5+
6+
/**
7+
* Gradle Plugin IDs.
8+
*/
9+
internal object PluginIds {
10+
11+
val kotlin: Set<String> = setOf(
12+
"org.jetbrains.kotlin.android",
13+
"org.jetbrains.kotlin.js",
14+
"org.jetbrains.kotlin.jvm",
15+
"org.jetbrains.kotlin.multiplatform",
16+
)
17+
18+
val android: Set<String> = setOf(
19+
"com.android.base",
20+
"com.android.application",
21+
"com.android.library",
22+
"com.android.test",
23+
"com.android.dynamic-feature",
24+
)
25+
}

0 commit comments

Comments
 (0)