From afb8b3188ca4452123b51c40fc2128161d626015 Mon Sep 17 00:00:00 2001 From: Oleg Yukhnevich Date: Fri, 15 Aug 2025 14:26:20 +0300 Subject: [PATCH 01/15] Fix compatibility with changes in KGP --- .../src/main/kotlin/adapters/KotlinAdapter.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt index 8ddac7a3fd..81c13e16b5 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt @@ -453,11 +453,18 @@ private class KotlinCompilationDetailsBuilder( return when (this) { is KotlinMetadataCompilation<*> -> true + // Use string-based comparison, not the actual classes, because AGP has deprecated and + // moved the Library/Application classes to a different package. + // Using strings is more widely compatible. is KotlinJvmAndroidCompilation -> { - // Use string-based comparison, not the actual classes, because AGP has deprecated and - // moved the Library/Application classes to a different package. - // Using strings is more widely compatible. - val variantName = androidVariant::class.jvmName + // `runCatching` is used here, in case `androidVariant` will be removed in a future version of KGP. + val variantName = runCatching { + // https://youtrack.jetbrains.com/issue/KT-77023: + // `androidVariant` has `null` value in case of AGP/built-in Kotlin project. + @Suppress("DEPRECATION", "UNNECESSARY_NOT_NULL_ASSERTION") + androidVariant!!::class.jvmName + }.getOrNull() ?: return true // published by default + "LibraryVariant" in variantName || "ApplicationVariant" in variantName } From 3296827d0caf39182d165940a66b0c0bfd9f0da7 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Tue, 26 Aug 2025 14:40:17 +0200 Subject: [PATCH 02/15] WIP update KotlinAdapter, to determine whether an AGP source set is 'publishable' --- .../src/main/kotlin/adapters/KotlinAdapter.kt | 265 +++++++++++++----- 1 file changed, 199 insertions(+), 66 deletions(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt index 81c13e16b5..4ed1c07762 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt @@ -11,7 +11,6 @@ import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileCollection import org.gradle.api.logging.Logging import org.gradle.api.model.ObjectFactory -import org.gradle.api.plugins.ExtensionContainer import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory @@ -44,7 +43,6 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion import java.io.File import javax.inject.Inject -import kotlin.reflect.jvm.jvmName /** * The [KotlinAdapter] plugin will automatically register Kotlin source sets as Dokka source sets. @@ -72,25 +70,8 @@ abstract class KotlinAdapter @Inject constructor( } private fun exec(project: Project) { - val kotlinExtension = project.extensions.findKotlinExtension() + val kotlinExtension = project.findKotlinExtension() if (kotlinExtension == null) { - if (project.extensions.findByName("kotlin") != null) { - // uh oh - the Kotlin extension is present but findKotlinExtension() failed. - // Is there a class loader issue? https://github.com/gradle/gradle/issues/27218 - logger.warn { - val allPlugins = - project.plugins.joinToString { it::class.qualifiedName ?: "${it::class}" } - val allExtensions = - project.extensions.extensionsSchema.elements.joinToString { "${it.name} ${it.publicType}" } - - /* language=TEXT */ - """ - |$dkaName failed to get KotlinProjectExtension in ${project.path} - | Applied plugins: $allPlugins - | Available extensions: $allExtensions - """.trimMargin() - } - } logger.info("Skipping applying $dkaName in ${project.path} - could not find KotlinProjectExtension") return } @@ -215,25 +196,49 @@ abstract class KotlinAdapter @Inject constructor( private val logger = Logging.getLogger(KotlinAdapter::class.java) /** Try and get [KotlinProjectExtension], or `null` if it's not present. */ - private fun ExtensionContainer.findKotlinExtension(): KotlinProjectExtension? = - try { - findByType() - // fallback to trying to get the JVM extension - // (not sure why I did this... maybe to be compatible with really old versions?) - ?: findByType() - } catch (e: Throwable) { - when (e) { - is TypeNotPresentException, - is ClassNotFoundException, - is NoClassDefFoundError -> { - logger.info("$dkaName failed to find KotlinExtension ${e::class} ${e.message}") - null + private fun Project.findKotlinExtension(): KotlinProjectExtension? { + val kotlinExtension = + try { + extensions.findByType() + // fallback to trying to get the JVM extension + // (not sure why I did this... maybe to be compatible with really old versions?) + ?: extensions.findByType() + } catch (e: Throwable) { + when (e) { + is TypeNotPresentException, + is ClassNotFoundException, + is NoClassDefFoundError -> { + logger.info("$dkaName failed to find KotlinExtension ${e::class} ${e.message}") + null + } + + else -> throw e } + } - else -> throw e + if (kotlinExtension == null) { + if (project.extensions.findByName("kotlin") != null) { + // uh oh - the Kotlin extension is present but findKotlinExtension() failed. + // Is there a class loader issue? https://github.com/gradle/gradle/issues/27218 + logger.warn { + val allPlugins = + project.plugins.joinToString { it::class.qualifiedName ?: "${it::class}" } + val allExtensions = + project.extensions.extensionsSchema.elements.joinToString { "${it.name} ${it.publicType}" } + + /* language=TEXT */ + """ + |$dkaName failed to get KotlinProjectExtension in ${project.path} + | Applied plugins: $allPlugins + | Available extensions: $allExtensions + """.trimMargin() + } } } + return kotlinExtension + } + /** Get the version of the Kotlin Gradle Plugin currently used to compile the project. */ // Must be lazy, else tests fail (because the KGP plugin isn't accessible) internal val currentKotlinToolingVersion: KotlinToolingVersion by lazy { @@ -274,7 +279,7 @@ private data class KotlinCompilationDetails( * * (E.g. 'main' compilations are published, 'test' compilations are not.) */ - val publishedCompilation: Boolean, + val publishedCompilation: Provider, /** [KotlinCompilation.kotlinSourceSets] → [KotlinSourceSet.dependsOn] names. */ val dependentSourceSetNames: Set, @@ -293,6 +298,7 @@ private class KotlinCompilationDetailsBuilder( private val konanHome: Provider, private val project: Project, ) { + private val androidComponentsInfo: Provider> = getAgpVariantInfo(project) fun createCompilationDetails( kotlinProjectExtension: KotlinProjectExtension, @@ -312,6 +318,69 @@ private class KotlinCompilationDetailsBuilder( return details } + private data class AgpVariantInfo( + val name: String, + val hasPublishedComponent: Boolean, + ) + + private fun getAgpVariantInfo( + project: Project, + ): Provider> { + + val androidVariants = objects.setProperty(AgpVariantInfo::class) + + fun collectAndroidVariants() { + val androidComponents = project.findAndroidComponentExtension() + + androidComponents?.onVariants { variant -> + androidVariants.add( + AgpVariantInfo( + name = variant.name, + hasPublishedComponent = variant.components.any { it is com.android.build.api.variant.Variant }, + ) + ) + } + } + + project.pluginManager.apply { + withPlugin(PluginId.AndroidBase) { collectAndroidVariants() } + withPlugin(PluginId.AndroidApplication) { collectAndroidVariants() } + withPlugin(PluginId.AndroidLibrary) { collectAndroidVariants() } + } + return androidVariants + +// try { +// project.extensions.findByType(AndroidComponentsExtension::class) +// } catch (ex: ClassNotFoundException) { +// KotlinCompilationDetailsBuilder.Companion.logger.info("Unable to find AndroidComponentsExtension in project $project", ex) +// null +// } +// if (androidComponents == null) { +// KotlinCompilationDetailsBuilder.Companion.logger.warn("AndroidComponentsExtension not available in project $project") +// return providers.provider { false } +// } else { +// val agpVariants = objects.listProperty(AgpVariantInfo::class) +// +// androidComponents.onVariants { v -> +// println("[DOKKA KotlinAdapter] ${compilation.name} checking variant $v, ${v.name}, ${v.components.map { "${it.name}=${it::class}" }}") +// agpVariants.add( +// AgpVariantInfo( +// name = v.name, +// hasPublishedComponent = +// v.components.any { it is com.android.build.api.variant.Variant }, +// ) +// ) +// } +// +// return agpVariants.map { agpVariants -> +// agpVariants +// .singleOrNull { it.name == compilation.name } +// ?.hasPublishedComponent +// ?: false +// } +// } + } + /** Create a single [KotlinCompilationDetails] for [compilation]. */ private fun createCompilationDetails( compilation: KotlinCompilation<*>, @@ -438,41 +507,58 @@ private class KotlinCompilationDetailsBuilder( } } - companion object { + /** + * Determine if a [KotlinCompilation] is 'publishable', and so should be enabled by default + * when creating a Dokka publication. + * + * Typically, 'main' compilations are publishable and 'test' compilations should be suppressed. + * This can be overridden manually, though. + * + * @see DokkaSourceSetSpec.suppress + */ + private fun KotlinCompilation<*>.isPublished(): Provider { + return when (this) { + is KotlinMetadataCompilation<*> -> + providers.provider { true } + + // Use string-based comparison, not the actual classes, because AGP has deprecated and + // moved the Library/Application classes to a different package. + // Using strings is more widely compatible. + is KotlinJvmAndroidCompilation -> { + isJvmAndroidPublished(this) + } - /** - * Determine if a [KotlinCompilation] is 'publishable', and so should be enabled by default - * when creating a Dokka publication. - * - * Typically, 'main' compilations are publishable and 'test' compilations should be suppressed. - * This can be overridden manually, though. - * - * @see DokkaSourceSetSpec.suppress - */ - private fun KotlinCompilation<*>.isPublished(): Boolean { - return when (this) { - is KotlinMetadataCompilation<*> -> true - - // Use string-based comparison, not the actual classes, because AGP has deprecated and - // moved the Library/Application classes to a different package. - // Using strings is more widely compatible. - is KotlinJvmAndroidCompilation -> { - // `runCatching` is used here, in case `androidVariant` will be removed in a future version of KGP. - val variantName = runCatching { - // https://youtrack.jetbrains.com/issue/KT-77023: - // `androidVariant` has `null` value in case of AGP/built-in Kotlin project. - @Suppress("DEPRECATION", "UNNECESSARY_NOT_NULL_ASSERTION") - androidVariant!!::class.jvmName - }.getOrNull() ?: return true // published by default - - "LibraryVariant" in variantName || "ApplicationVariant" in variantName - } + else -> + providers.provider { name == MAIN_COMPILATION_NAME } + } + } - else -> - name == MAIN_COMPILATION_NAME + private fun isJvmAndroidPublished(compilation: KotlinJvmAndroidCompilation): Provider { + println("[DOKKA KotlinAdapter] Checking if ${compilation.name} is publishable... (currentKotlinToolingVersion:${currentKotlinToolingVersion})") + +// if (currentKotlinToolingVersion < KotlinToolingVersion("2.2.10")) { // TODO revert, I made it lower for easier manual testing + if (currentKotlinToolingVersion < KotlinToolingVersion("2.1.10")) { + val variantName = compilation.androidVariant.name + return providers.provider { + val x = "LibraryVariant" in variantName || "ApplicationVariant" in variantName + println("[DOKKA KotlinAdapter] ${compilation.name} publishable:$x, variant:$variantName") + x + } + } else { + return androidComponentsInfo.map { components -> + components.any { component -> + val x = component.name == compilation.name + && component.hasPublishedComponent + println("[DOKKA KotlinAdapter] ${compilation.name} publishable:$x, component:${component.name}") + x + } } } } + + companion object { + private val logger = Logging.getLogger(KotlinCompilationDetailsBuilder::class.java) + } } @@ -515,7 +601,7 @@ private abstract class KotlinSourceSetDetails @Inject constructor( */ fun isPublishedSourceSet(): Provider = allCompilations.map { values -> - values.any { it.publishedCompilation } + values.any { it.publishedCompilation.get() } } override fun getName(): String = named @@ -625,3 +711,50 @@ private class KotlinSourceSetDetailsBuilder( ) } } + + +private typealias AndroidComponentsExtension = com.android.build.api.variant.AndroidComponentsExtension<*, *, *> + + +/** Try and get [KotlinProjectExtension], or `null` if it's not present. */ +private fun Project.findAndroidComponentExtension(): AndroidComponentsExtension? { + val androidComponentsExtensionName = "androidComponents" + val androidComponentsExtension = + try { + val candidate = extensions.findByName(androidComponentsExtensionName) + candidate as? AndroidComponentsExtension + } catch (e: Throwable) { + when (e) { + is TypeNotPresentException, + is ClassNotFoundException, + is NoClassDefFoundError -> { + logger.info("Dokka Gradle plugin failed to find AndroidComponentsExtension ${e::class} ${e.message}") + null + } + + else -> throw e + } + } + + if (androidComponentsExtension == null) { + if (project.extensions.findByName(androidComponentsExtensionName) != null) { + // uh oh - extension is present but findAndroidComponentExtension() failed. + // Is there a class loader issue? https://github.com/gradle/gradle/issues/27218 + logger.warn { + val allPlugins = + project.plugins.joinToString { it::class.qualifiedName ?: "${it::class}" } + val allExtensions = + project.extensions.extensionsSchema.elements.joinToString { "${it.name} ${it.publicType}" } + + /* language=TEXT */ + """ + |Dokka Gradle plugin failed to get AndroidComponentsExtension in ${project.path} + | Applied plugins: $allPlugins + | Available extensions: $allExtensions + """.trimMargin() + } + } + } + + return androidComponentsExtension +} From 6ed654045669b62171f02c3dbd19477a201a9ed4 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:51:24 +0200 Subject: [PATCH 03/15] tidy --- .../src/main/kotlin/adapters/KotlinAdapter.kt | 212 ++++++------------ .../src/main/kotlin/internal/pluginUtils.kt | 59 +++++ 2 files changed, 122 insertions(+), 149 deletions(-) create mode 100644 dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt index 4ed1c07762..588eafebf2 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt @@ -195,50 +195,6 @@ abstract class KotlinAdapter @Inject constructor( private val logger = Logging.getLogger(KotlinAdapter::class.java) - /** Try and get [KotlinProjectExtension], or `null` if it's not present. */ - private fun Project.findKotlinExtension(): KotlinProjectExtension? { - val kotlinExtension = - try { - extensions.findByType() - // fallback to trying to get the JVM extension - // (not sure why I did this... maybe to be compatible with really old versions?) - ?: extensions.findByType() - } catch (e: Throwable) { - when (e) { - is TypeNotPresentException, - is ClassNotFoundException, - is NoClassDefFoundError -> { - logger.info("$dkaName failed to find KotlinExtension ${e::class} ${e.message}") - null - } - - else -> throw e - } - } - - if (kotlinExtension == null) { - if (project.extensions.findByName("kotlin") != null) { - // uh oh - the Kotlin extension is present but findKotlinExtension() failed. - // Is there a class loader issue? https://github.com/gradle/gradle/issues/27218 - logger.warn { - val allPlugins = - project.plugins.joinToString { it::class.qualifiedName ?: "${it::class}" } - val allExtensions = - project.extensions.extensionsSchema.elements.joinToString { "${it.name} ${it.publicType}" } - - /* language=TEXT */ - """ - |$dkaName failed to get KotlinProjectExtension in ${project.path} - | Applied plugins: $allPlugins - | Available extensions: $allExtensions - """.trimMargin() - } - } - } - - return kotlinExtension - } - /** Get the version of the Kotlin Gradle Plugin currently used to compile the project. */ // Must be lazy, else tests fail (because the KGP plugin isn't accessible) internal val currentKotlinToolingVersion: KotlinToolingVersion by lazy { @@ -298,7 +254,7 @@ private class KotlinCompilationDetailsBuilder( private val konanHome: Provider, private val project: Project, ) { - private val androidComponentsInfo: Provider> = getAgpVariantInfo(project) + private val androidComponentsInfo: Provider> = getAgpVariantInfo(project) fun createCompilationDetails( kotlinProjectExtension: KotlinProjectExtension, @@ -318,67 +274,18 @@ private class KotlinCompilationDetailsBuilder( return details } - private data class AgpVariantInfo( - val name: String, - val hasPublishedComponent: Boolean, - ) - private fun getAgpVariantInfo( project: Project, - ): Provider> { - - val androidVariants = objects.setProperty(AgpVariantInfo::class) - - fun collectAndroidVariants() { - val androidComponents = project.findAndroidComponentExtension() + ): Provider> { - androidComponents?.onVariants { variant -> - androidVariants.add( - AgpVariantInfo( - name = variant.name, - hasPublishedComponent = variant.components.any { it is com.android.build.api.variant.Variant }, - ) - ) - } - } + val androidVariants = objects.setProperty(AndroidVariantInfo::class) project.pluginManager.apply { - withPlugin(PluginId.AndroidBase) { collectAndroidVariants() } - withPlugin(PluginId.AndroidApplication) { collectAndroidVariants() } - withPlugin(PluginId.AndroidLibrary) { collectAndroidVariants() } + withPlugin(PluginId.AndroidBase) { collectAndroidVariants(project, androidVariants) } + withPlugin(PluginId.AndroidApplication) { collectAndroidVariants(project, androidVariants) } + withPlugin(PluginId.AndroidLibrary) { collectAndroidVariants(project, androidVariants) } } return androidVariants - -// try { -// project.extensions.findByType(AndroidComponentsExtension::class) -// } catch (ex: ClassNotFoundException) { -// KotlinCompilationDetailsBuilder.Companion.logger.info("Unable to find AndroidComponentsExtension in project $project", ex) -// null -// } -// if (androidComponents == null) { -// KotlinCompilationDetailsBuilder.Companion.logger.warn("AndroidComponentsExtension not available in project $project") -// return providers.provider { false } -// } else { -// val agpVariants = objects.listProperty(AgpVariantInfo::class) -// -// androidComponents.onVariants { v -> -// println("[DOKKA KotlinAdapter] ${compilation.name} checking variant $v, ${v.name}, ${v.components.map { "${it.name}=${it::class}" }}") -// agpVariants.add( -// AgpVariantInfo( -// name = v.name, -// hasPublishedComponent = -// v.components.any { it is com.android.build.api.variant.Variant }, -// ) -// ) -// } -// -// return agpVariants.map { agpVariants -> -// agpVariants -// .singleOrNull { it.name == compilation.name } -// ?.hasPublishedComponent -// ?: false -// } -// } } /** Create a single [KotlinCompilationDetails] for [compilation]. */ @@ -533,31 +440,34 @@ private class KotlinCompilationDetailsBuilder( } } - private fun isJvmAndroidPublished(compilation: KotlinJvmAndroidCompilation): Provider { - println("[DOKKA KotlinAdapter] Checking if ${compilation.name} is publishable... (currentKotlinToolingVersion:${currentKotlinToolingVersion})") + private fun isJvmAndroidPublished( + compilation: KotlinJvmAndroidCompilation, + ): Provider { -// if (currentKotlinToolingVersion < KotlinToolingVersion("2.2.10")) { // TODO revert, I made it lower for easier manual testing - if (currentKotlinToolingVersion < KotlinToolingVersion("2.1.10")) { + // in KGP 2.2.10 androidVariant will be nullable KT-77023 + if (currentKotlinToolingVersion < KotlinToolingVersion("2.2.10")) { val variantName = compilation.androidVariant.name return providers.provider { - val x = "LibraryVariant" in variantName || "ApplicationVariant" in variantName - println("[DOKKA KotlinAdapter] ${compilation.name} publishable:$x, variant:$variantName") - x + val result = "LibraryVariant" in variantName || "ApplicationVariant" in variantName + logger.info { + "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, androidVariant:$variantName" + } + result } } else { return androidComponentsInfo.map { components -> - components.any { component -> - val x = component.name == compilation.name - && component.hasPublishedComponent - println("[DOKKA KotlinAdapter] ${compilation.name} publishable:$x, component:${component.name}") - x + val compilationComponents = components.filter { it.name == compilation.name } + val result = compilationComponents.any { component -> component.hasPublishedComponent } + logger.info { + "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, compilationComponents:$compilationComponents" } + result } } } companion object { - private val logger = Logging.getLogger(KotlinCompilationDetailsBuilder::class.java) + private val logger = Logging.getLogger(KotlinAdapter::class.java) } } @@ -713,48 +623,52 @@ private class KotlinSourceSetDetailsBuilder( } +/** Try and get [KotlinProjectExtension], or `null` if it's not present. */ +private fun Project.findKotlinExtension(): KotlinProjectExtension? = + findExtensionLenient("kotlin") + + private typealias AndroidComponentsExtension = com.android.build.api.variant.AndroidComponentsExtension<*, *, *> +/** Try and get [AndroidComponentsExtension], or `null` if it's not present. */ +private fun Project.findAndroidComponentExtension(): AndroidComponentsExtension? = + findExtensionLenient("androidComponents") -/** Try and get [KotlinProjectExtension], or `null` if it's not present. */ -private fun Project.findAndroidComponentExtension(): AndroidComponentsExtension? { - val androidComponentsExtensionName = "androidComponents" - val androidComponentsExtension = - try { - val candidate = extensions.findByName(androidComponentsExtensionName) - candidate as? AndroidComponentsExtension - } catch (e: Throwable) { - when (e) { - is TypeNotPresentException, - is ClassNotFoundException, - is NoClassDefFoundError -> { - logger.info("Dokka Gradle plugin failed to find AndroidComponentsExtension ${e::class} ${e.message}") - null - } - else -> throw e - } - } +/** + * Store details about a [com.android.build.api.variant.Variant]. + * + * @param[name] [com.android.build.api.variant.Variant.name]. + * @param[hasPublishedComponent] `true` if any component of the variant is 'published', + * i.e. it is an instance of [com.android.build.api.variant.Variant]. + */ +private data class AndroidVariantInfo( + val name: String, + val hasPublishedComponent: Boolean, +) - if (androidComponentsExtension == null) { - if (project.extensions.findByName(androidComponentsExtensionName) != null) { - // uh oh - extension is present but findAndroidComponentExtension() failed. - // Is there a class loader issue? https://github.com/gradle/gradle/issues/27218 - logger.warn { - val allPlugins = - project.plugins.joinToString { it::class.qualifiedName ?: "${it::class}" } - val allExtensions = - project.extensions.extensionsSchema.elements.joinToString { "${it.name} ${it.publicType}" } - - /* language=TEXT */ - """ - |Dokka Gradle plugin failed to get AndroidComponentsExtension in ${project.path} - | Applied plugins: $allPlugins - | Available extensions: $allExtensions - """.trimMargin() +/** + * Collect [AndroidVariantInfo]s for all variants in the project. + * + * Should only be called when AGP is applied (otherwise the `androidComponents` extension will be missing). + */ +private fun collectAndroidVariants( + project: Project, + androidVariants: SetProperty, +) { + val androidComponents = project.findAndroidComponentExtension() + + androidComponents?.onVariants { variant -> + val hasPublishedComponent = + variant.components.any { component -> + component is com.android.build.api.variant.Variant } - } - } - return androidComponentsExtension + androidVariants.add( + AndroidVariantInfo( + name = variant.name, + hasPublishedComponent = hasPublishedComponent, + ) + ) + } } diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt new file mode 100644 index 0000000000..aaa18448ae --- /dev/null +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2014-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.gradle.internal + +import org.gradle.api.Project + +/** + * Try and get an extension from [Project.getExtensions], or `null` if it's not present. + * + * Handles as many errors as possible. + * + * Logs a warning if the extension is present, but the wrong type + * (probably caused by an inconsistent buildscript classpath https://github.com/gradle/gradle/issues/27218) + */ +internal inline fun Project.findExtensionLenient( + extensionName: String, +): T? { + val candidate = extensions.findByName(extensionName) + ?: return null + + val extensionInstance = + try { + candidate as? T + } catch (e: Throwable) { + when (e) { + is TypeNotPresentException, + is ClassNotFoundException, + is NoClassDefFoundError -> { + logger.info("Dokka Gradle plugin failed to find extension ${T::class.simpleName}. ${e::class} ${e.message}") + null + } + + else -> throw e + } + } + + if (extensionInstance == null) { + if (project.extensions.findByName(extensionName) != null) { + // uh oh - extension is present, but it's the wrong type + // Is there a class loader issue? https://github.com/gradle/gradle/issues/27218 + logger.warn { + val allPlugins = + project.plugins.joinToString { it::class.qualifiedName ?: "${it::class}" } + val allExtensions = + project.extensions.extensionsSchema.elements.joinToString { "${it.name} ${it.publicType}" } + + """ + |Dokka Gradle plugin failed to get AndroidComponentsExtension in ${project.path} + | Applied plugins: $allPlugins + | Available extensions: $allExtensions + """.trimMargin() + } + } + } + + return extensionInstance +} From 144405d77dc8f24cf37d774fbd56dda301a4cb95 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Wed, 27 Aug 2025 17:14:15 +0200 Subject: [PATCH 04/15] tidy --- .../src/main/kotlin/internal/pluginUtils.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt index aaa18448ae..4aa7d3ee11 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt @@ -28,7 +28,7 @@ internal inline fun Project.findExtensionLenient( is TypeNotPresentException, is ClassNotFoundException, is NoClassDefFoundError -> { - logger.info("Dokka Gradle plugin failed to find extension ${T::class.simpleName}. ${e::class} ${e.message}") + logger.info("Dokka Gradle plugin failed to find extension $extensionName ${T::class.simpleName}. ${e::class} ${e.message}") null } @@ -47,7 +47,7 @@ internal inline fun Project.findExtensionLenient( project.extensions.extensionsSchema.elements.joinToString { "${it.name} ${it.publicType}" } """ - |Dokka Gradle plugin failed to get AndroidComponentsExtension in ${project.path} + |Dokka Gradle plugin failed to get extension $extensionName ${T::class.simpleName} in ${project.path} | Applied plugins: $allPlugins | Available extensions: $allExtensions """.trimMargin() From a869239d43f39cbfd2e687fb2eef973b74d61f5a Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Thu, 28 Aug 2025 10:43:19 +0200 Subject: [PATCH 05/15] Avoid `java.lang.NoClassDefFoundError` in `findExtensionLenient`. rename file to match function name Update test. --- .../kotlin/internal/findExtensionLenient.kt | 62 +++++++++++++++++++ .../src/main/kotlin/internal/pluginUtils.kt | 59 ------------------ .../kotlin/MultiModuleFunctionalTest.kt | 11 ++-- 3 files changed, 69 insertions(+), 63 deletions(-) create mode 100644 dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/findExtensionLenient.kt delete mode 100644 dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/findExtensionLenient.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/findExtensionLenient.kt new file mode 100644 index 0000000000..5ac5412bcd --- /dev/null +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/findExtensionLenient.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2014-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.gradle.internal + +import org.gradle.api.Project + +/** + * Try and get an extension from [Project.getExtensions], or `null` if it's not present. + * + * Handles as many errors as possible. + * + * Logs a warning if the extension is present, but the wrong type + * (probably caused by an inconsistent buildscript classpath https://github.com/gradle/gradle/issues/27218) + */ +internal inline fun Project.findExtensionLenient( + extensionName: String, +): T? { + + val extensionByName = extensions.findByName(extensionName) + if (extensionByName == null) { + logger.info("Dokka Gradle plugin failed to find extension $extensionName by name ${T::class.java}") + return null + } + + try { + return extensions.findByType(T::class.java) + } catch (e: Throwable) { + when (e) { + is TypeNotPresentException, + is ClassNotFoundException, + is NoClassDefFoundError -> { + + // uh oh - extension is present, but it's the wrong type + // Is there a class loader issue? https://github.com/gradle/gradle/issues/27218 + logger.warn { + // If we're here, then T isn't available, so don't use T::class. + // Instead, use the available extension's class. + val actualExtensionFqn = + extensions.extensionsSchema.firstOrNull { it.name == extensionName }?.publicType?.fullyQualifiedName + + val allPlugins = + project.plugins.joinToString { it::class.qualifiedName ?: "${it::class.java}" } + val allExtensions = + project.extensions.extensionsSchema.elements.joinToString { "${it.name} ${it.publicType}" } + + """ + |Dokka Gradle plugin failed to get extension $extensionName $actualExtensionFqn in ${project.path} + |Please make sure plugins in all subprojects are consistent. See https://github.com/gradle/gradle/issues/27218 + | Applied plugins: $allPlugins + | Available extensions: $allExtensions + """.trimMargin() + } + + return null + } + + else -> throw e + } + } +} diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt deleted file mode 100644 index 4aa7d3ee11..0000000000 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/pluginUtils.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2014-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package org.jetbrains.dokka.gradle.internal - -import org.gradle.api.Project - -/** - * Try and get an extension from [Project.getExtensions], or `null` if it's not present. - * - * Handles as many errors as possible. - * - * Logs a warning if the extension is present, but the wrong type - * (probably caused by an inconsistent buildscript classpath https://github.com/gradle/gradle/issues/27218) - */ -internal inline fun Project.findExtensionLenient( - extensionName: String, -): T? { - val candidate = extensions.findByName(extensionName) - ?: return null - - val extensionInstance = - try { - candidate as? T - } catch (e: Throwable) { - when (e) { - is TypeNotPresentException, - is ClassNotFoundException, - is NoClassDefFoundError -> { - logger.info("Dokka Gradle plugin failed to find extension $extensionName ${T::class.simpleName}. ${e::class} ${e.message}") - null - } - - else -> throw e - } - } - - if (extensionInstance == null) { - if (project.extensions.findByName(extensionName) != null) { - // uh oh - extension is present, but it's the wrong type - // Is there a class loader issue? https://github.com/gradle/gradle/issues/27218 - logger.warn { - val allPlugins = - project.plugins.joinToString { it::class.qualifiedName ?: "${it::class}" } - val allExtensions = - project.extensions.extensionsSchema.elements.joinToString { "${it.name} ${it.publicType}" } - - """ - |Dokka Gradle plugin failed to get extension $extensionName ${T::class.simpleName} in ${project.path} - | Applied plugins: $allPlugins - | Available extensions: $allExtensions - """.trimMargin() - } - } - } - - return extensionInstance -} diff --git a/dokka-runners/dokka-gradle-plugin/src/testFunctional/kotlin/MultiModuleFunctionalTest.kt b/dokka-runners/dokka-gradle-plugin/src/testFunctional/kotlin/MultiModuleFunctionalTest.kt index 9d0c278150..7452c9370a 100644 --- a/dokka-runners/dokka-gradle-plugin/src/testFunctional/kotlin/MultiModuleFunctionalTest.kt +++ b/dokka-runners/dokka-gradle-plugin/src/testFunctional/kotlin/MultiModuleFunctionalTest.kt @@ -520,14 +520,17 @@ class MultiModuleFunctionalTest : FunSpec({ test("expect warning regarding KotlinProjectExtension") { project.runner - .addArguments("clean") + .addArguments( + "clean", + "--stacktrace", + ) .forwardOutput() .build { // the root project doesn't have the KGP applied, so KotlinProjectExtension shouldn't be applied - output shouldNotContain "KotlinAdapter failed to get KotlinProjectExtension in :\n" + output shouldNotContain "Dokka Gradle plugin failed to get extension kotlin org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension in :\n" - output shouldContain "KotlinAdapter failed to get KotlinProjectExtension in :subproject-hello\n" - output shouldContain "KotlinAdapter failed to get KotlinProjectExtension in :subproject-goodbye\n" + output shouldContain "Dokka Gradle plugin failed to get extension kotlin org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension in :subproject-hello\n" + output shouldContain "Dokka Gradle plugin failed to get extension kotlin org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension in :subproject-goodbye\n" } } } From e8cd21efac4134a3ca5a430ac20f616ecc758654 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:00:31 +0200 Subject: [PATCH 06/15] limit fetching data unlesss KGP >= 2.2.10 Add kdoc --- .../src/main/kotlin/adapters/KotlinAdapter.kt | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt index 588eafebf2..7ae951b686 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt @@ -274,17 +274,30 @@ private class KotlinCompilationDetailsBuilder( return details } + /** + * Collect information about Android variants. + * Used to determine whether a source set is published or not. + * See [KotlinSourceSetDetails.isPublishedSourceSet]. + * + * Android variant info must be fetched eagerly, + * since AGP doesn't provide a lazy way of accessing component information. + * + * @see collectAndroidVariants + * @see supportsAgpKotlinBuiltInCompilation + */ private fun getAgpVariantInfo( project: Project, ): Provider> { - val androidVariants = objects.setProperty(AndroidVariantInfo::class) - project.pluginManager.apply { - withPlugin(PluginId.AndroidBase) { collectAndroidVariants(project, androidVariants) } - withPlugin(PluginId.AndroidApplication) { collectAndroidVariants(project, androidVariants) } - withPlugin(PluginId.AndroidLibrary) { collectAndroidVariants(project, androidVariants) } + if (currentKotlinToolingVersion.supportsAgpKotlinBuiltInCompilation()) { + project.pluginManager.apply { + withPlugin(PluginId.AndroidBase) { collectAndroidVariants(project, androidVariants) } + withPlugin(PluginId.AndroidApplication) { collectAndroidVariants(project, androidVariants) } + withPlugin(PluginId.AndroidLibrary) { collectAndroidVariants(project, androidVariants) } + } } + return androidVariants } @@ -445,21 +458,21 @@ private class KotlinCompilationDetailsBuilder( ): Provider { // in KGP 2.2.10 androidVariant will be nullable KT-77023 - if (currentKotlinToolingVersion < KotlinToolingVersion("2.2.10")) { - val variantName = compilation.androidVariant.name - return providers.provider { - val result = "LibraryVariant" in variantName || "ApplicationVariant" in variantName + if (currentKotlinToolingVersion.supportsAgpKotlinBuiltInCompilation()) { + return androidComponentsInfo.map { components -> + val compilationComponents = components.filter { it.name == compilation.name } + val result = compilationComponents.any { component -> component.hasPublishedComponent } logger.info { - "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, androidVariant:$variantName" + "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, compilationComponents:$compilationComponents" } result } } else { - return androidComponentsInfo.map { components -> - val compilationComponents = components.filter { it.name == compilation.name } - val result = compilationComponents.any { component -> component.hasPublishedComponent } + val variantName = compilation.androidVariant.name + return providers.provider { + val result = "LibraryVariant" in variantName || "ApplicationVariant" in variantName logger.info { - "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, compilationComponents:$compilationComponents" + "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, androidVariant:$variantName" } result } @@ -672,3 +685,13 @@ private fun collectAndroidVariants( ) } } + +/** + * KGP 2.2.10 will start delegating Kotlin compilation to AGP ("Built-in Kotlin"). + * + * [KotlinJvmAndroidCompilation.androidVariant] will be deprecated and nullable. + * + * See https://youtrack.jetbrains.com/issue/KT-77023 + */ +private fun KotlinToolingVersion.supportsAgpKotlinBuiltInCompilation(): Boolean = + this >= KotlinToolingVersion("2.2.10") From f8a02d6dc8d4fd9236626d8c32ae2bac7c1fad74 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:53:36 +0200 Subject: [PATCH 07/15] fix android variant typecheck --- .../src/main/kotlin/adapters/KotlinAdapter.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt index 7ae951b686..c0680e3573 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt @@ -43,6 +43,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion import java.io.File import javax.inject.Inject +import kotlin.reflect.jvm.jvmName /** * The [KotlinAdapter] plugin will automatically register Kotlin source sets as Dokka source sets. @@ -468,11 +469,11 @@ private class KotlinCompilationDetailsBuilder( result } } else { - val variantName = compilation.androidVariant.name + val androidVariantJvmName = compilation.androidVariant::class.jvmName return providers.provider { - val result = "LibraryVariant" in variantName || "ApplicationVariant" in variantName + val result = "LibraryVariant" in androidVariantJvmName || "ApplicationVariant" in androidVariantJvmName logger.info { - "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, androidVariant:$variantName" + "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, androidVariantJvmName:$androidVariantJvmName" } result } From f08f6ad73ce8e40a8409f004a64ed7050a338f4b Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:07:20 +0200 Subject: [PATCH 08/15] rm unnecessary `typealias AndroidComponentsExtension` --- .../src/main/kotlin/adapters/KotlinAdapter.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt index c0680e3573..7875346f63 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt @@ -3,6 +3,7 @@ */ package org.jetbrains.dokka.gradle.adapters +import com.android.build.api.variant.AndroidComponentsExtension import org.gradle.api.Named import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Plugin @@ -642,11 +643,9 @@ private fun Project.findKotlinExtension(): KotlinProjectExtension? = findExtensionLenient("kotlin") -private typealias AndroidComponentsExtension = com.android.build.api.variant.AndroidComponentsExtension<*, *, *> - /** Try and get [AndroidComponentsExtension], or `null` if it's not present. */ -private fun Project.findAndroidComponentExtension(): AndroidComponentsExtension? = - findExtensionLenient("androidComponents") +private fun Project.findAndroidComponentExtension(): AndroidComponentsExtension<*, *, *>? = + findExtensionLenient>("androidComponents") /** From 3663dcea4226b0f367a69acdb6b6bddeaf4d8ef2 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:09:47 +0200 Subject: [PATCH 09/15] move comment about 'string based class comparison' to relevant part of code --- .../src/main/kotlin/adapters/KotlinAdapter.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt index 7875346f63..a3ce7f95cc 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt @@ -443,9 +443,6 @@ private class KotlinCompilationDetailsBuilder( is KotlinMetadataCompilation<*> -> providers.provider { true } - // Use string-based comparison, not the actual classes, because AGP has deprecated and - // moved the Library/Application classes to a different package. - // Using strings is more widely compatible. is KotlinJvmAndroidCompilation -> { isJvmAndroidPublished(this) } @@ -472,6 +469,9 @@ private class KotlinCompilationDetailsBuilder( } else { val androidVariantJvmName = compilation.androidVariant::class.jvmName return providers.provider { + // Use string-based comparison for the class names, not the actual classes, + // because AGP has deprecated and moved the Library/Application classes to a different package. + // Using strings is more widely compatible. val result = "LibraryVariant" in androidVariantJvmName || "ApplicationVariant" in androidVariantJvmName logger.info { "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, androidVariantJvmName:$androidVariantJvmName" From d7446577125e330e6471279dc2dffe63e5d4ace5 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Tue, 4 Nov 2025 07:56:45 +0100 Subject: [PATCH 10/15] bump tested KGP 2.2 version --- .../org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt b/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt index 2b56655420..696a3c3a65 100644 --- a/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt +++ b/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt @@ -47,7 +47,7 @@ fun interface TestedVersionsSource { "1.9.25", "2.0.21", "2.1.21", - "2.2.20", + "2.2.21", ) /** From 6642daba2e038728acf87a108a658607e414c92e Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Tue, 4 Nov 2025 07:59:22 +0100 Subject: [PATCH 11/15] add tested KGP 2.3 version --- .../org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt b/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt index 696a3c3a65..c5719174cc 100644 --- a/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt +++ b/dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt @@ -48,6 +48,7 @@ fun interface TestedVersionsSource { "2.0.21", "2.1.21", "2.2.21", + "2.3.0-Beta2", ) /** From 5554f874ec49efd63099a8ec53236e9c109df394 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Tue, 4 Nov 2025 08:01:00 +0100 Subject: [PATCH 12/15] remove condition usage of AndroidComponentsExtension --- .../src/main/kotlin/adapters/KotlinAdapter.kt | 63 ++++++------------- 1 file changed, 18 insertions(+), 45 deletions(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt index 935e49d2a6..174b72df39 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt @@ -4,12 +4,14 @@ package org.jetbrains.dokka.gradle.adapters import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.Variant import org.gradle.api.Named import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileCollection +import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty @@ -44,13 +46,12 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion import java.io.File import javax.inject.Inject -import kotlin.reflect.jvm.jvmName /** * The [KotlinAdapter] plugin will automatically register Kotlin source sets as Dokka source sets. * * This is an internal Dokka plugin and should not be used externally. - * It is not a standalone plugin, it requires [org.jetbrains.dokka.gradle.DokkaBasePlugin] is also applied. + * It is not a standalone plugin, it requires [DokkaBasePlugin] is also applied. */ @InternalDokkaGradlePluginApi abstract class KotlinAdapter @Inject constructor( @@ -292,12 +293,10 @@ private class KotlinCompilationDetailsBuilder( ): Provider> { val androidVariants = objects.setProperty(AndroidVariantInfo::class) - if (currentKotlinToolingVersion.supportsAgpKotlinBuiltInCompilation()) { - project.pluginManager.apply { - withPlugin(PluginId.AndroidBase) { collectAndroidVariants(project, androidVariants) } - withPlugin(PluginId.AndroidApplication) { collectAndroidVariants(project, androidVariants) } - withPlugin(PluginId.AndroidLibrary) { collectAndroidVariants(project, androidVariants) } - } + project.pluginManager.apply { + withPlugin(PluginId.AndroidBase) { collectAndroidVariants(project, androidVariants) } + withPlugin(PluginId.AndroidApplication) { collectAndroidVariants(project, androidVariants) } + withPlugin(PluginId.AndroidLibrary) { collectAndroidVariants(project, androidVariants) } } return androidVariants @@ -455,34 +454,18 @@ private class KotlinCompilationDetailsBuilder( private fun isJvmAndroidPublished( compilation: KotlinJvmAndroidCompilation, ): Provider { - - // in KGP 2.2.10 androidVariant will be nullable KT-77023 - if (currentKotlinToolingVersion.supportsAgpKotlinBuiltInCompilation()) { - return androidComponentsInfo.map { components -> - val compilationComponents = components.filter { it.name == compilation.name } - val result = compilationComponents.any { component -> component.hasPublishedComponent } - logger.info { - "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, compilationComponents:$compilationComponents" - } - result - } - } else { - val androidVariantJvmName = compilation.androidVariant::class.jvmName - return providers.provider { - // Use string-based comparison for the class names, not the actual classes, - // because AGP has deprecated and moved the Library/Application classes to a different package. - // Using strings is more widely compatible. - val result = "LibraryVariant" in androidVariantJvmName || "ApplicationVariant" in androidVariantJvmName - logger.info { - "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, androidVariantJvmName:$androidVariantJvmName" - } - result + return androidComponentsInfo.map { components -> + val compilationComponents = components.filter { it.name == compilation.name } + val result = compilationComponents.any { component -> component.hasPublishedComponent } + logger.info { + "[KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result, compilationComponents:$compilationComponents" } + result } } companion object { - private val logger = Logging.getLogger(KotlinAdapter::class.java) + private val logger: Logger = Logging.getLogger(KotlinAdapter::class.java) } } @@ -649,11 +632,11 @@ private fun Project.findAndroidComponentExtension(): AndroidComponentsExtension< /** - * Store details about a [com.android.build.api.variant.Variant]. + * Store details about a [Variant]. * - * @param[name] [com.android.build.api.variant.Variant.name]. + * @param[name] [Variant.name]. * @param[hasPublishedComponent] `true` if any component of the variant is 'published', - * i.e. it is an instance of [com.android.build.api.variant.Variant]. + * i.e. it is an instance of [Variant]. */ private data class AndroidVariantInfo( val name: String, @@ -674,7 +657,7 @@ private fun collectAndroidVariants( androidComponents?.onVariants { variant -> val hasPublishedComponent = variant.components.any { component -> - component is com.android.build.api.variant.Variant + component is Variant } androidVariants.add( @@ -685,13 +668,3 @@ private fun collectAndroidVariants( ) } } - -/** - * KGP 2.2.10 will start delegating Kotlin compilation to AGP ("Built-in Kotlin"). - * - * [KotlinJvmAndroidCompilation.androidVariant] will be deprecated and nullable. - * - * See https://youtrack.jetbrains.com/issue/KT-77023 - */ -private fun KotlinToolingVersion.supportsAgpKotlinBuiltInCompilation(): Boolean = - this >= KotlinToolingVersion("2.2.10") From 6fca4134502e603a91912cb84544aa25b58846f9 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:28:31 +0100 Subject: [PATCH 13/15] add more docs for `collectAndroidVariants` --- .../src/main/kotlin/adapters/KotlinAdapter.kt | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt index 8bbd968c4d..a2fdb1fa83 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt @@ -650,9 +650,44 @@ private data class AndroidVariantInfo( ) /** - * Collect [AndroidVariantInfo]s for all variants in the project. + * Collect [AndroidVariantInfo]s of the Android [Variant]s in this Android project. * - * Should only be called when AGP is applied (otherwise the `androidComponents` extension will be missing). + * We store the collected data in a custom class to aid with Configuration Cache compatibility. + * + * This function must only be called when AGP is applied + * (otherwise [findAndroidComponentExtension] will return `null`), + * i.e. inside a `withPlugin(...) {}` block. + * + * ## How to determine publishability of AGP Variants + * + * There are several Android Gradle plugins. + * Each AGP has a specific associated [Variant]: + * - `com.android.application` - [com.android.build.api.variant.ApplicationVariant] + * - `com.android.library` - [com.android.build.api.variant.DynamicFeatureVariant] + * - `com.android.test` - [com.android.build.api.variant.LibraryVariant] + * - `com.android.dynamic-feature` - [com.android.build.api.variant.TestVariant] + * + * A [Variant] is 'published' (or otherwise shared with other projects). + * Note that a [Variant] might have [nestedComponents][Variant.nestedComponents]. + * If any of these [com.android.build.api.variant.Component]s are [Variant]s, + * then the [Variant] itself should be considered 'publishable'. + * + * If a [KotlinSourceSet] has an associated [Variant], + * it should therefore be documented by Dokka by default. + * + * ### Associating Variants with Compilations with SourceSets + * + * So, how can we associate a [KotlinSourceSet] with a [Variant]? + * + * Fortunately, Dokka already knows about the [KotlinCompilation]s associated with a specific [KotlinSourceSet]. + * + * So, for each [KotlinCompilation], find a [Variant] with the same name, + * i.e. [KotlinCompilation.getName] is the same as [Variant.name]. + * + * Next, determine if the [Variant] associated with a [KotlinCompilation] is 'publishable' by + * checking if it _or_ any of its [nestedComponents][Variant.nestedComponents] + * are 'publishable' (i.e. is an instance of [Variant]). + * (We can we use [Variant.components] to check both the [Variant] and its `nestedComponents` the same time.) */ private fun collectAndroidVariants( project: Project, @@ -663,6 +698,8 @@ private fun collectAndroidVariants( androidComponents?.onVariants { variant -> val hasPublishedComponent = variant.components.any { component -> + // a Variant is a subtype of a Component that is shared with consumers, + // so Dokka should consider it 'publishable' component is Variant } From acd43cbf09893ec70cbfdb89bb950c1af8149b27 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:37:01 +0100 Subject: [PATCH 14/15] rm old kdoc reference --- .../src/main/kotlin/adapters/KotlinAdapter.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt index a2fdb1fa83..bdf2bf7007 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/adapters/KotlinAdapter.kt @@ -292,7 +292,6 @@ private class KotlinCompilationDetailsBuilder( * since AGP doesn't provide a lazy way of accessing component information. * * @see collectAndroidVariants - * @see supportsAgpKotlinBuiltInCompilation */ private fun getAgpVariantInfo( project: Project, From e10f1e557f9adb65200a093d3f79db7cd6169e68 Mon Sep 17 00:00:00 2001 From: Adam Semenenko <152864218+adam-enko@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:48:13 +0100 Subject: [PATCH 15/15] update `findExtensionLenient` kdoc --- .../src/main/kotlin/internal/findExtensionLenient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/findExtensionLenient.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/findExtensionLenient.kt index 5ac5412bcd..2a6b001943 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/findExtensionLenient.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/findExtensionLenient.kt @@ -9,7 +9,7 @@ import org.gradle.api.Project /** * Try and get an extension from [Project.getExtensions], or `null` if it's not present. * - * Handles as many errors as possible. + * If [T] is not accessible in the current classloader, returns `null`. * * Logs a warning if the extension is present, but the wrong type * (probably caused by an inconsistent buildscript classpath https://github.com/gradle/gradle/issues/27218)