33 */
44package org.jetbrains.dokka.gradle.adapters
55
6+ import com.android.build.api.variant.AndroidComponentsExtension
7+ import com.android.build.api.variant.Variant
68import org.gradle.api.Named
79import org.gradle.api.NamedDomainObjectContainer
810import org.gradle.api.Plugin
911import org.gradle.api.Project
1012import org.gradle.api.file.ConfigurableFileCollection
1113import org.gradle.api.file.FileCollection
14+ import org.gradle.api.logging.Logger
1215import org.gradle.api.logging.Logging
1316import org.gradle.api.model.ObjectFactory
14- import org.gradle.api.plugins.ExtensionContainer
1517import org.gradle.api.provider.ListProperty
1618import org.gradle.api.provider.Provider
1719import org.gradle.api.provider.ProviderFactory
@@ -44,13 +46,12 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget
4446import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion
4547import java.io.File
4648import javax.inject.Inject
47- import kotlin.reflect.jvm.jvmName
4849
4950/* *
5051 * The [KotlinAdapter] plugin will automatically register Kotlin source sets as Dokka source sets.
5152 *
5253 * This is an internal Dokka plugin and should not be used externally.
53- * It is not a standalone plugin, it requires [org.jetbrains.dokka.gradle. DokkaBasePlugin] is also applied.
54+ * It is not a standalone plugin, it requires [DokkaBasePlugin] is also applied.
5455 */
5556@InternalDokkaGradlePluginApi
5657abstract class KotlinAdapter @Inject constructor(
@@ -72,25 +73,8 @@ abstract class KotlinAdapter @Inject constructor(
7273 }
7374
7475 private fun exec (project : Project ) {
75- val kotlinExtension = project.extensions. findKotlinExtension()
76+ val kotlinExtension = project.findKotlinExtension()
7677 if (kotlinExtension == null ) {
77- if (project.extensions.findByName(" kotlin" ) != null ) {
78- // uh oh - the Kotlin extension is present but findKotlinExtension() failed.
79- // Is there a class loader issue? https://github.com/gradle/gradle/issues/27218
80- logger.warn {
81- val allPlugins =
82- project.plugins.joinToString { it::class .qualifiedName ? : " ${it::class } " }
83- val allExtensions =
84- project.extensions.extensionsSchema.elements.joinToString { " ${it.name} ${it.publicType} " }
85-
86- /* language=TEXT */
87- """
88- |$dkaName failed to get KotlinProjectExtension in ${project.path}
89- | Applied plugins: $allPlugins
90- | Available extensions: $allExtensions
91- """ .trimMargin()
92- }
93- }
9478 logger.info(" Skipping applying $dkaName in ${project.path} - could not find KotlinProjectExtension" )
9579 return
9680 }
@@ -220,26 +204,6 @@ abstract class KotlinAdapter @Inject constructor(
220204
221205 private val logger = Logging .getLogger(KotlinAdapter ::class .java)
222206
223- /* * Try and get [KotlinProjectExtension], or `null` if it's not present. */
224- private fun ExtensionContainer.findKotlinExtension (): KotlinProjectExtension ? =
225- try {
226- findByType()
227- // fallback to trying to get the JVM extension
228- // (not sure why I did this... maybe to be compatible with really old versions?)
229- ? : findByType< org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension > ()
230- } catch (e: Throwable ) {
231- when (e) {
232- is TypeNotPresentException ,
233- is ClassNotFoundException ,
234- is NoClassDefFoundError -> {
235- logger.info(" $dkaName failed to find KotlinExtension ${e::class } ${e.message} " )
236- null
237- }
238-
239- else -> throw e
240- }
241- }
242-
243207 /* * Get the version of the Kotlin Gradle Plugin currently used to compile the project. */
244208 // Must be lazy, else tests fail (because the KGP plugin isn't accessible)
245209 internal val currentKotlinToolingVersion: KotlinToolingVersion by lazy {
@@ -280,7 +244,7 @@ private data class KotlinCompilationDetails(
280244 *
281245 * (E.g. 'main' compilations are published, 'test' compilations are not.)
282246 */
283- val publishedCompilation : Boolean ,
247+ val publishedCompilation : Provider < Boolean > ,
284248
285249 /* * [KotlinCompilation.kotlinSourceSets] → [KotlinSourceSet.dependsOn] names. */
286250 val dependentSourceSetNames : Set <String >,
@@ -299,6 +263,7 @@ private class KotlinCompilationDetailsBuilder(
299263 private val konanHome : Provider <File >,
300264 private val project : Project ,
301265) {
266+ private val androidComponentsInfo: Provider <Set <AndroidVariantInfo >> = getAgpVariantInfo(project)
302267
303268 fun createCompilationDetails (
304269 kotlinProjectExtension : KotlinProjectExtension ,
@@ -318,6 +283,30 @@ private class KotlinCompilationDetailsBuilder(
318283 return details
319284 }
320285
286+ /* *
287+ * Collect information about Android variants.
288+ * Used to determine whether a source set is published or not.
289+ * See [KotlinSourceSetDetails.isPublishedSourceSet].
290+ *
291+ * Android variant info must be fetched eagerly,
292+ * since AGP doesn't provide a lazy way of accessing component information.
293+ *
294+ * @see collectAndroidVariants
295+ */
296+ private fun getAgpVariantInfo (
297+ project : Project ,
298+ ): Provider <Set <AndroidVariantInfo >> {
299+ val androidVariants = objects.setProperty(AndroidVariantInfo ::class )
300+
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) }
305+ }
306+
307+ return androidVariants
308+ }
309+
321310 /* * Create a single [KotlinCompilationDetails] for [compilation]. */
322311 private fun createCompilationDetails (
323312 compilation : KotlinCompilation <* >,
@@ -444,34 +433,45 @@ private class KotlinCompilationDetailsBuilder(
444433 }
445434 }
446435
447- companion object {
436+ /* *
437+ * Determine if a [KotlinCompilation] is 'publishable', and so should be enabled by default
438+ * when creating a Dokka publication.
439+ *
440+ * Typically, 'main' compilations are publishable and 'test' compilations should be suppressed.
441+ * This can be overridden manually, though.
442+ *
443+ * @see DokkaSourceSetSpec.suppress
444+ */
445+ private fun KotlinCompilation <* >.isPublished (): Provider <Boolean > {
446+ return when (this ) {
447+ is KotlinMetadataCompilation <* > ->
448+ providers.provider { true }
448449
449- /* *
450- * Determine if a [KotlinCompilation] is 'publishable', and so should be enabled by default
451- * when creating a Dokka publication.
452- *
453- * Typically, 'main' compilations are publishable and 'test' compilations should be suppressed.
454- * This can be overridden manually, though.
455- *
456- * @see DokkaSourceSetSpec.suppress
457- */
458- private fun KotlinCompilation <* >.isPublished (): Boolean {
459- return when (this ) {
460- is KotlinMetadataCompilation <* > -> true
461-
462- is KotlinJvmAndroidCompilation -> {
463- // Use string-based comparison, not the actual classes, because AGP has deprecated and
464- // moved the Library/Application classes to a different package.
465- // Using strings is more widely compatible.
466- val variantName = androidVariant::class .jvmName
467- " LibraryVariant" in variantName || " ApplicationVariant" in variantName
468- }
450+ is KotlinJvmAndroidCompilation -> {
451+ isJvmAndroidPublished(this )
452+ }
469453
470- else ->
471- name == MAIN_COMPILATION_NAME
454+ else ->
455+ providers.provider { name == MAIN_COMPILATION_NAME }
456+ }
457+ }
458+
459+ private fun isJvmAndroidPublished (
460+ compilation : KotlinJvmAndroidCompilation ,
461+ ): Provider <Boolean > {
462+ return androidComponentsInfo.map { components ->
463+ val compilationComponents = components.filter { it.name == compilation.name }
464+ val result = compilationComponents.any { component -> component.hasPublishedComponent }
465+ logger.info {
466+ " [KotlinAdapter isJvmAndroidPublished] ${compilation.name} publishable:$result , compilationComponents:$compilationComponents "
472467 }
468+ result
473469 }
474470 }
471+
472+ companion object {
473+ private val logger: Logger = Logging .getLogger(KotlinAdapter ::class .java)
474+ }
475475}
476476
477477
@@ -514,7 +514,7 @@ private abstract class KotlinSourceSetDetails @Inject constructor(
514514 */
515515 fun isPublishedSourceSet (): Provider <Boolean > =
516516 allCompilations.map { values ->
517- values.any { it.publishedCompilation }
517+ values.any { it.publishedCompilation.get() }
518518 }
519519
520520 override fun getName (): String = named
@@ -624,3 +624,89 @@ private class KotlinSourceSetDetailsBuilder(
624624 )
625625 }
626626}
627+
628+
629+ /* * Try and get [KotlinProjectExtension], or `null` if it's not present. */
630+ private fun Project.findKotlinExtension (): KotlinProjectExtension ? =
631+ findExtensionLenient<KotlinProjectExtension >(" kotlin" )
632+
633+
634+ /* * Try and get [AndroidComponentsExtension], or `null` if it's not present. */
635+ private fun Project.findAndroidComponentExtension (): AndroidComponentsExtension <* , * , * >? =
636+ findExtensionLenient<AndroidComponentsExtension <* , * , * >>(" androidComponents" )
637+
638+
639+ /* *
640+ * Store details about a [Variant].
641+ *
642+ * @param[name] [Variant.name].
643+ * @param[hasPublishedComponent] `true` if any component of the variant is 'published',
644+ * i.e. it is an instance of [Variant].
645+ */
646+ private data class AndroidVariantInfo (
647+ val name : String ,
648+ val hasPublishedComponent : Boolean ,
649+ )
650+
651+ /* *
652+ * Collect [AndroidVariantInfo]s of the Android [Variant]s in this Android project.
653+ *
654+ * We store the collected data in a custom class to aid with Configuration Cache compatibility.
655+ *
656+ * This function must only be called when AGP is applied
657+ * (otherwise [findAndroidComponentExtension] will return `null`),
658+ * i.e. inside a `withPlugin(...) {}` block.
659+ *
660+ * ## How to determine publishability of AGP Variants
661+ *
662+ * There are several Android Gradle plugins.
663+ * Each AGP has a specific associated [Variant]:
664+ * - `com.android.application` - [com.android.build.api.variant.ApplicationVariant]
665+ * - `com.android.library` - [com.android.build.api.variant.DynamicFeatureVariant]
666+ * - `com.android.test` - [com.android.build.api.variant.LibraryVariant]
667+ * - `com.android.dynamic-feature` - [com.android.build.api.variant.TestVariant]
668+ *
669+ * A [Variant] is 'published' (or otherwise shared with other projects).
670+ * Note that a [Variant] might have [nestedComponents][Variant.nestedComponents].
671+ * If any of these [com.android.build.api.variant.Component]s are [Variant]s,
672+ * then the [Variant] itself should be considered 'publishable'.
673+ *
674+ * If a [KotlinSourceSet] has an associated [Variant],
675+ * it should therefore be documented by Dokka by default.
676+ *
677+ * ### Associating Variants with Compilations with SourceSets
678+ *
679+ * So, how can we associate a [KotlinSourceSet] with a [Variant]?
680+ *
681+ * Fortunately, Dokka already knows about the [KotlinCompilation]s associated with a specific [KotlinSourceSet].
682+ *
683+ * So, for each [KotlinCompilation], find a [Variant] with the same name,
684+ * i.e. [KotlinCompilation.getName] is the same as [Variant.name].
685+ *
686+ * Next, determine if the [Variant] associated with a [KotlinCompilation] is 'publishable' by
687+ * checking if it _or_ any of its [nestedComponents][Variant.nestedComponents]
688+ * are 'publishable' (i.e. is an instance of [Variant]).
689+ * (We can we use [Variant.components] to check both the [Variant] and its `nestedComponents` the same time.)
690+ */
691+ private fun collectAndroidVariants (
692+ project : Project ,
693+ androidVariants : SetProperty <AndroidVariantInfo >,
694+ ) {
695+ val androidComponents = project.findAndroidComponentExtension()
696+
697+ androidComponents?.onVariants { variant ->
698+ val hasPublishedComponent =
699+ variant.components.any { component ->
700+ // a Variant is a subtype of a Component that is shared with consumers,
701+ // so Dokka should consider it 'publishable'
702+ component is Variant
703+ }
704+
705+ androidVariants.add(
706+ AndroidVariantInfo (
707+ name = variant.name,
708+ hasPublishedComponent = hasPublishedComponent,
709+ )
710+ )
711+ }
712+ }
0 commit comments