aboutsummaryrefslogtreecommitdiff
path: root/dokka-runners
diff options
context:
space:
mode:
Diffstat (limited to 'dokka-runners')
-rw-r--r--dokka-runners/runner-cli/api/runner-cli.api100
-rw-r--r--dokka-runners/runner-cli/build.gradle.kts26
-rw-r--r--dokka-runners/runner-cli/gradle.properties11
-rw-r--r--dokka-runners/runner-cli/settings.gradle.kts31
-rw-r--r--dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/CliArgumentTypes.kt97
-rw-r--r--dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/GlobalArguments.kt168
-rw-r--r--dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/LinkMapper.kt41
-rw-r--r--dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/PackageOptionsParser.kt38
-rw-r--r--dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/SourceSetArgumentsParser.kt165
-rw-r--r--dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/main.kt32
-rw-r--r--dokka-runners/runner-cli/src/test/kotlin/org/jetbrains/dokka/CliTest.kt46
-rw-r--r--dokka-runners/runner-cli/src/test/resources/my-file-no-sourceset-options.json13
-rw-r--r--dokka-runners/runner-cli/src/test/resources/my-file.json51
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/MIGRATION.md167
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/api/runner-gradle-plugin-classic.api186
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/build.gradle.kts69
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/gradle.properties13
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/settings.gradle.kts31
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt29
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout.kt94
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaPlugin.kt131
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaProperty.kt10
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetMapper.kt76
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt483
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderExtensions.kt36
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderFactory.kt12
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleExternalDocumentationLinkBuilder.kt86
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradlePackageOptionsBuilder.kt113
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceLinkBuilder.kt108
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/TaskDependencyInternalWithAdditions.kt22
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/automagicTypedProxy.kt49
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/checkChildDokkaTasksIsNotEmpty.kt47
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/checkDependentSourceSets.kt28
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaBootstrapFactory.kt22
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaDefaultOutputDirectory.kt17
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaSourceSetIDFactory.kt18
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/gradleConfigurations.kt53
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/internal/AbstractDokkaTaskExtensions.kt28
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinGradlePluginVersion.kt34
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinNativeDistributionAccessor.kt39
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinSourceSetGist.kt33
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/isMainSourceSet.kt32
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinClasspathUtils.kt109
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinCompilationUtils.kt81
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinSourceSetUtils.kt17
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/platformOfSourceSet.kt17
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/sourceSetKotlinGistConfiguration.kt32
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaLeafTask.kt41
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaParentTask.kt107
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaTask.kt257
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaCollectorTask.kt41
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaMultiModuleTask.kt115
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTask.kt29
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTaskPartial.kt32
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt49
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTaskTest.kt204
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AndroidAutoConfigurationTest.kt88
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt52
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/CheckSourceSetDependenciesTest.kt71
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/ConfigureWithKotlinSourceSetGistTest.kt170
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationJsonTest.kt72
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationSerializableTest.kt78
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayoutTest.kt138
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaPluginApplyTest.kt153
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt14
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderTest.kt494
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt103
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinSourceSetGistTest.kt248
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinGradlePluginVersionTest.kt79
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaCollectorTaskTest.kt113
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaMultiModuleTaskTest.kt244
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTaskTest.kt51
-rw-r--r--dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/utils/samWithReceiverWorkarounds.kt73
-rw-r--r--dokka-runners/runner-maven-plugin/api/runner-maven-plugin.api141
-rw-r--r--dokka-runners/runner-maven-plugin/build.gradle.kts137
-rw-r--r--dokka-runners/runner-maven-plugin/gradle.properties9
-rw-r--r--dokka-runners/runner-maven-plugin/pom.template.xml56
-rw-r--r--dokka-runners/runner-maven-plugin/settings.gradle.kts30
-rw-r--r--dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/DokkaMojo.kt615
-rw-r--r--dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/ExternalDocumentationLinkBuilder.kt68
-rw-r--r--dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/MavenDokkaLogger.kt44
-rw-r--r--dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/PackageOptions.kt89
-rw-r--r--dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/SourceLinkMapItem.kt69
83 files changed, 7515 insertions, 0 deletions
diff --git a/dokka-runners/runner-cli/api/runner-cli.api b/dokka-runners/runner-cli/api/runner-cli.api
new file mode 100644
index 00000000..cfa173c5
--- /dev/null
+++ b/dokka-runners/runner-cli/api/runner-cli.api
@@ -0,0 +1,100 @@
+public final class org/jetbrains/dokka/ArgTypeArgument : kotlinx/cli/ArgType {
+ public fun <init> (Lkotlinx/cli/CLIEntity;)V
+ public final fun component1 ()Lkotlinx/cli/CLIEntity;
+ public synthetic fun convert (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+ public fun convert (Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;
+ public final fun copy (Lkotlinx/cli/CLIEntity;)Lorg/jetbrains/dokka/ArgTypeArgument;
+ public static synthetic fun copy$default (Lorg/jetbrains/dokka/ArgTypeArgument;Lkotlinx/cli/CLIEntity;ILjava/lang/Object;)Lorg/jetbrains/dokka/ArgTypeArgument;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getDescription ()Ljava/lang/String;
+ public final fun getModuleName ()Lkotlinx/cli/CLIEntity;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/ArgTypeFile : kotlinx/cli/ArgType {
+ public static final field INSTANCE Lorg/jetbrains/dokka/ArgTypeFile;
+ public fun convert (Ljava/lang/String;Ljava/lang/String;)Ljava/io/File;
+ public synthetic fun convert (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+ public fun getDescription ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/ArgTypeHelpSourceSet : kotlinx/cli/ArgType {
+ public fun <init> (Lkotlinx/cli/CLIEntity;)V
+ public final fun component1 ()Lkotlinx/cli/CLIEntity;
+ public fun convert (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+ public final fun copy (Lkotlinx/cli/CLIEntity;)Lorg/jetbrains/dokka/ArgTypeHelpSourceSet;
+ public static synthetic fun copy$default (Lorg/jetbrains/dokka/ArgTypeHelpSourceSet;Lkotlinx/cli/CLIEntity;ILjava/lang/Object;)Lorg/jetbrains/dokka/ArgTypeHelpSourceSet;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getDescription ()Ljava/lang/String;
+ public final fun getModuleName ()Lkotlinx/cli/CLIEntity;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/ArgTypePlatform : kotlinx/cli/ArgType {
+ public static final field INSTANCE Lorg/jetbrains/dokka/ArgTypePlatform;
+ public synthetic fun convert (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+ public fun convert (Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/dokka/Platform;
+ public fun getDescription ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/ArgTypePlugin : kotlinx/cli/ArgType {
+ public static final field INSTANCE Lorg/jetbrains/dokka/ArgTypePlugin;
+ public synthetic fun convert (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+ public fun convert (Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/dokka/DokkaConfiguration$PluginConfiguration;
+ public fun getDescription ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/ArgTypeSourceLinkDefinition : kotlinx/cli/ArgType {
+ public static final field INSTANCE Lorg/jetbrains/dokka/ArgTypeSourceLinkDefinition;
+ public synthetic fun convert (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+ public fun convert (Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/dokka/DokkaConfiguration$SourceLinkDefinition;
+ public fun getDescription ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/ArgTypeVisibility : kotlinx/cli/ArgType {
+ public static final field INSTANCE Lorg/jetbrains/dokka/ArgTypeVisibility;
+ public synthetic fun convert (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+ public fun convert (Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/dokka/DokkaConfiguration$Visibility;
+ public fun getDescription ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/GlobalArguments : org/jetbrains/dokka/DokkaConfiguration {
+ public fun <init> ([Ljava/lang/String;)V
+ public fun getCacheRoot ()Ljava/io/File;
+ public fun getDelayTemplateSubstitution ()Z
+ public fun getFailOnWarning ()Z
+ public fun getFinalizeCoroutines ()Z
+ public final fun getGlobalLinks ()Ljava/util/List;
+ public final fun getGlobalPackageOptions ()Ljava/util/List;
+ public final fun getGlobalSrcLink ()Ljava/util/List;
+ public final fun getHelpSourceSet ()Ljava/lang/Object;
+ public fun getIncludes ()Ljava/util/Set;
+ public final fun getJson ()Ljava/lang/String;
+ public final fun getLogger ()Lorg/jetbrains/dokka/utilities/DokkaLogger;
+ public final fun getLoggingLevel ()Lorg/jetbrains/dokka/utilities/LoggingLevel;
+ public fun getModuleName ()Ljava/lang/String;
+ public fun getModuleVersion ()Ljava/lang/String;
+ public fun getModules ()Ljava/util/List;
+ public final fun getNoSuppressObviousFunctions ()Z
+ public fun getOfflineMode ()Z
+ public fun getOutputDir ()Ljava/io/File;
+ public final fun getParser ()Lkotlinx/cli/ArgParser;
+ public fun getPluginsClasspath ()Ljava/util/List;
+ public fun getPluginsConfiguration ()Ljava/util/List;
+ public fun getSourceSets ()Ljava/util/List;
+ public fun getSuppressInheritedMembers ()Z
+ public fun getSuppressObviousFunctions ()Z
+}
+
+public final class org/jetbrains/dokka/LinkMapperKt {
+ public static final fun defaultLinks (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Ljava/util/List;
+ public static final fun parseLinks (Ljava/util/List;)Ljava/util/List;
+}
+
+public final class org/jetbrains/dokka/MainKt {
+ public static final fun initializeConfiguration (Lorg/jetbrains/dokka/GlobalArguments;)Lorg/jetbrains/dokka/DokkaConfiguration;
+ public static final fun main ([Ljava/lang/String;)V
+}
+
diff --git a/dokka-runners/runner-cli/build.gradle.kts b/dokka-runners/runner-cli/build.gradle.kts
new file mode 100644
index 00000000..c078e22c
--- /dev/null
+++ b/dokka-runners/runner-cli/build.gradle.kts
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import dokkabuild.overridePublicationArtifactId
+
+plugins {
+ id("dokkabuild.kotlin-jvm")
+ id("dokkabuild.publish-shadow")
+ alias(libs.plugins.kotlinx.binaryCompatibilityValidator)
+}
+
+overridePublicationArtifactId("dokka-cli")
+
+dependencies {
+ implementation("org.jetbrains.dokka:dokka-core")
+ implementation(libs.kotlinx.cli)
+
+ testImplementation(kotlin("test"))
+}
+
+tasks.shadowJar {
+ manifest {
+ attributes("Main-Class" to "org.jetbrains.dokka.MainKt")
+ }
+}
diff --git a/dokka-runners/runner-cli/gradle.properties b/dokka-runners/runner-cli/gradle.properties
new file mode 100644
index 00000000..08f2ce2a
--- /dev/null
+++ b/dokka-runners/runner-cli/gradle.properties
@@ -0,0 +1,11 @@
+#
+# Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
+# Project Settings
+group=org.jetbrains.dokka
+version=1.9.20-SNAPSHOT
+
+org.jetbrains.dokka.javaToolchain.mainCompiler=8
+org.jetbrains.dokka.javaToolchain.testLauncher=8
+org.jetbrains.dokka.kotlinLanguageLevel=1.4
diff --git a/dokka-runners/runner-cli/settings.gradle.kts b/dokka-runners/runner-cli/settings.gradle.kts
new file mode 100644
index 00000000..37b36fda
--- /dev/null
+++ b/dokka-runners/runner-cli/settings.gradle.kts
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+rootProject.name = "runner-cli"
+
+pluginManagement {
+ includeBuild("../../build-logic")
+
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ google()
+ }
+
+ versionCatalogs {
+ create("libs") {
+ from(files("../../gradle/libs.versions.toml"))
+ }
+ }
+}
+
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
diff --git a/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/CliArgumentTypes.kt b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/CliArgumentTypes.kt
new file mode 100644
index 00000000..1c6b0ba4
--- /dev/null
+++ b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/CliArgumentTypes.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka
+
+import kotlinx.cli.ArgParser
+import kotlinx.cli.ArgType
+import kotlinx.cli.CLIEntity
+import java.io.File
+import java.nio.file.Paths
+
+
+public object ArgTypeFile : ArgType<File>(true) {
+ override fun convert(value: kotlin.String, name: kotlin.String): File = Paths.get(value).toRealPath().toFile()
+ override val description: kotlin.String
+ get() = "{ String that represents a directory / file path }"
+}
+
+public object ArgTypePlatform : ArgType<Platform>(true) {
+ override fun convert(value: kotlin.String, name: kotlin.String): Platform = Platform.fromString(value)
+ override val description: kotlin.String
+ get() = "{ String that represents a Kotlin platform. Possible values: jvm/js/native/common/android }"
+}
+
+public object ArgTypeVisibility : ArgType<DokkaConfiguration.Visibility>(true) {
+ override fun convert(value: kotlin.String, name: kotlin.String): DokkaConfiguration.Visibility {
+ return DokkaConfiguration.Visibility.fromString(value)
+ }
+
+ override val description: kotlin.String
+ get() = "{ String that represents a visibility modifier. Possible values: ${getPossibleVisibilityValues()}"
+
+ private fun getPossibleVisibilityValues(): kotlin.String =
+ DokkaConfiguration.Visibility.values().joinToString(separator = ", ")
+}
+
+public object ArgTypePlugin : ArgType<DokkaConfiguration.PluginConfiguration>(true) {
+ override fun convert(
+ value: kotlin.String,
+ name: kotlin.String
+ ): DokkaConfiguration.PluginConfiguration {
+ return value.split("=").let {
+ PluginConfigurationImpl(
+ fqPluginName = it[0],
+ serializationFormat = DokkaConfiguration.SerializationFormat.JSON,
+ values = it[1]
+ )
+ }
+ }
+
+ override val description: kotlin.String
+ get() = "{ String that represents plugin configuration. " +
+ "Format is {fullyQualifiedPluginName}={jsonConfiguration}. " +
+ "Quotation marks (`\"`) inside json must be escaped. }"
+}
+
+public object ArgTypeSourceLinkDefinition : ArgType<DokkaConfiguration.SourceLinkDefinition>(true) {
+ override fun convert(value: kotlin.String, name: kotlin.String): DokkaConfiguration.SourceLinkDefinition {
+ return if (value.isNotEmpty() && value.contains("="))
+ SourceLinkDefinitionImpl.parseSourceLinkDefinition(value)
+ else {
+ throw IllegalArgumentException(
+ "Warning: Invalid -srcLink syntax. " +
+ "Expected: <path>=<url>[#lineSuffix]. No source links will be generated."
+ )
+ }
+ }
+
+ override val description: kotlin.String
+ get() = "{ String that represent source links. Format: {srcPath}={remotePath#lineSuffix} }"
+}
+
+public data class ArgTypeArgument(val moduleName: CLIEntity<kotlin.String>) :
+ ArgType<DokkaConfiguration.DokkaSourceSet>(true) {
+ override fun convert(value: kotlin.String, name: kotlin.String): DokkaConfiguration.DokkaSourceSet =
+ (if (moduleName.valueOrigin != ArgParser.ValueOrigin.UNSET && moduleName.valueOrigin != ArgParser.ValueOrigin.UNDEFINED) {
+ moduleName.value
+ } else {
+ DokkaDefaults.moduleName
+ }).let { moduleNameOrDefault ->
+ parseSourceSet(moduleNameOrDefault, value.split(" ").filter { it.isNotBlank() }.toTypedArray())
+ }
+
+ override val description: kotlin.String
+ get() = ""
+}
+
+// Workaround for printing nested parsers help
+public data class ArgTypeHelpSourceSet(val moduleName: CLIEntity<kotlin.String>) : ArgType<Any>(false) {
+ override fun convert(value: kotlin.String, name: kotlin.String): Any = Any().also {
+ parseSourceSet(moduleName.value, arrayOf("-h"))
+ }
+
+ override val description: kotlin.String
+ get() = ""
+}
diff --git a/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/GlobalArguments.kt b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/GlobalArguments.kt
new file mode 100644
index 00000000..5c95f63f
--- /dev/null
+++ b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/GlobalArguments.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka
+
+import kotlinx.cli.*
+import org.jetbrains.dokka.utilities.DokkaConsoleLogger
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.dokka.utilities.LoggingLevel
+import org.jetbrains.dokka.utilities.cast
+import java.io.File
+
+public class GlobalArguments(args: Array<String>) : DokkaConfiguration {
+
+ public val parser: ArgParser = ArgParser("dokka-cli", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
+
+ public val json: String? by parser.argument(ArgType.String, description = "JSON configuration file path").optional()
+
+ private val _moduleName = parser.option(
+ ArgType.String,
+ description = "Name of the project/module",
+ fullName = "moduleName"
+ ).default(DokkaDefaults.moduleName)
+
+ override val moduleName: String by _moduleName
+
+ override val moduleVersion: String? by parser.option(
+ ArgType.String,
+ description = "Documented version",
+ fullName = "moduleVersion"
+ )
+
+ override val outputDir: File by parser.option(ArgTypeFile, description = "Output directory path, ./dokka by default")
+ .default(DokkaDefaults.outputDir)
+
+ override val cacheRoot: File? = null
+
+ override val sourceSets: List<DokkaConfiguration.DokkaSourceSet> by parser.option(
+ ArgTypeArgument(_moduleName),
+ description = "Configuration for a Dokka source set. Contains nested configuration.",
+ fullName = "sourceSet"
+ ).multiple()
+
+ override val pluginsConfiguration: List<DokkaConfiguration.PluginConfiguration> by parser.option(
+ ArgTypePlugin,
+ description = "Configuration for Dokka plugins. Accepts multiple values separated by `^^`."
+ ).delimiter("^^")
+
+ override val pluginsClasspath: List<File> by parser.option(
+ ArgTypeFile,
+ fullName = "pluginsClasspath",
+ description = "List of jars with Dokka plugins and their dependencies. Accepts multiple paths separated by semicolons"
+ ).delimiter(";")
+
+ override val offlineMode: Boolean by parser.option(
+ ArgType.Boolean,
+ description = "Whether to resolve remote files/links over network"
+ ).default(DokkaDefaults.offlineMode)
+
+ override val failOnWarning: Boolean by parser.option(
+ ArgType.Boolean,
+ description = "Whether to fail documentation generation if Dokka has emitted a warning or an error"
+ ).default(DokkaDefaults.failOnWarning)
+
+ override val delayTemplateSubstitution: Boolean by parser.option(
+ ArgType.Boolean,
+ description = "Delay substitution of some elements. Used in incremental builds of multimodule projects"
+ ).default(DokkaDefaults.delayTemplateSubstitution)
+
+ public val noSuppressObviousFunctions: Boolean by parser.option(
+ ArgType.Boolean,
+ description = "Whether to suppress obvious functions such as inherited from `kotlin.Any` and `java.lang.Object`"
+ ).default(!DokkaDefaults.suppressObviousFunctions)
+
+ override val suppressObviousFunctions: Boolean by lazy { !noSuppressObviousFunctions }
+
+ private val _includes by parser.option(
+ ArgTypeFile,
+ fullName = "includes",
+ description = "Markdown files that contain module and package documentation. " +
+ "Accepts multiple values separated by semicolons"
+ ).delimiter(";")
+
+ override val includes: Set<File> by lazy { _includes.toSet() }
+
+ override val suppressInheritedMembers: Boolean by parser.option(
+ ArgType.Boolean,
+ description = "Whether to suppress inherited members that aren't explicitly overridden in a given class"
+ ).default(DokkaDefaults.suppressInheritedMembers)
+
+ override val finalizeCoroutines: Boolean = true
+
+ public val globalPackageOptions: List<String> by parser.option(
+ ArgType.String,
+ description = "Global list of package configurations in format " +
+ "\"matchingRegexp,-deprecated,-privateApi,+warnUndocumented,+suppress;...\". " +
+ "Accepts multiple values separated by semicolons. "
+ ).delimiter(";")
+
+ public val globalLinks: List<String> by parser.option(
+ ArgType.String,
+ description = "Global external documentation links in format {url}^{packageListUrl}. " +
+ "Accepts multiple values separated by `^^`"
+ ).delimiter("^^")
+
+ public val globalSrcLink: List<String> by parser.option(
+ ArgType.String,
+ description = "Global mapping between a source directory and a Web service for browsing the code. " +
+ "Accepts multiple paths separated by semicolons"
+ ).delimiter(";")
+
+ public val helpSourceSet: Any? by parser.option(
+ ArgTypeHelpSourceSet(_moduleName),
+ description = "Prints help for nested -sourceSet configuration"
+ )
+
+ public val loggingLevel: LoggingLevel by parser.option(
+ ArgType.Choice(toVariant = {
+ when (it.toUpperCase().trim()) {
+ "DEBUG", "" -> LoggingLevel.DEBUG
+ "PROGRESS" -> LoggingLevel.PROGRESS
+ "INFO" -> LoggingLevel.INFO
+ "WARN" -> LoggingLevel.WARN
+ "ERROR" -> LoggingLevel.ERROR
+ else -> {
+ println("""Failed to deserialize logging level, got $it expected one of
+ |"DEBUG", "PROGRESS", "INFO", "WARN", "ERROR", falling back to PROGRESS""".trimMargin())
+ LoggingLevel.PROGRESS
+ }
+ }
+ }, toString = { it.toString() }
+ )).default(LoggingLevel.PROGRESS)
+
+ override val modules: List<DokkaConfiguration.DokkaModuleDescription> = emptyList()
+
+ public val logger: DokkaLogger by lazy {
+ DokkaConsoleLogger(loggingLevel)
+ }
+
+ init {
+ parser.parse(args)
+
+ sourceSets.forEach {
+ it.perPackageOptions.cast<MutableList<DokkaConfiguration.PackageOptions>>()
+ .addAll(parsePerPackageOptions(globalPackageOptions))
+ }
+
+ sourceSets.forEach {
+ it.externalDocumentationLinks.cast<MutableSet<DokkaConfiguration.ExternalDocumentationLink>>().addAll(parseLinks(globalLinks))
+ }
+
+ globalSrcLink.forEach {
+ if (it.isNotEmpty() && it.contains("="))
+ sourceSets.all { sourceSet ->
+ sourceSet.sourceLinks.cast<MutableSet<SourceLinkDefinitionImpl>>()
+ .add(SourceLinkDefinitionImpl.parseSourceLinkDefinition(it))
+ }
+ else {
+ logger.warn("Invalid -srcLink syntax. Expected: <path>=<url>[#lineSuffix]. No source links will be generated.")
+ }
+ }
+
+ sourceSets.forEach {
+ it.externalDocumentationLinks.cast<MutableSet<DokkaConfiguration.ExternalDocumentationLink>>().addAll(defaultLinks(it))
+ }
+ }
+}
diff --git a/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/LinkMapper.kt b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/LinkMapper.kt
new file mode 100644
index 00000000..dbfa8db1
--- /dev/null
+++ b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/LinkMapper.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka
+
+import java.io.File
+import java.net.MalformedURLException
+import java.net.URL
+
+@OptIn(ExperimentalStdlibApi::class) // for buildList
+public fun defaultLinks(config: DokkaConfiguration.DokkaSourceSet): MutableList<DokkaConfiguration.ExternalDocumentationLink> =
+ buildList<DokkaConfiguration.ExternalDocumentationLink> {
+ if (!config.noJdkLink) {
+ add(DokkaConfiguration.ExternalDocumentationLink.jdk(config.jdkVersion))
+ }
+
+ if (!config.noStdlibLink) {
+ add(DokkaConfiguration.ExternalDocumentationLink.kotlinStdlib())
+ }
+ }.toMutableList()
+
+
+public fun parseLinks(links: List<String>): List<DokkaConfiguration.ExternalDocumentationLink> {
+ val (parsedLinks, parsedOfflineLinks) = links
+ .map { it.split("^").map { it.trim() }.filter { it.isNotBlank() } }
+ .filter { it.isNotEmpty() }
+ .partition { it.size == 1 }
+
+ return parsedLinks.map { (root) -> ExternalDocumentationLink(root) } +
+ parsedOfflineLinks.map { (root, packageList) ->
+ val rootUrl = URL(root)
+ val packageListUrl =
+ try {
+ URL(packageList)
+ } catch (ex: MalformedURLException) {
+ File(packageList).toURI().toURL()
+ }
+ ExternalDocumentationLink(rootUrl, packageListUrl)
+ }
+}
diff --git a/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/PackageOptionsParser.kt b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/PackageOptionsParser.kt
new file mode 100644
index 00000000..377e32e6
--- /dev/null
+++ b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/PackageOptionsParser.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka
+
+internal fun parsePerPackageOptions(args: List<String>): List<DokkaConfiguration.PackageOptions> = args.map { it.split(",") }.map {
+ val matchingRegex = it.first()
+
+ val options = it.subList(1, it.size)
+
+ val deprecated = options.find { it.endsWith("skipDeprecated") }?.startsWith("+")
+ ?: DokkaDefaults.skipDeprecated
+
+ val reportUndocumented = options.find { it.endsWith("reportUndocumented") }?.startsWith("+")
+ ?: DokkaDefaults.reportUndocumented
+
+ val privateApi = options.find { it.endsWith("includeNonPublic") }?.startsWith("+")
+ ?: DokkaDefaults.includeNonPublic
+
+ val suppress = options.find { it.endsWith("suppress") }?.startsWith("+")
+ ?: DokkaDefaults.suppress
+
+ val documentedVisibilities = options
+ .filter { it.matches(Regex("\\+visibility:.+")) } // matches '+visibility:' with at least one symbol after the semicolon
+ .map { DokkaConfiguration.Visibility.fromString(it.split(":")[1]) }
+ .toSet()
+ .ifEmpty { DokkaDefaults.documentedVisibilities }
+
+ PackageOptionsImpl(
+ matchingRegex,
+ includeNonPublic = privateApi,
+ documentedVisibilities = documentedVisibilities,
+ reportUndocumented = reportUndocumented,
+ skipDeprecated = !deprecated,
+ suppress = suppress
+ )
+}
diff --git a/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/SourceSetArgumentsParser.kt b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/SourceSetArgumentsParser.kt
new file mode 100644
index 00000000..a1c44a13
--- /dev/null
+++ b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/SourceSetArgumentsParser.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka
+
+import kotlinx.cli.ArgParser
+import kotlinx.cli.ArgType
+import kotlinx.cli.default
+import kotlinx.cli.delimiter
+
+internal fun parseSourceSet(moduleName: String, args: Array<String>): DokkaConfiguration.DokkaSourceSet {
+
+ if (moduleName.contains(',')) {
+ // To figure out why this is needed and if it is still relevant, see the comment here:
+ // https://github.com/Kotlin/dokka/issues/3011#issuecomment-1568620493
+ throw IllegalArgumentException("Module name cannot contain commas as it is used internally as a delimiter.")
+ }
+
+ val parser = ArgParser("sourceSet", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
+
+ val sourceSetName by parser.option(
+ ArgType.String,
+ description = "Name of the source set"
+ ).default("main")
+
+ val displayName by parser.option(
+ ArgType.String,
+ description = "Display name of the source set, used both internally and externally"
+ ).default(DokkaDefaults.sourceSetDisplayName)
+
+ val classpath by parser.option(
+ ArgTypeFile,
+ description = "Classpath for analysis and interactive samples. Accepts multiple paths separated by semicolons"
+ ).delimiter(";")
+
+ val sourceRoots by parser.option(
+ ArgTypeFile,
+ description = "Source code roots to be analyzed and documented. Accepts multiple paths separated by semicolons",
+ fullName = "src"
+ ).delimiter(";")
+
+ val dependentSourceSets by parser.option(
+ ArgType.String,
+ description = "Names of dependent source sets in format \"moduleName/sourceSetName\". " +
+ "Accepts multiple paths separated by semicolons"
+ ).delimiter(";")
+
+ val samples by parser.option(
+ ArgTypeFile,
+ description = "List of directories or files that contain sample functions. " +
+ "Accepts multiple paths separated by semicolons"
+ ).delimiter(";")
+
+ val includes by parser.option(
+ ArgTypeFile,
+ description = "Markdown files that contain module and package documentation. " +
+ "Accepts multiple paths separated by semicolons"
+ ).delimiter(";")
+
+ val includeNonPublic: Boolean by parser.option(
+ ArgType.Boolean,
+ description = "Deprecated, use documentedVisibilities")
+ .default(DokkaDefaults.includeNonPublic)
+
+ val documentedVisibilities by parser.option(
+ ArgTypeVisibility,
+ description = "Visibilities to be documented. Accepts multiple values separated by semicolons"
+ ).delimiter(";")
+
+ val reportUndocumented by parser.option(ArgType.Boolean, description = "Whether to report undocumented declarations")
+ .default(DokkaDefaults.reportUndocumented)
+
+ val noSkipEmptyPackages by parser.option(
+ ArgType.Boolean,
+ description = "Whether to create pages for empty packages"
+ ).default(!DokkaDefaults.skipEmptyPackages)
+
+ val skipEmptyPackages by lazy { !noSkipEmptyPackages }
+
+ val skipDeprecated by parser.option(ArgType.Boolean, description = "Whether to skip deprecated declarations")
+ .default(DokkaDefaults.skipDeprecated)
+
+ val jdkVersion by parser.option(
+ ArgType.Int,
+ description = "Version of JDK to use for linking to JDK Javadocs"
+ ).default(DokkaDefaults.jdkVersion)
+
+ val languageVersion by parser.option(
+ ArgType.String,
+ description = "Language version used for setting up analysis and samples"
+ )
+
+ val apiVersion by parser.option(
+ ArgType.String,
+ description = "Kotlin API version used for setting up analysis and samples"
+ )
+
+ val noStdlibLink by parser.option(ArgType.Boolean, description = "Whether to generate links to Standard library")
+ .default(DokkaDefaults.noStdlibLink)
+
+ val noJdkLink by parser.option(ArgType.Boolean, description = "Whether to generate links to JDK Javadocs")
+ .default(DokkaDefaults.noJdkLink)
+
+ val suppressedFiles by parser.option(
+ ArgTypeFile,
+ description = "Paths to files to be suppressed. Accepts multiple paths separated by semicolons."
+ ).delimiter(";")
+
+ val analysisPlatform: Platform by parser.option(
+ ArgTypePlatform,
+ description = "Platform used for setting up analysis"
+ ).default(DokkaDefaults.analysisPlatform)
+
+ val perPackageOptions by parser.option(
+ ArgType.String,
+ description = "List of package source set configuration in format " +
+ "\"matchingRegexp,-deprecated,-privateApi,+warnUndocumented,+suppress;...\". " +
+ "Accepts multiple values separated by semicolons. "
+ ).delimiter(";")
+
+ val externalDocumentationLinks by parser.option(
+ ArgType.String,
+ description = "External documentation links in format {url}^{packageListUrl}. " +
+ "Accepts multiple values separated by `^^`"
+ ).delimiter("^^")
+
+ val sourceLinks by parser.option(
+ ArgTypeSourceLinkDefinition,
+ description = "Mapping between a source directory and a Web service for browsing the code. " +
+ "Accepts multiple paths separated by semicolons",
+ fullName = "srcLink"
+ ).delimiter(";")
+
+ parser.parse(args)
+
+ return object : DokkaConfiguration.DokkaSourceSet {
+ override val displayName = displayName
+ override val sourceSetID = DokkaSourceSetID(moduleName, sourceSetName)
+ override val classpath = classpath.toMutableList()
+ override val sourceRoots = sourceRoots.toMutableSet()
+ override val dependentSourceSets = dependentSourceSets
+ .map { dependentSourceSetName -> dependentSourceSetName.split('/').let { DokkaSourceSetID(it[0], it[1]) } }
+ .toMutableSet()
+ override val samples = samples.toMutableSet()
+ override val includes = includes.toMutableSet()
+ @Deprecated("Use [documentedVisibilities] property for a more flexible control over documented visibilities")
+ override val includeNonPublic = includeNonPublic
+ override val reportUndocumented = reportUndocumented
+ override val skipEmptyPackages = skipEmptyPackages
+ override val skipDeprecated = skipDeprecated
+ override val jdkVersion = jdkVersion
+ override val sourceLinks = sourceLinks.toMutableSet()
+ override val analysisPlatform = analysisPlatform
+ override val perPackageOptions = parsePerPackageOptions(perPackageOptions).toMutableList()
+ override val externalDocumentationLinks = parseLinks(externalDocumentationLinks).toMutableSet()
+ override val languageVersion = languageVersion
+ override val apiVersion = apiVersion
+ override val noStdlibLink = noStdlibLink
+ override val noJdkLink = noJdkLink
+ override val suppressedFiles = suppressedFiles.toMutableSet()
+ override val documentedVisibilities: Set<DokkaConfiguration.Visibility> = documentedVisibilities.toSet()
+ .ifEmpty { DokkaDefaults.documentedVisibilities }
+ }
+}
diff --git a/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/main.kt b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/main.kt
new file mode 100644
index 00000000..e1949a93
--- /dev/null
+++ b/dokka-runners/runner-cli/src/main/kotlin/org/jetbrains/dokka/main.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka
+
+import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink
+import org.jetbrains.dokka.utilities.*
+import java.nio.file.Paths
+
+public fun main(args: Array<String>) {
+ val globalArguments = GlobalArguments(args)
+ val configuration = initializeConfiguration(globalArguments)
+ DokkaGenerator(configuration, globalArguments.logger).generate()
+}
+
+public fun initializeConfiguration(globalArguments: GlobalArguments): DokkaConfiguration {
+ return if (globalArguments.json != null) {
+ val jsonContent = Paths.get(checkNotNull(globalArguments.json)).toFile().readText()
+ val globals = GlobalDokkaConfiguration(jsonContent)
+ val dokkaConfigurationImpl = DokkaConfigurationImpl(jsonContent)
+
+ dokkaConfigurationImpl.apply(globals).apply {
+ sourceSets.forEach {
+ it.externalDocumentationLinks.cast<MutableSet<ExternalDocumentationLink>>().addAll(defaultLinks(it))
+ }
+ }
+ } else {
+ globalArguments
+ }
+}
+
diff --git a/dokka-runners/runner-cli/src/test/kotlin/org/jetbrains/dokka/CliTest.kt b/dokka-runners/runner-cli/src/test/kotlin/org/jetbrains/dokka/CliTest.kt
new file mode 100644
index 00000000..48441e4c
--- /dev/null
+++ b/dokka-runners/runner-cli/src/test/kotlin/org/jetbrains/dokka/CliTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka
+
+import java.nio.file.Paths
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class CliIntegrationTest {
+
+ @Test
+ fun `should apply global settings to all source sets`() {
+ val jsonPath = Paths.get(javaClass.getResource("/my-file.json")?.toURI() ?: throw IllegalStateException("No JSON found!")).toFile().toString()
+ val globalArguments = GlobalArguments(arrayOf(jsonPath))
+
+ val configuration = initializeConfiguration(globalArguments)
+
+ configuration.sourceSets.forEach {
+ assertTrue(it.perPackageOptions.isNotEmpty())
+ assertTrue(it.sourceLinks.isNotEmpty())
+ assertTrue(it.externalDocumentationLinks.isNotEmpty())
+
+ assertTrue(it.externalDocumentationLinks.any { it.url.toString() == "https://docs.oracle.com/javase/8/docs/api/" })
+ assertEquals(it.sourceLinks.single().localDirectory, "/home/Vadim.Mishenev/dokka/examples/cli/src/main/kotlin")
+ assertEquals(it.perPackageOptions.single().matchingRegex, "my-custom-regex")
+ }
+
+ }
+
+ @Test
+ fun `should not fail when no sourceset options are specified`() {
+ val jsonPath = Paths.get(javaClass.getResource("/my-file-no-sourceset-options.json")?.toURI() ?: throw IllegalStateException("No JSON found!")).toFile().toString()
+ val globalArguments = GlobalArguments(arrayOf(jsonPath))
+
+ val configuration = initializeConfiguration(globalArguments)
+
+ configuration.sourceSets.forEach {
+ assertTrue(it.perPackageOptions.isEmpty())
+ assertTrue(it.sourceLinks.isEmpty())
+ assertTrue(it.externalDocumentationLinks.size == 2) // there are default values, java and kotlin stdlibs
+ }
+ }
+}
diff --git a/dokka-runners/runner-cli/src/test/resources/my-file-no-sourceset-options.json b/dokka-runners/runner-cli/src/test/resources/my-file-no-sourceset-options.json
new file mode 100644
index 00000000..3a8643d1
--- /dev/null
+++ b/dokka-runners/runner-cli/src/test/resources/my-file-no-sourceset-options.json
@@ -0,0 +1,13 @@
+{
+ "outputDir": "build/docs",
+ "sourceSets": [
+ {
+ "moduleDisplayName": "Sample",
+ "sourceSetID": {
+ "scopeId": "sample",
+ "sourceSetName": "main"
+ },
+ "sourceRoots": ["sample"]
+ }
+ ]
+}
diff --git a/dokka-runners/runner-cli/src/test/resources/my-file.json b/dokka-runners/runner-cli/src/test/resources/my-file.json
new file mode 100644
index 00000000..49dda814
--- /dev/null
+++ b/dokka-runners/runner-cli/src/test/resources/my-file.json
@@ -0,0 +1,51 @@
+{
+ "moduleName": "Dokka Example",
+ "moduleVersion": null,
+ "outputDir": "$outputPath",
+ "pluginsClasspath": ["$pluginsClasspath"],
+ "cacheRoot": null,
+ "offlineMode": false,
+ "sourceLinks": [{
+ "localDirectory": "/home/Vadim.Mishenev/dokka/examples/cli/src/main/kotlin",
+ "remoteUrl": "https://github.com/Kotlin/dokka/tree/master/examples/gradle/dokka-gradle-example/src/main/kotlin",
+ "remoteLineSuffix": "#L"
+ }],
+ "externalDocumentationLinks": [{
+ "url": "https://docs.oracle.com/javase/8/docs/api/",
+ "packageListUrl": "https://docs.oracle.com/javase/8/docs/api/package-list"
+ }],
+ "perPackageOptions": [{
+ "matchingRegex": "my-custom-regex",
+ "skipDeprecated": "true",
+ "reportUndocumented": "true",
+ "includeNonPublic": "true",
+ "documentedVisibilities": ["PUBLIC", "PRIVATE", "PROTECTED", "INTERNAL", "PACKAGE"]
+ }],
+ "sourceSets": [
+ {
+ "displayName": "jvm",
+ "sourceSetID": {
+ "scopeId": ":dokkaHtml",
+ "sourceSetName": "main"
+ },
+ "sourceRoots": [
+ "$projectPath"
+ ],
+ "dependentSourceSets": [],
+ "samples": [],
+ "includes": [],
+ "includeNonPublic": false,
+ "reportUndocumented": false,
+ "skipEmptyPackages": true,
+ "skipDeprecated": false,
+ "jdkVersion": 8,
+ "sourceLinks": [],
+ "perPackageOptions": [],
+ "externalDocumentationLinks": [],
+ "noStdlibLink": false,
+ "noJdkLink": false,
+ "suppressedFiles": [],
+ "analysisPlatform": "jvm"
+ }
+ ]
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/MIGRATION.md b/dokka-runners/runner-gradle-plugin-classic/MIGRATION.md
new file mode 100644
index 00000000..7f881bfa
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/MIGRATION.md
@@ -0,0 +1,167 @@
+### Configuring a dokka task
+Dokka 1.4.x will create dedicated tasks for each format.
+You can expect the following formats being registered and configured by default:
+- `dokkaHtml`
+- `dokkaJavadoc`
+- `dokkaGfm`
+- `dokkaJekyll`
+
+Therefore, you need to either select specifically which task you want to configure or configure all with Type `DokkaTask`
+
+```kotlin
+/* 0.10.x */
+tasks.dokka.configure { /*...*/ }
+
+/* 1.4.x */
+// configure all formats
+tasks.withType<DokkaTask>().configureEach { /*...*/ }
+
+// configure only html format e.g.
+tasks.dokkaHtml.configure { /*...*/ }
+```
+
+#### properties
+```kotlin
+/* 0.10.x */ outputFormat = "html"
+/* 1.4.x No equivalent.
+ Formats are only configured by plugins.
+ See `dokkaHtml`, `dokkaJavadoc`,... tasks */
+
+/* 0.10.x */ outputDirectory = "$buildDir/javadoc"
+/* 1.4.x */ outputDirectory.set(buildDir.resolve("javadoc"))
+
+
+/* 0.10.x */ subProjects = ["subproject1", "subproject2"]
+/* 1.4.x No equivalent.
+ See `DokkaCollectorTask` and `DokkaMultiModuleTask` */
+
+
+/* 0.10.x */ disableAutoconfiguration = false
+/* 1.4.x No equivalent.
+ Source sets are synced with Kotlin Model by default.
+ All settings can still be overridden */
+
+/* 0.10.x */ cacheRoot = "default"
+/* 1.4.x */ cacheRoot.set(file("default"))
+
+```
+
+### Configure a source set
+```kotlin
+/* 0.10.x */
+tasks.dokka.configure {
+ configuration {
+ // ...
+ }
+}
+
+/* 1.4.x */
+tasks.dokkaHtml.configure {
+ dokkaSourceSets {
+ named("main") { /* configure main source set */ }
+ configureEach { /* configure all source sets */ }
+ register("custom") { /* register custom source set */ }
+ }
+}
+```
+
+#### Properties
+```kotlin
+/* 0.10.x */ moduleName = "myModule"
+/* 1.4.0 */ /* Use AbstractDokkaTask#moduleDisplayName instead */
+/* 1.4.10+ */ /* Use AbstractDokkaTask#moduleName instead */
+
+/* 0.10.x */ includeNonPublic = false
+/* 1.4.x */ includeNonPublic.set(false)
+
+/* 0.10.x */ skipDeprecated = false
+/* 1.4.x */ skipDeprecated.set(false)
+
+/* 0.10.x */ reportUndocumented = true
+/* 1.4.x */ reportUndocumented.set(true)
+
+/* 0.10.x */ skipEmptyPackages = true
+/* 1.4.x */ skipEmptyPackages.set(true)
+
+/* 0.10.x */ targets = ["JVM"]
+/* 1.4.x */ /* No equivalent */
+ /* Use platform and displayName instead */
+
+/* 0.10.x */ platform = "JVM"
+/* 1.4.x */ platform.set(org.jetbrains.dokka.Platform.jvm)
+/* 1.4.x */ platform.set(Platform.jvm) // with import
+
+/* 0.10.x */ classpath = [new File("$buildDir/other.jar")]
+/* 1.4.x */ classpath.setFrom(buildDir.resolve("other.jar")) // setting classpath
+/* 1.4.x */ classpath.from(buildDir.resolve("other.jar")) // adding to existing classpath
+
+/* 0.10.x */ sourceRoots = [files("src/main/kotlin")]
+/* 1.4.x */ sourceRoots.setFrom(file("src/main/kotlin")) // setting all source roots
+/* 1.4.x */ sourceRoots.from(file("src/main/kotlin")) // adding to existing source roots
+
+/* 0.10.x */ includes = ["packages.md", "extra.md"]
+/* 1.4.x */ includes.setFrom(files("packages.md", "extra.md")) // setting all includes
+/* 1.4.x */ includes.from(files("packages.md", "extra.md")) // adding to existing includes
+
+/* 0.10.x */ samples = ["samples/basic.kt", "samples/advanced.kt"]
+/* 1.4.x */ samples.setFrom(files("samples/basic.kt", "samples/advanced.kt"))
+/* 1.4.x */ samples.from(files("samples/basic.kt", "samples/advanced.kt"))
+
+/* 0.10.x */ kotlinTasks { /* ... */ }
+/* 1.4.x */ /* No *direct* equivalent */
+ /* Source sets synced with Kotlin Gradle Plugin will be configured properly */
+ /* Custom source sets can use extension `kotlinSourceSet(...)` */
+
+/* 0.10.x */ jdkVersion = 6
+/* 1.4.x */ jdkVersion.set(6)
+
+/* 0.10.x */ noStdlibLink = false
+/* 1.4.x */ noStdlibLink.set(false)
+
+/* 0.10.x */ noJdkLink = false
+/* 1.4.x */ noJdkLink.set(false)
+
+sourceLink {
+ /* 0.10.x */ path = "src/main/kotlin"
+ /* 1.4.x */ localDirectory.set(file("src/main/kotlin"))
+
+ /* 0.10.x */ url = "https://github.com/myproject/blob/master/src/main/kotlin"
+ /* 1.4.x */ remoteUrl.set(java.net.URL("https://github.com/myproject/blob/master/src/main/kotlin"))
+ /* 1.4.x */ remoteUrl.set(uri("https://github.com/myproject/blob/master/src/main/kotlin").toURL())
+
+ /* 0.10.x */ lineSuffix = "#L"
+ /* 1.4.x */ remoteLineSuffix.set("#L")
+}
+
+
+externalDocumentationLink {
+ /* 0.10.x */ url = URL("https://example.com/docs/")
+ /* 1.4.x */ url.set(URL("https://example.com/docs/"))
+
+ /* 0.10.x */ packageListUrl = URL("file:///home/user/localdocs/package-list")
+ /* 1.4.x */ packageListUrl.set(URL("file:///home/user/localdocs/package-list"))
+}
+
+// Allows to customize documentation generation options on a per-package basis
+// Repeat for multiple packageOptions
+perPackageOption {
+ /* 0.10.x */ prefix = "kotlin"
+ /* 1.4.x */ prefix.set("kotlin")
+ /* 1.4.20+ */ matchingRegex("kotlin($|\\.).*")
+
+ /* 0.10.x */ skipDeprecated = false
+ /* 1.4.x */ skipDeprecated.set(false)
+
+ /* 0.10.x */ reportUndocumented = true
+ /* 1.4.x */ reportUndocumented.set(true)
+
+ /* 0.10.x */ includeNonPublic = false
+ /* 1.4.x */ includeNonPublic.set(false)
+
+ /* 0.10.x */ suppress = true
+ /* 1.4.x */ suppress.set(true)
+
+}
+```
+
+For more information or help, feel free to ask questions in the [official Kotlin Slack Channel](https://kotlinlang.slack.com/)
diff --git a/dokka-runners/runner-gradle-plugin-classic/api/runner-gradle-plugin-classic.api b/dokka-runners/runner-gradle-plugin-classic/api/runner-gradle-plugin-classic.api
new file mode 100644
index 00000000..7e10ebf1
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/api/runner-gradle-plugin-classic.api
@@ -0,0 +1,186 @@
+public abstract class org/jetbrains/dokka/gradle/AbstractDokkaLeafTask : org/jetbrains/dokka/gradle/AbstractDokkaTask {
+ public fun <init> ()V
+ public final fun getDokkaSourceSets ()Lorg/gradle/api/NamedDomainObjectContainer;
+ protected final fun getUnsuppressedSourceSets ()Ljava/util/List;
+}
+
+public abstract class org/jetbrains/dokka/gradle/AbstractDokkaParentTask : org/jetbrains/dokka/gradle/AbstractDokkaTask {
+ public fun <init> ()V
+ public final fun addChildTask (Ljava/lang/String;)V
+ public final fun addChildTask (Lorg/jetbrains/dokka/gradle/AbstractDokkaTask;)V
+ public final fun addChildTasks (Ljava/lang/Iterable;Ljava/lang/String;)V
+ public final fun addSubprojectChildTasks (Ljava/lang/String;)V
+ public final fun removeChildTask (Ljava/lang/String;)V
+ public final fun removeChildTask (Lorg/jetbrains/dokka/gradle/AbstractDokkaTask;)V
+ public final fun removeChildTasks (Ljava/lang/Iterable;)V
+ public final fun removeChildTasks (Ljava/lang/Iterable;Ljava/lang/String;)V
+ public final fun removeChildTasks (Lorg/gradle/api/Project;)V
+ public final fun removeSubprojectChildTasks (Ljava/lang/String;)V
+}
+
+public abstract class org/jetbrains/dokka/gradle/AbstractDokkaTask : org/gradle/api/DefaultTask {
+ public fun <init> ()V
+ public final fun doFirst (Lgroovy/lang/Closure;)Lorg/gradle/api/Task;
+ public final fun doFirst (Lorg/gradle/api/Action;)Lorg/gradle/api/Task;
+ public abstract fun getCacheRoot ()Lorg/gradle/api/file/DirectoryProperty;
+ public final fun getFailOnWarning ()Lorg/gradle/api/provider/Property;
+ public final fun getModuleName ()Lorg/gradle/api/provider/Property;
+ public final fun getModuleVersion ()Lorg/gradle/api/provider/Property;
+ public final fun getOfflineMode ()Lorg/gradle/api/provider/Property;
+ public abstract fun getOutputDirectory ()Lorg/gradle/api/file/DirectoryProperty;
+ public final fun getPlugins ()Lorg/gradle/api/artifacts/Configuration;
+ public final fun getPluginsConfiguration ()Lorg/gradle/api/provider/ListProperty;
+ public final fun getPluginsMapConfiguration ()Lorg/gradle/api/provider/MapProperty;
+ public final fun getRuntime ()Lorg/gradle/api/artifacts/Configuration;
+ public final fun getSuppressInheritedMembers ()Lorg/gradle/api/provider/Property;
+ public final fun getSuppressObviousFunctions ()Lorg/gradle/api/provider/Property;
+}
+
+public final class org/jetbrains/dokka/gradle/DokkaBootstrapFactoryKt {
+ public static final fun DokkaBootstrap (Lorg/gradle/api/artifacts/Configuration;Lkotlin/reflect/KClass;)Lorg/jetbrains/dokka/DokkaBootstrap;
+}
+
+public abstract class org/jetbrains/dokka/gradle/DokkaCollectorTask : org/jetbrains/dokka/gradle/AbstractDokkaParentTask {
+ public fun <init> ()V
+}
+
+public abstract interface class org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout {
+ public abstract fun targetChildOutputDirectory (Lorg/jetbrains/dokka/gradle/DokkaMultiModuleTask;Lorg/jetbrains/dokka/gradle/AbstractDokkaTask;)Lorg/gradle/api/provider/Provider;
+}
+
+public final class org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout$CompactInParent : org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout {
+ public static final field INSTANCE Lorg/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout$CompactInParent;
+ public fun targetChildOutputDirectory (Lorg/jetbrains/dokka/gradle/DokkaMultiModuleTask;Lorg/jetbrains/dokka/gradle/AbstractDokkaTask;)Lorg/gradle/api/provider/Provider;
+}
+
+public final class org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout$NoCopy : org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout {
+ public static final field INSTANCE Lorg/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout$NoCopy;
+ public fun targetChildOutputDirectory (Lorg/jetbrains/dokka/gradle/DokkaMultiModuleTask;Lorg/jetbrains/dokka/gradle/AbstractDokkaTask;)Lorg/gradle/api/provider/Provider;
+}
+
+public abstract class org/jetbrains/dokka/gradle/DokkaMultiModuleTask : org/jetbrains/dokka/gradle/AbstractDokkaParentTask {
+ public fun <init> ()V
+ public final fun getFileLayout ()Lorg/gradle/api/provider/Property;
+ public abstract fun getIncludes ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public fun getTaskDependencies ()Lorg/gradle/api/internal/tasks/TaskDependencyInternal;
+ public synthetic fun getTaskDependencies ()Lorg/gradle/api/tasks/TaskDependency;
+}
+
+public class org/jetbrains/dokka/gradle/DokkaPlugin : org/gradle/api/Plugin {
+ public fun <init> ()V
+ public synthetic fun apply (Ljava/lang/Object;)V
+ public fun apply (Lorg/gradle/api/Project;)V
+}
+
+public final class org/jetbrains/dokka/gradle/DokkaPlugin$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action {
+ public fun <init> (Lkotlin/jvm/functions/Function1;)V
+ public final synthetic fun execute (Ljava/lang/Object;)V
+}
+
+public abstract class org/jetbrains/dokka/gradle/DokkaTask : org/jetbrains/dokka/gradle/AbstractDokkaLeafTask {
+ public fun <init> ()V
+}
+
+public abstract class org/jetbrains/dokka/gradle/DokkaTaskPartial : org/jetbrains/dokka/gradle/AbstractDokkaLeafTask {
+ public fun <init> ()V
+}
+
+public class org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder : org/jetbrains/dokka/DokkaConfigurationBuilder {
+ public fun <init> (Ljava/lang/String;Lorg/gradle/api/Project;Lorg/gradle/api/NamedDomainObjectFactory;)V
+ public final fun DokkaSourceSetID (Ljava/lang/String;)Lorg/jetbrains/dokka/DokkaSourceSetID;
+ public synthetic fun build ()Ljava/lang/Object;
+ public fun build ()Lorg/jetbrains/dokka/DokkaSourceSetImpl;
+ public final fun dependsOn (Ljava/lang/String;)V
+ public final fun dependsOn (Lorg/gradle/api/tasks/SourceSet;)V
+ public final fun dependsOn (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)V
+ public final fun dependsOn (Lorg/jetbrains/dokka/DokkaSourceSetID;)V
+ public final fun dependsOn (Lorg/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder;)V
+ public final fun externalDocumentationLink (Lgroovy/lang/Closure;)V
+ public final fun externalDocumentationLink (Ljava/lang/String;Ljava/lang/String;)V
+ public final fun externalDocumentationLink (Ljava/net/URL;Ljava/net/URL;)V
+ public final fun externalDocumentationLink (Lorg/gradle/api/Action;)V
+ public static synthetic fun externalDocumentationLink$default (Lorg/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V
+ public static synthetic fun externalDocumentationLink$default (Lorg/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder;Ljava/net/URL;Ljava/net/URL;ILjava/lang/Object;)V
+ public final fun getApiVersion ()Lorg/gradle/api/provider/Property;
+ public final fun getClasspath ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public final fun getDependentSourceSets ()Lorg/gradle/api/provider/SetProperty;
+ public final fun getDisplayName ()Lorg/gradle/api/provider/Property;
+ public final fun getDocumentedVisibilities ()Lorg/gradle/api/provider/SetProperty;
+ public final fun getExternalDocumentationLinks ()Lorg/gradle/api/provider/SetProperty;
+ public final fun getIncludeNonPublic ()Lorg/gradle/api/provider/Property;
+ public final fun getIncludes ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public final fun getJdkVersion ()Lorg/gradle/api/provider/Property;
+ public final fun getLanguageVersion ()Lorg/gradle/api/provider/Property;
+ public final fun getName ()Ljava/lang/String;
+ public final fun getNoAndroidSdkLink ()Lorg/gradle/api/provider/Property;
+ public final fun getNoJdkLink ()Lorg/gradle/api/provider/Property;
+ public final fun getNoStdlibLink ()Lorg/gradle/api/provider/Property;
+ public final fun getPerPackageOptions ()Lorg/gradle/api/provider/ListProperty;
+ public final fun getPlatform ()Lorg/gradle/api/provider/Property;
+ public final fun getReportUndocumented ()Lorg/gradle/api/provider/Property;
+ public final fun getSamples ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public final fun getSkipDeprecated ()Lorg/gradle/api/provider/Property;
+ public final fun getSkipEmptyPackages ()Lorg/gradle/api/provider/Property;
+ public final fun getSourceLinks ()Lorg/gradle/api/provider/SetProperty;
+ public final fun getSourceRoots ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public final fun getSourceSetID ()Lorg/jetbrains/dokka/DokkaSourceSetID;
+ public final fun getSuppress ()Lorg/gradle/api/provider/Property;
+ public final fun getSuppressGeneratedFiles ()Lorg/gradle/api/provider/Property;
+ public final fun getSuppressedFiles ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public final fun perPackageOption (Lgroovy/lang/Closure;)V
+ public final fun perPackageOption (Lorg/gradle/api/Action;)V
+ public final fun sourceLink (Lgroovy/lang/Closure;)V
+ public final fun sourceLink (Lorg/gradle/api/Action;)V
+ public final fun sourceRoot (Ljava/io/File;)V
+ public final fun sourceRoot (Ljava/lang/String;)V
+}
+
+public final class org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderExtensionsKt {
+ public static final fun dependsOn (Lorg/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder;Lcom/android/build/api/dsl/AndroidSourceSet;)V
+ public static final fun dependsOn (Lorg/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder;Lcom/android/build/gradle/api/AndroidSourceSet;)V
+ public static final fun dependsOn (Lorg/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder;Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)V
+ public static final fun kotlinSourceSet (Lorg/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder;Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)V
+}
+
+public final class org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderFactoryKt {
+ public static final fun gradleDokkaSourceSetBuilderFactory (Lorg/jetbrains/dokka/gradle/AbstractDokkaTask;)Lorg/gradle/api/NamedDomainObjectFactory;
+}
+
+public final class org/jetbrains/dokka/gradle/GradleExternalDocumentationLinkBuilder : org/jetbrains/dokka/DokkaConfigurationBuilder {
+ public fun <init> (Lorg/gradle/api/Project;)V
+ public synthetic fun build ()Ljava/lang/Object;
+ public fun build ()Lorg/jetbrains/dokka/ExternalDocumentationLinkImpl;
+ public final fun getPackageListUrl ()Lorg/gradle/api/provider/Property;
+ public final fun getUrl ()Lorg/gradle/api/provider/Property;
+}
+
+public final class org/jetbrains/dokka/gradle/GradlePackageOptionsBuilder : org/jetbrains/dokka/DokkaConfigurationBuilder {
+ public fun <init> (Lorg/gradle/api/Project;)V
+ public synthetic fun build ()Ljava/lang/Object;
+ public fun build ()Lorg/jetbrains/dokka/PackageOptionsImpl;
+ public final fun getDocumentedVisibilities ()Lorg/gradle/api/provider/SetProperty;
+ public final fun getIncludeNonPublic ()Lorg/gradle/api/provider/Property;
+ public final fun getMatchingRegex ()Lorg/gradle/api/provider/Property;
+ public final fun getReportUndocumented ()Lorg/gradle/api/provider/Property;
+ public final fun getSkipDeprecated ()Lorg/gradle/api/provider/Property;
+ public final fun getSuppress ()Lorg/gradle/api/provider/Property;
+}
+
+public final class org/jetbrains/dokka/gradle/GradleSourceLinkBuilder : org/jetbrains/dokka/DokkaConfigurationBuilder {
+ public fun <init> (Lorg/gradle/api/Project;)V
+ public synthetic fun build ()Ljava/lang/Object;
+ public fun build ()Lorg/jetbrains/dokka/SourceLinkDefinitionImpl;
+ public final fun getLocalDirectory ()Lorg/gradle/api/provider/Property;
+ public final fun getRemoteLineSuffix ()Lorg/gradle/api/provider/Property;
+ public final fun getRemoteUrl ()Lorg/gradle/api/provider/Property;
+}
+
+public final class org/jetbrains/dokka/gradle/SourceSetKotlinGistConfigurationKt {
+ public static final fun configureWithKotlinSourceSet (Lorg/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder;Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)V
+}
+
+public final class org/jetbrains/dokka/gradle/internal/AbstractDokkaTaskExtensionsKt {
+ public static final fun buildJsonConfiguration (Lorg/jetbrains/dokka/gradle/AbstractDokkaTask;Z)Ljava/lang/String;
+ public static synthetic fun buildJsonConfiguration$default (Lorg/jetbrains/dokka/gradle/AbstractDokkaTask;ZILjava/lang/Object;)Ljava/lang/String;
+}
+
diff --git a/dokka-runners/runner-gradle-plugin-classic/build.gradle.kts b/dokka-runners/runner-gradle-plugin-classic/build.gradle.kts
new file mode 100644
index 00000000..4b142cc8
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/build.gradle.kts
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import dokkabuild.PublicationName
+import dokkabuild.overridePublicationArtifactId
+
+plugins {
+ id("dokkabuild.gradle-plugin")
+ alias(libs.plugins.kotlinx.binaryCompatibilityValidator)
+}
+
+overridePublicationArtifactId("dokka-gradle-plugin", PublicationName.GRADLE_PLUGIN)
+
+dependencies {
+ // the version is required for Gradle plugin publishing
+ api("org.jetbrains.dokka:dokka-core:$version")
+
+ compileOnly(libs.gradlePlugin.kotlin)
+ compileOnly(libs.gradlePlugin.kotlin.klibCommonizerApi)
+ compileOnly(libs.gradlePlugin.android)
+
+ testImplementation(kotlin("test"))
+ testImplementation(libs.gradlePlugin.kotlin)
+ testImplementation(libs.gradlePlugin.kotlin.klibCommonizerApi)
+ testImplementation(libs.gradlePlugin.android)
+}
+
+@Suppress("UnstableApiUsage")
+gradlePlugin {
+ plugins {
+ create("dokka") {
+ id = "org.jetbrains.dokka"
+ implementationClass = "org.jetbrains.dokka.gradle.DokkaPlugin"
+
+ displayName = "Dokka plugin"
+ description = "Dokka is an API documentation engine for Kotlin"
+ tags.addAll("dokka", "kotlin", "kdoc", "android", "documentation", "api")
+ }
+ }
+}
+
+// Gradle will put its own version of the stdlib in the classpath, so not pull our own we will end up with
+// warnings like 'Runtime JAR files in the classpath should have the same version'
+listOf(
+ configurations.api,
+ configurations.implementation,
+ configurations.runtimeOnly
+).forEach {
+ it.configure { excludeGradleCommonDependencies() }
+}
+
+/**
+ * These dependencies will be provided by Gradle, and we should prevent version conflict
+ * Code taken from the Kotlin Gradle plugin:
+ * https://github.com/JetBrains/kotlin/blob/70e15b281cb43379068facb82b8e4bcb897a3c4f/buildSrc/src/main/kotlin/GradleCommon.kt#L72
+ */
+fun Configuration.excludeGradleCommonDependencies() {
+ dependencies
+ .withType<ModuleDependency>()
+ .configureEach {
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-common")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-reflect")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-script-runtime")
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/gradle.properties b/dokka-runners/runner-gradle-plugin-classic/gradle.properties
new file mode 100644
index 00000000..cf8d7228
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/gradle.properties
@@ -0,0 +1,13 @@
+#
+# Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
+# Project Settings
+group=org.jetbrains.dokka
+version=1.9.20-SNAPSHOT
+
+kotlin.stdlib.default.dependency=false
+
+org.jetbrains.dokka.javaToolchain.mainCompiler=8
+org.jetbrains.dokka.javaToolchain.testLauncher=8
+org.jetbrains.dokka.kotlinLanguageLevel=1.4
diff --git a/dokka-runners/runner-gradle-plugin-classic/settings.gradle.kts b/dokka-runners/runner-gradle-plugin-classic/settings.gradle.kts
new file mode 100644
index 00000000..24f21fe1
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/settings.gradle.kts
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+rootProject.name = "runner-gradle-plugin-classic"
+
+pluginManagement {
+ includeBuild("../../build-logic")
+
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ google()
+ }
+
+ versionCatalogs {
+ create("libs") {
+ from(files("../../gradle/libs.versions.toml"))
+ }
+ }
+}
+
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt
new file mode 100644
index 00000000..241c0449
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Dependency
+import org.jetbrains.dokka.DokkaVersion
+
+internal val Project.dokkaArtifacts get() = DokkaArtifacts(this)
+
+internal class DokkaArtifacts(private val project: Project) {
+ private fun fromModuleName(name: String): Dependency =
+ project.dependencies.create("org.jetbrains.dokka:$name:${DokkaVersion.version}")
+
+ // TODO [beresnev] analysis switcher
+ val analysisKotlinDescriptors get() = fromModuleName("analysis-kotlin-descriptors")
+ val analysisKotlinSymbols get() = fromModuleName("analysis-kotlin-symbols")
+
+ val allModulesPage get() = fromModuleName("all-modules-page-plugin")
+ val dokkaCore get() = fromModuleName("dokka-core")
+ val dokkaBase get() = fromModuleName("dokka-base")
+ val javadocPlugin get() = fromModuleName("javadoc-plugin")
+ val gfmPlugin get() = fromModuleName("gfm-plugin")
+ val gfmTemplateProcessing get() = fromModuleName("gfm-template-processing-plugin")
+ val jekyllTemplateProcessing get() = fromModuleName("jekyll-template-processing-plugin")
+ val jekyllPlugin get() = fromModuleName("jekyll-plugin")
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout.kt
new file mode 100644
index 00000000..b6120129
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.file.Directory
+import org.gradle.api.provider.Provider
+import org.jetbrains.dokka.DokkaException
+import org.jetbrains.dokka.gradle.DokkaMultiModuleFileLayout.CompactInParent
+import org.jetbrains.dokka.gradle.DokkaMultiModuleFileLayout.NoCopy
+import java.io.File
+
+/**
+ * @see DokkaMultiModuleFileLayout.targetChildOutputDirectory
+ * @see NoCopy
+ * @see CompactInParent
+ */
+fun interface DokkaMultiModuleFileLayout {
+
+ /**
+ * @param parent: The [DokkaMultiModuleTask] that is initiating a composite documentation run
+ * @param child: Some child task registered in [parent]
+ * @return The target output directory of the [child] dokka task referenced by [parent]. This should
+ * be unique for all registered child tasks.
+ */
+ fun targetChildOutputDirectory(parent: DokkaMultiModuleTask, child: AbstractDokkaTask): Provider<Directory>
+
+ /**
+ * Will link to the original [AbstractDokkaTask.outputDirectory]. This requires no copying of the output files.
+ */
+ object NoCopy : DokkaMultiModuleFileLayout {
+ override fun targetChildOutputDirectory(
+ parent: DokkaMultiModuleTask,
+ child: AbstractDokkaTask
+ ): Provider<Directory> = child.outputDirectory
+ }
+
+ /**
+ * Will point to a subfolder inside the output directory of the parent.
+ * The subfolder will follow the structure of the gradle project structure
+ * e.g.
+ * :parentProject:firstAncestor:secondAncestor will be be resolved to
+ * {parent output directory}/firstAncestor/secondAncestor
+ */
+ object CompactInParent : DokkaMultiModuleFileLayout {
+ override fun targetChildOutputDirectory(
+ parent: DokkaMultiModuleTask,
+ child: AbstractDokkaTask
+ ): Provider<Directory> {
+ val relativeProjectPath = parent.project.relativeProjectPath(child.project.path)
+ val relativeFilePath = relativeProjectPath.replace(":", File.separator)
+ check(!File(relativeFilePath).isAbsolute) { "Unexpected absolute path $relativeFilePath" }
+ return parent.outputDirectory.dir(relativeFilePath)
+ }
+ }
+}
+
+internal fun DokkaMultiModuleTask.targetChildOutputDirectory(
+ child: AbstractDokkaTask
+): Provider<Directory> = fileLayout.get().targetChildOutputDirectory(this, child)
+
+
+internal fun DokkaMultiModuleTask.copyChildOutputDirectories() {
+ childDokkaTasks.forEach { child ->
+ this.copyChildOutputDirectory(child)
+ }
+}
+
+internal fun DokkaMultiModuleTask.copyChildOutputDirectory(child: AbstractDokkaTask) {
+ val targetChildOutputDirectory = project.file(fileLayout.get().targetChildOutputDirectory(this, child))
+ val sourceChildOutputDirectory = child.outputDirectory.asFile.get()
+
+ /* Pointing to the same directory -> No copy necessary */
+ if (sourceChildOutputDirectory.absoluteFile == targetChildOutputDirectory.absoluteFile) {
+ return
+ }
+
+ /* Cannot target *inside* the original folder */
+ if (targetChildOutputDirectory.absoluteFile.startsWith(sourceChildOutputDirectory.absoluteFile)) {
+ throw DokkaException(
+ "Cannot re-locate output directory into itself.\n" +
+ "sourceChildOutputDirectory=${sourceChildOutputDirectory.path}\n" +
+ "targetChildOutputDirectory=${targetChildOutputDirectory.path}"
+ )
+ }
+
+ /* Source output directory is empty -> No copy necessary */
+ if (!sourceChildOutputDirectory.exists()) {
+ return
+ }
+
+ sourceChildOutputDirectory.copyRecursively(targetChildOutputDirectory, overwrite = true)
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaPlugin.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaPlugin.kt
new file mode 100644
index 00000000..77fba8f2
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaPlugin.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Dependency
+import org.gradle.kotlin.dsl.register
+import org.gradle.kotlin.dsl.withType
+import org.gradle.util.GradleVersion
+import org.jetbrains.dokka.DokkaDefaults
+
+open class DokkaPlugin : Plugin<Project> {
+ override fun apply(project: Project) {
+ if (GradleVersion.version(project.gradle.gradleVersion) < GradleVersion.version("5.6")) {
+ project.logger.warn("Dokka: Build is using unsupported gradle version, expected at least 5.6 but got ${project.gradle.gradleVersion}. This may result in strange errors")
+ }
+ if (project.shouldUseK2())
+ project.logger.warn(
+ "Dokka's K2 Analysis is being used. " +
+ "It is still under active development and is thus experimental. " +
+ "It can be the cause of failed builds or incorrectly generated documentation. " +
+ "If you encounter an issue, please consider reporting it: https://github.com/Kotlin/dokka/issues"
+ )
+
+ project.setupDokkaTasks("dokkaHtml") {
+ description = "Generates documentation in 'html' format"
+ }
+
+ project.setupDokkaTasks(
+ name = "dokkaJavadoc",
+ multiModuleTaskSupported = false
+ ) {
+ plugins.dependencies.add(project.dokkaArtifacts.javadocPlugin)
+ description = "Generates documentation in 'javadoc' format"
+ }
+
+ project.setupDokkaTasks(
+ "dokkaGfm",
+ allModulesPageAndTemplateProcessing = project.dokkaArtifacts.gfmTemplateProcessing
+ ) {
+ plugins.dependencies.add(project.dokkaArtifacts.gfmPlugin)
+ description = "Generates documentation in GitHub flavored markdown format"
+ }
+
+ project.setupDokkaTasks(
+ "dokkaJekyll",
+ allModulesPageAndTemplateProcessing = project.dokkaArtifacts.jekyllTemplateProcessing
+ ) {
+ plugins.dependencies.add(project.dokkaArtifacts.jekyllPlugin)
+ description = "Generates documentation in Jekyll flavored markdown format"
+ }
+
+ project.configureEachAbstractDokkaTask()
+ project.configureEachDokkaMultiModuleTask()
+ }
+
+ /**
+ * Creates [DokkaTask], [DokkaMultiModuleTask] for the given
+ * name and configuration.
+ */
+ private fun Project.setupDokkaTasks(
+ name: String,
+ multiModuleTaskSupported: Boolean = true,
+ allModulesPageAndTemplateProcessing: Dependency = project.dokkaArtifacts.allModulesPage,
+ configuration: AbstractDokkaTask.() -> Unit = {}
+ ) {
+ project.maybeCreateDokkaPluginConfiguration(name)
+ project.maybeCreateDokkaRuntimeConfiguration(name)
+ project.tasks.register<DokkaTask>(name) {
+ configuration()
+ }
+
+ if (project.parent != null) {
+ val partialName = "${name}Partial"
+ project.maybeCreateDokkaPluginConfiguration(partialName)
+ project.maybeCreateDokkaRuntimeConfiguration(partialName)
+ project.tasks.register<DokkaTaskPartial>(partialName) {
+ configuration()
+ }
+ }
+
+ if (project.subprojects.isNotEmpty()) {
+ if (multiModuleTaskSupported) {
+ val multiModuleName = "${name}MultiModule"
+ project.maybeCreateDokkaPluginConfiguration(multiModuleName, setOf(allModulesPageAndTemplateProcessing))
+ project.maybeCreateDokkaRuntimeConfiguration(multiModuleName)
+
+ project.tasks.register<DokkaMultiModuleTask>(multiModuleName) {
+ @Suppress("DEPRECATION")
+ addSubprojectChildTasks("${name}Partial")
+ configuration()
+ description = "Runs all subprojects '$name' tasks and generates module navigation page"
+ }
+
+ project.tasks.register<DefaultTask>("${name}Multimodule") {
+ group = "deprecated"
+ description = "DEPRECATED: 'Multimodule' is deprecated. Use 'MultiModule' instead."
+ dependsOn(multiModuleName)
+ doLast {
+ logger.warn("'Multimodule' is deprecated. Use 'MultiModule' instead")
+ }
+ }
+ }
+
+ project.tasks.register<DokkaCollectorTask>("${name}Collector") {
+ @Suppress("DEPRECATION")
+ addSubprojectChildTasks(name)
+ description =
+ "Generates documentation merging all subprojects '$name' tasks into one virtual module"
+ }
+ }
+ }
+
+ private fun Project.configureEachAbstractDokkaTask() {
+ tasks.withType<AbstractDokkaTask>().configureEach {
+ val formatClassifier = name.removePrefix("dokka").decapitalize()
+ outputDirectory.convention(project.layout.buildDirectory.dir("dokka/$formatClassifier"))
+ cacheRoot.convention(project.layout.dir(providers.provider { DokkaDefaults.cacheRoot }))
+ }
+ }
+
+ private fun Project.configureEachDokkaMultiModuleTask() {
+ tasks.withType<DokkaMultiModuleTask>().configureEach {
+ sourceChildOutputDirectories.from({ childDokkaTasks.map { it.outputDirectory } })
+ }
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaProperty.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaProperty.kt
new file mode 100644
index 00000000..6c6e967d
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaProperty.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.provider.Provider
+
+
+internal fun Provider<String>.getValidVersionOrNull() = orNull?.takeIf { it != "unspecified" }
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetMapper.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetMapper.kt
new file mode 100644
index 00000000..c0112719
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetMapper.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink
+import java.io.File
+
+internal fun GradleDokkaSourceSetBuilder.toDokkaSourceSetImpl(): DokkaSourceSetImpl = DokkaSourceSetImpl(
+ classpath = classpath.toList(),
+ displayName = displayNameOrDefault(),
+ sourceSetID = sourceSetID,
+ sourceRoots = sourceRoots.toSet(),
+ dependentSourceSets = dependentSourceSets.get().toSet(),
+ samples = samples.toSet(),
+ includes = includes.toSet(),
+ includeNonPublic = includeNonPublic.get(),
+ documentedVisibilities = documentedVisibilities.get(),
+ reportUndocumented = reportUndocumented.get(),
+ skipEmptyPackages = skipEmptyPackages.get(),
+ skipDeprecated = skipDeprecated.get(),
+ jdkVersion = jdkVersion.get(),
+ sourceLinks = sourceLinks.get().build().toSet(),
+ perPackageOptions = perPackageOptions.get().build(),
+ externalDocumentationLinks = externalDocumentationLinksWithDefaults(),
+ languageVersion = languageVersion.orNull,
+ apiVersion = apiVersion.orNull,
+ noStdlibLink = noStdlibLink.get(),
+ noJdkLink = noJdkLink.get(),
+ suppressedFiles = suppressedFilesWithDefaults(),
+ analysisPlatform = platform.get()
+)
+
+private fun GradleDokkaSourceSetBuilder.displayNameOrDefault(): String {
+ displayName.orNull?.let { return it }
+ if (name.endsWith("Main") && name != "Main") {
+ return name.removeSuffix("Main")
+ }
+
+ return name
+}
+
+private fun GradleDokkaSourceSetBuilder.externalDocumentationLinksWithDefaults(): Set<ExternalDocumentationLinkImpl> {
+ return externalDocumentationLinks.get().build()
+ .run {
+ if (noJdkLink.get()) this
+ else this + ExternalDocumentationLink.jdk(jdkVersion.get())
+ }
+ .run {
+ if (noStdlibLink.get()) this
+ else this + ExternalDocumentationLink.kotlinStdlib()
+ }
+ .run {
+ if (noAndroidSdkLink.get() || !project.isAndroidProject()) this
+ else this +
+ ExternalDocumentationLink.androidSdk() +
+ ExternalDocumentationLink.androidX()
+ }
+ .toSet()
+}
+
+private fun GradleDokkaSourceSetBuilder.suppressedFilesWithDefaults(): Set<File> {
+ val suppressedGeneratedFiles = if (suppressGeneratedFiles.get()) {
+ val generatedRoot = project.buildDir.resolve("generated").absoluteFile
+ sourceRoots
+ .filter { it.startsWith(generatedRoot) }
+ .flatMap { it.walk().toList() }
+ .toSet()
+ } else {
+ emptySet()
+ }
+
+ return suppressedFiles.toSet() + suppressedGeneratedFiles
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt
new file mode 100644
index 00000000..aca3721a
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt
@@ -0,0 +1,483 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import groovy.lang.Closure
+import org.gradle.api.*
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.SetProperty
+import org.gradle.api.tasks.*
+import org.gradle.kotlin.dsl.listProperty
+import org.gradle.kotlin.dsl.property
+import org.gradle.kotlin.dsl.setProperty
+import org.jetbrains.dokka.*
+import java.io.File
+import java.net.URL
+
+/**
+ * [Source set](https://kotlinlang.org/docs/multiplatform-discover-project.html#source-sets) level configuration.
+ *
+ * Can be configured in the following way with Gradle Kotlin DSL:
+ *
+ * ```kotlin
+ * import org.jetbrains.dokka.gradle.DokkaTask
+ *
+ * tasks.dokkaHtml {
+ * dokkaSourceSets {
+ * // configure individual source set by name
+ * named("customSourceSet") {
+ * suppress.set(true)
+ * }
+ *
+ * // configure all source sets at once
+ * configureEach {
+ * reportUndocumented.set(true)
+ * }
+ * }
+ * }
+ * ```
+ */
+open class GradleDokkaSourceSetBuilder(
+ @Transient @get:Input val name: String,
+ @Transient @get:Internal internal val project: Project,
+ @Transient @get:Internal internal val sourceSetIdFactory: NamedDomainObjectFactory<DokkaSourceSetID>,
+) : DokkaConfigurationBuilder<DokkaSourceSetImpl> {
+
+ @Input
+ val sourceSetID: DokkaSourceSetID = sourceSetIdFactory.create(name)
+
+ /**
+ * Whether this source set should be skipped when generating documentation.
+ *
+ * Default is `false`.
+ */
+ @Input
+ val suppress: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(false)
+
+ /**
+ * Display name used to refer to the source set.
+ *
+ * The name will be used both externally (for example, source set name visible to documentation readers) and
+ * internally (for example, for logging messages of [reportUndocumented]).
+ *
+ * By default, the value is deduced from information provided by the Kotlin Gradle plugin.
+ */
+ @Input
+ @Optional
+ val displayName: Property<String?> = project.objects.property()
+
+ /**
+ * List of Markdown files that contain
+ * [module and package documentation](https://kotlinlang.org/docs/dokka-module-and-package-docs.html).
+ *
+ * Contents of specified files will be parsed and embedded into documentation as module and package descriptions.
+ *
+ * Example of such a file:
+ *
+ * ```markdown
+ * # Module kotlin-demo
+ *
+ * The module shows the Dokka usage.
+ *
+ * # Package org.jetbrains.kotlin.demo
+ *
+ * Contains assorted useful stuff.
+ *
+ * ## Level 2 heading
+ *
+ * Text after this heading is also part of documentation for `org.jetbrains.kotlin.demo`
+ *
+ * # Package org.jetbrains.kotlin.demo2
+ *
+ * Useful stuff in another package.
+ * ```
+ */
+ @InputFiles
+ @Optional
+ @PathSensitive(PathSensitivity.RELATIVE)
+ val includes: ConfigurableFileCollection = project.files()
+
+ /**
+ * Set of visibility modifiers that should be documented.
+ *
+ * This can be used if you want to document protected/internal/private declarations,
+ * as well as if you want to exclude public declarations and only document internal API.
+ *
+ * Can be configured on per-package basis, see [GradlePackageOptionsBuilder.documentedVisibilities].
+ *
+ * Default is [DokkaConfiguration.Visibility.PUBLIC].
+ */
+ @Input
+ val documentedVisibilities: SetProperty<DokkaConfiguration.Visibility> =
+ project.objects.setProperty<DokkaConfiguration.Visibility>()
+ .convention(DokkaDefaults.documentedVisibilities)
+
+ /**
+ * Specifies source sets that current source set depends on.
+ *
+ * Among other things, this information is needed to resolve
+ * [expect/actual](https://kotlinlang.org/docs/multiplatform-connect-to-apis.html) declarations.
+ *
+ * Prefer using [dependsOn] function to append dependent source sets to this list.
+ *
+ * By default, the values are deduced from information provided by the Kotlin Gradle plugin.
+ */
+ @Input
+ val dependentSourceSets: SetProperty<DokkaSourceSetID> = project.objects.setProperty<DokkaSourceSetID>()
+ .convention(emptySet())
+
+ /**
+ * Classpath for analysis and interactive samples.
+ *
+ * Useful if some types that come from dependencies are not resolved/picked up automatically.
+ * Property accepts both `.jar` and `.klib` files.
+ *
+ * By default, classpath is deduced from information provided by the Kotlin Gradle plugin.
+ */
+ @Classpath
+ @Optional
+ val classpath: ConfigurableFileCollection = project.files()
+
+ /**
+ * Source code roots to be analyzed and documented.
+ * Accepts directories and individual `.kt` / `.java` files.
+ *
+ * Prefer using [sourceRoot] function to append source roots to this list.
+ *
+ * By default, source roots are deduced from information provided by the Kotlin Gradle plugin.
+ */
+ @InputFiles
+ @PathSensitive(PathSensitivity.RELATIVE)
+ val sourceRoots: ConfigurableFileCollection = project.objects.fileCollection()
+
+ /**
+ * List of directories or files that contain sample functions which are referenced via
+ * [@sample](https://kotlinlang.org/docs/kotlin-doc.html#sample-identifier) KDoc tag.
+ */
+ @InputFiles
+ @Optional
+ @PathSensitive(PathSensitivity.RELATIVE)
+ val samples: ConfigurableFileCollection = project.files()
+
+ /**
+ * Whether to emit warnings about visible undocumented declarations, that is declarations without KDocs
+ * after they have been filtered by [documentedVisibilities].
+ *
+ * This setting works well with [AbstractDokkaTask.failOnWarning].
+ *
+ * Can be overridden for a specific package by setting [GradlePackageOptionsBuilder.reportUndocumented].
+ *
+ * Default is `false`.
+ */
+ @Input
+ val reportUndocumented: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.reportUndocumented)
+
+ /**
+ * Specifies the location of the project source code on the Web. If provided, Dokka generates
+ * "source" links for each declaration. See [GradleSourceLinkBuilder] for more details.
+ *
+ * Prefer using [sourceLink] action/closure for adding source links.
+ */
+ @Nested
+ val sourceLinks: SetProperty<GradleSourceLinkBuilder> = project.objects.setProperty<GradleSourceLinkBuilder>()
+ .convention(emptySet())
+
+ /**
+ * Allows to customize documentation generation options on a per-package basis.
+ *
+ * @see GradlePackageOptionsBuilder for details
+ */
+ @Nested
+ val perPackageOptions: ListProperty<GradlePackageOptionsBuilder> =
+ project.objects.listProperty<GradlePackageOptionsBuilder>()
+ .convention(emptyList())
+
+ /**
+ * Allows linking to Dokka/Javadoc documentation of the project's dependencies.
+ *
+ * Prefer using [externalDocumentationLink] action/closure for adding links.
+ */
+ @Nested
+ val externalDocumentationLinks: SetProperty<GradleExternalDocumentationLinkBuilder> =
+ project.objects.setProperty<GradleExternalDocumentationLinkBuilder>()
+ .convention(emptySet())
+
+ /**
+ * Platform to be used for setting up code analysis and samples.
+ *
+ * The default value is deduced from information provided by the Kotlin Gradle plugin.
+ */
+ @Input
+ @Optional
+ val platform: Property<Platform> = project.objects.property<Platform>()
+ .convention(Platform.DEFAULT)
+
+ /**
+ * Whether to skip packages that contain no visible declarations after
+ * various filters have been applied.
+ *
+ * For instance, if [skipDeprecated] is set to `true` and your package contains only
+ * deprecated declarations, it will be considered to be empty.
+ *
+ * Default is `true`.
+ */
+ @Input
+ val skipEmptyPackages: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.skipEmptyPackages)
+
+ /**
+ * Whether to document declarations annotated with [Deprecated].
+ *
+ * Can be overridden on package level by setting [GradlePackageOptionsBuilder.skipDeprecated].
+ *
+ * Default is `false`.
+ */
+ @Input
+ val skipDeprecated: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.skipDeprecated)
+
+ /**
+ * Directories or individual files that should be suppressed, meaning declarations from them
+ * will be not documented.
+ *
+ * Will be concatenated with generated files if [suppressGeneratedFiles] is set to `false`.
+ */
+ @InputFiles
+ @PathSensitive(PathSensitivity.RELATIVE)
+ val suppressedFiles: ConfigurableFileCollection = project.files()
+
+ /**
+ * Whether to document/analyze generated files.
+ *
+ * Generated files are expected to be present under `{project}/{buildDir}/generated` directory.
+ * If set to `true`, it effectively adds all files from that directory to [suppressedFiles], so
+ * you can configure it manually.
+ *
+ * Default is `true`.
+ */
+ @Input
+ val suppressGeneratedFiles: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.suppressGeneratedFiles)
+
+ /**
+ * Whether to generate external documentation links that lead to API reference
+ * documentation for Kotlin's standard library when declarations from it are used.
+ *
+ * Default is `false`, meaning links will be generated.
+ */
+ @Input
+ val noStdlibLink: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.noStdlibLink)
+
+ /**
+ * Whether to generate external documentation links to JDK's Javadocs
+ * when declarations from it are used.
+ *
+ * The version of JDK Javadocs is determined by [jdkVersion] property.
+ *
+ * Default is `false`, meaning links will be generated.
+ */
+ @Input
+ val noJdkLink: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.noJdkLink)
+
+ /**
+ * Whether to generate external documentation links for Android SDK API reference
+ * when declarations from it are used.
+ *
+ * Only relevant in Android projects, ignored otherwise.
+ *
+ * Default is `false`, meaning links will be generated.
+ */
+ @Input
+ val noAndroidSdkLink: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.noAndroidSdkLink)
+
+ /**
+ * [Kotlin language version](https://kotlinlang.org/docs/compatibility-modes.html)
+ * used for setting up analysis and [@sample](https://kotlinlang.org/docs/kotlin-doc.html#sample-identifier)
+ * environment.
+ *
+ * By default, the latest language version available to Dokka's embedded compiler will be used.
+ */
+ @Input
+ @Optional
+ val languageVersion: Property<String?> = project.objects.property()
+
+ /**
+ * [Kotlin API version](https://kotlinlang.org/docs/compatibility-modes.html)
+ * used for setting up analysis and [@sample](https://kotlinlang.org/docs/kotlin-doc.html#sample-identifier)
+ * environment.
+ *
+ * By default, it will be deduced from [languageVersion].
+ */
+ @Input
+ @Optional
+ val apiVersion: Property<String?> = project.objects.property()
+
+ /**
+ * JDK version to use when generating external documentation links for Java types.
+ *
+ * For instance, if you use [java.util.UUID] from JDK in some public declaration signature,
+ * and this property is set to `8`, Dokka will generate an external documentation link
+ * to [JDK 8 Javadocs](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) for it.
+ *
+ * Default is JDK 8.
+ */
+ @Input
+ val jdkVersion: Property<Int> = project.objects.property<Int>()
+ .convention(DokkaDefaults.jdkVersion)
+
+ /**
+ * Deprecated. Use [documentedVisibilities] instead.
+ */
+ @Input
+ val includeNonPublic: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.includeNonPublic)
+
+ fun DokkaSourceSetID(sourceSetName: String): DokkaSourceSetID = sourceSetIdFactory.create(sourceSetName)
+
+ /**
+ * Convenient override to **append** source sets to [dependentSourceSets]
+ */
+ fun dependsOn(sourceSet: SourceSet) {
+ dependsOn(DokkaSourceSetID(sourceSet.name))
+ }
+
+ /**
+ * Convenient override to **append** source sets to [dependentSourceSets]
+ */
+ fun dependsOn(sourceSet: GradleDokkaSourceSetBuilder) {
+ dependsOn(sourceSet.sourceSetID)
+ }
+
+ /**
+ * Convenient override to **append** source sets to [dependentSourceSets]
+ */
+ fun dependsOn(sourceSet: DokkaConfiguration.DokkaSourceSet) {
+ dependsOn(sourceSet.sourceSetID)
+ }
+
+ /**
+ * Convenient override to **append** source sets to [dependentSourceSets]
+ */
+ fun dependsOn(sourceSetName: String) {
+ dependsOn(DokkaSourceSetID(sourceSetName))
+ }
+
+ /**
+ * Convenient override to **append** source sets to [dependentSourceSets]
+ */
+ fun dependsOn(sourceSetID: DokkaSourceSetID) {
+ dependentSourceSets.add(sourceSetID)
+ }
+
+ /**
+ * Convenient override to **append** source roots to [sourceRoots]
+ */
+ fun sourceRoot(file: File) {
+ sourceRoots.from(file)
+ }
+
+ /**
+ * Convenient override to **append** source roots to [sourceRoots]
+ */
+ fun sourceRoot(path: String) {
+ sourceRoot(project.file(path))
+ }
+
+ /**
+ * Closure for configuring source links, appending to [sourceLinks].
+ *
+ * @see [GradleSourceLinkBuilder] for details.
+ */
+ @Suppress("DEPRECATION")
+ fun sourceLink(c: Closure<in GradleSourceLinkBuilder>) {
+ val configured = org.gradle.util.ConfigureUtil.configure(c, GradleSourceLinkBuilder(project))
+ sourceLinks.add(configured)
+ }
+
+ /**
+ * Action for configuring source links, appending to [sourceLinks].
+ *
+ * @see [GradleSourceLinkBuilder] for details.
+ */
+ fun sourceLink(action: Action<in GradleSourceLinkBuilder>) {
+ val sourceLink = GradleSourceLinkBuilder(project)
+ action.execute(sourceLink)
+ sourceLinks.add(sourceLink)
+ }
+
+ /**
+ * Closure for configuring package options, appending to [perPackageOptions].
+ *
+ * @see [GradlePackageOptionsBuilder] for details.
+ */
+ @Suppress("DEPRECATION")
+ fun perPackageOption(c: Closure<in GradlePackageOptionsBuilder>) {
+ val configured = org.gradle.util.ConfigureUtil.configure(c, GradlePackageOptionsBuilder(project))
+ perPackageOptions.add(configured)
+ }
+
+ /**
+ * Action for configuring package options, appending to [perPackageOptions].
+ *
+ * @see [GradlePackageOptionsBuilder] for details.
+ */
+ fun perPackageOption(action: Action<in GradlePackageOptionsBuilder>) {
+ val option = GradlePackageOptionsBuilder(project)
+ action.execute(option)
+ perPackageOptions.add(option)
+ }
+
+ /**
+ * Closure for configuring external documentation links, appending to [externalDocumentationLinks].
+ *
+ * @see [GradleExternalDocumentationLinkBuilder] for details.
+ */
+ @Suppress("DEPRECATION")
+ fun externalDocumentationLink(c: Closure<in GradleExternalDocumentationLinkBuilder>) {
+ val link = org.gradle.util.ConfigureUtil.configure(c, GradleExternalDocumentationLinkBuilder(project))
+ externalDocumentationLinks.add(link)
+ }
+
+ /**
+ * Action for configuring external documentation links, appending to [externalDocumentationLinks].
+ *
+ * See [GradleExternalDocumentationLinkBuilder] for details.
+ */
+ fun externalDocumentationLink(action: Action<in GradleExternalDocumentationLinkBuilder>) {
+ val link = GradleExternalDocumentationLinkBuilder(project)
+ action.execute(link)
+ externalDocumentationLinks.add(link)
+ }
+
+ /**
+ * Convenient override to **append** external documentation links to [externalDocumentationLinks].
+ */
+ fun externalDocumentationLink(url: String, packageListUrl: String? = null) {
+ externalDocumentationLink(URL(url), packageListUrl = packageListUrl?.let(::URL))
+ }
+
+ /**
+ * Convenient override to **append** external documentation links to [externalDocumentationLinks].
+ */
+ fun externalDocumentationLink(url: URL, packageListUrl: URL? = null) {
+ externalDocumentationLinks.add(
+ GradleExternalDocumentationLinkBuilder(project).apply {
+ this.url.convention(url)
+ if (packageListUrl != null) {
+ this.packageListUrl.convention(packageListUrl)
+ }
+ }
+ )
+ }
+
+ override fun build(): DokkaSourceSetImpl = toDokkaSourceSetImpl()
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderExtensions.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderExtensions.kt
new file mode 100644
index 00000000..fd962acc
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderExtensions.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+
+/**
+ * Convenient override to **append** source sets to [GradleDokkaSourceSetBuilder.dependentSourceSets]
+ */
+fun GradleDokkaSourceSetBuilder.dependsOn(sourceSet: KotlinSourceSet) {
+ dependsOn(DokkaSourceSetID(sourceSet.name))
+}
+
+/**
+ * Convenient override to **append** source sets to [GradleDokkaSourceSetBuilder.dependentSourceSets]
+ */
+fun GradleDokkaSourceSetBuilder.dependsOn(@Suppress("DEPRECATION") sourceSet: com.android.build.gradle.api.AndroidSourceSet) {
+ dependsOn(DokkaSourceSetID(sourceSet.name))
+}
+
+/**
+ * Convenient override to **append** source sets to [GradleDokkaSourceSetBuilder.dependentSourceSets]
+ */
+fun GradleDokkaSourceSetBuilder.dependsOn(@Suppress("UnstableApiUsage") sourceSet: com.android.build.api.dsl.AndroidSourceSet) {
+ dependsOn(DokkaSourceSetID(sourceSet.name))
+}
+
+/**
+ * Extension allowing configuration of Dokka source sets via Kotlin Gradle plugin source sets.
+ */
+fun GradleDokkaSourceSetBuilder.kotlinSourceSet(kotlinSourceSet: KotlinSourceSet) {
+ configureWithKotlinSourceSet(kotlinSourceSet)
+}
+
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderFactory.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderFactory.kt
new file mode 100644
index 00000000..b2fc0394
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderFactory.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.NamedDomainObjectFactory
+
+@Suppress("ObjectLiteralToLambda") // Will fail at runtime in Gradle versions <= 6.6
+fun AbstractDokkaTask.gradleDokkaSourceSetBuilderFactory(): NamedDomainObjectFactory<GradleDokkaSourceSetBuilder> =
+ NamedDomainObjectFactory { name -> GradleDokkaSourceSetBuilder(name, project, DokkaSourceSetIdFactory()) }
+
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleExternalDocumentationLinkBuilder.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleExternalDocumentationLinkBuilder.kt
new file mode 100644
index 00000000..8137d7a9
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleExternalDocumentationLinkBuilder.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.Optional
+import org.gradle.kotlin.dsl.property
+import org.jetbrains.dokka.DokkaConfigurationBuilder
+import org.jetbrains.dokka.ExternalDocumentationLink
+import org.jetbrains.dokka.ExternalDocumentationLinkImpl
+import java.net.URL
+
+/**
+ * Configuration builder that allows creating links leading to externally hosted
+ * documentation of your dependencies.
+ *
+ * For instance, if you are using types from `kotlinx.serialization`, by default
+ * they will be unclickable in your documentation, as if unresolved. However,
+ * since API reference for `kotlinx.serialization` is also built by Dokka and is
+ * [published on kotlinlang.org](https://kotlinlang.org/api/kotlinx.serialization/),
+ * you can configure external documentation links for it, allowing Dokka to generate
+ * documentation links for used types, making them clickable and appear resolved.
+ *
+ * Example in Gradle Kotlin DSL:
+ *
+ * ```kotlin
+ * externalDocumentationLink {
+ * url.set(URL("https://kotlinlang.org/api/kotlinx.serialization/"))
+ * packageListUrl.set(
+ * rootProject.projectDir.resolve("serialization.package.list").toURL()
+ * )
+ * }
+ * ```
+ */
+class GradleExternalDocumentationLinkBuilder(
+ @Transient @get:Internal internal val project: Project
+) : DokkaConfigurationBuilder<ExternalDocumentationLinkImpl> {
+
+ /**
+ * Root URL of documentation to link with. **Must** contain a trailing slash.
+ *
+ * Dokka will do its best to automatically find `package-list` for the given URL, and link
+ * declarations together.
+ *
+ * It automatic resolution fails or if you want to use locally cached files instead,
+ * consider providing [packageListUrl].
+ *
+ * Example:
+ *
+ * ```kotlin
+ * java.net.URL("https://kotlinlang.org/api/kotlinx.serialization/")
+ * ```
+ */
+ @Internal
+ val url: Property<URL> = project.objects.property()
+
+ @Input // URL is deprecated in gradle inputs
+ internal fun getUrlString() = url.map(URL::toString)
+
+ /**
+ * Specifies the exact location of a `package-list` instead of relying on Dokka
+ * automatically resolving it. Can also be a locally cached file to avoid network calls.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * rootProject.projectDir.resolve("serialization.package.list").toURL()
+ * ```
+ */
+ @Internal
+ val packageListUrl: Property<URL> = project.objects.property()
+
+ @Input // URL is deprecated in gradle inputs
+ @Optional
+ internal fun getPackageListUrlString() = packageListUrl.map(URL::toString)
+
+ override fun build(): ExternalDocumentationLinkImpl = ExternalDocumentationLink(
+ url = checkNotNull(url.get()) { "url not specified " },
+ packageListUrl = packageListUrl.orNull,
+ )
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradlePackageOptionsBuilder.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradlePackageOptionsBuilder.kt
new file mode 100644
index 00000000..93b1f52c
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradlePackageOptionsBuilder.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.SetProperty
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.kotlin.dsl.*
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaConfigurationBuilder
+import org.jetbrains.dokka.DokkaDefaults
+import org.jetbrains.dokka.PackageOptionsImpl
+
+/**
+ * Configuration builder that allows setting some options for specific packages
+ * matched by [matchingRegex].
+ *
+ * Example in Gradle Kotlin DSL:
+ *
+ * ```kotlin
+ * tasks.dokkaHtml {
+ * dokkaSourceSets.configureEach {
+ * perPackageOption {
+ * matchingRegex.set(".*internal.*")
+ * suppress.set(true)
+ * }
+ * }
+ * }
+ * ```
+ */
+class GradlePackageOptionsBuilder(
+ @Transient @get:Internal internal val project: Project
+) : DokkaConfigurationBuilder<PackageOptionsImpl> {
+
+ /**
+ * Regular expression that is used to match the package.
+ *
+ * Default is any string: `.*`.
+ */
+ @Input
+ val matchingRegex: Property<String> = project.objects.property<String>()
+ .convention(".*")
+
+ /**
+ * Whether this package should be skipped when generating documentation.
+ *
+ * Default is `false`.
+ */
+ @Input
+ val suppress: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.suppress)
+
+ /**
+ * Set of visibility modifiers that should be documented.
+ *
+ * This can be used if you want to document protected/internal/private declarations within a
+ * specific package, as well as if you want to exclude public declarations and only document internal API.
+ *
+ * Can be configured for a whole source set, see [GradleDokkaSourceSetBuilder.documentedVisibilities].
+ *
+ * Default is [DokkaConfiguration.Visibility.PUBLIC].
+ */
+ @Input
+ val documentedVisibilities: SetProperty<DokkaConfiguration.Visibility> =
+ project.objects.setProperty<DokkaConfiguration.Visibility>()
+ .convention(DokkaDefaults.documentedVisibilities)
+
+ /**
+ * Whether to document declarations annotated with [Deprecated].
+ *
+ * Can be overridden on source set level by setting [GradleDokkaSourceSetBuilder.skipDeprecated].
+ *
+ * Default is `false`.
+ */
+ @Input
+ val skipDeprecated: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.skipDeprecated)
+
+ /**
+ * Whether to emit warnings about visible undocumented declarations, that is declarations from
+ * this package and without KDocs, after they have been filtered by [documentedVisibilities].
+ *
+ * This setting works well with [AbstractDokkaTask.failOnWarning].
+ *
+ * Can be overridden on source set level by setting [GradleDokkaSourceSetBuilder.reportUndocumented].
+ *
+ * Default is `false`.
+ */
+ @Input
+ val reportUndocumented: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.reportUndocumented)
+
+ /**
+ * Deprecated. Use [documentedVisibilities] instead.
+ */
+ @Input
+ val includeNonPublic: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.includeNonPublic)
+
+
+ override fun build(): PackageOptionsImpl = PackageOptionsImpl(
+ matchingRegex = matchingRegex.get(),
+ includeNonPublic = includeNonPublic.get(),
+ documentedVisibilities = documentedVisibilities.get(),
+ reportUndocumented = reportUndocumented.get(),
+ skipDeprecated = skipDeprecated.get(),
+ suppress = suppress.get()
+ )
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceLinkBuilder.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceLinkBuilder.kt
new file mode 100644
index 00000000..5a267962
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceLinkBuilder.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.Optional
+import org.gradle.kotlin.dsl.property
+import org.jetbrains.dokka.DokkaConfigurationBuilder
+import org.jetbrains.dokka.SourceLinkDefinitionImpl
+import java.io.File
+import java.net.URL
+
+/**
+ * Configuration builder that allows adding a `source` link to each signature
+ * which leads to [remoteUrl] with a specific line number (configurable by setting [remoteLineSuffix]),
+ * letting documentation readers find source code for each declaration.
+ *
+ * Example in Gradle Kotlin DSL:
+ *
+ * ```kotlin
+ * sourceLink {
+ * localDirectory.set(projectDir.resolve("src"))
+ * remoteUrl.set(URL("https://github.com/kotlin/dokka/tree/master/src"))
+ * remoteLineSuffix.set("#L")
+ * }
+ * ```
+ */
+class GradleSourceLinkBuilder(
+ @Transient @get:Internal internal val project: Project
+) : DokkaConfigurationBuilder<SourceLinkDefinitionImpl> {
+
+ /**
+ * Path to the local source directory. The path must be relative to the root of current project.
+ *
+ * This path is used to find relative paths of the source files from which the documentation is built.
+ * These relative paths are then combined with the base url of a source code hosting service specified with
+ * the [remoteUrl] property to create source links for each declaration.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * projectDir.resolve("src")
+ * ```
+ */
+ @Internal // changing contents of the directory should not invalidate the task
+ val localDirectory: Property<File?> = project.objects.property()
+
+ /**
+ * The relative path to [localDirectory] from the project directory. Declared as an input to invalidate the task if that path changes.
+ * Should not be used anywhere directly.
+ */
+ @Suppress("unused")
+ @get:Input
+ internal val localDirectoryPath: Provider<String?> =
+ localDirectory.map { it.relativeToOrSelf(project.projectDir).invariantSeparatorsPath }
+
+ /**
+ * URL of source code hosting service that can be accessed by documentation readers,
+ * like GitHub, GitLab, Bitbucket, etc. This URL will be used to generate
+ * source code links of declarations.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * java.net.URL("https://github.com/username/projectname/tree/master/src"))
+ * ```
+ */
+ @Internal
+ val remoteUrl: Property<URL> = project.objects.property()
+
+ @Input // TODO: URL is deprecated in grapdle inputs
+ internal fun getRemoteUrlString() = remoteUrl.map(URL::toString)
+
+
+ /**
+ * Suffix used to append source code line number to the URL. This will help readers navigate
+ * not only to the file, but to the specific line number of the declaration.
+ *
+ * The number itself will be appended to the specified suffix. For instance,
+ * if this property is set to `#L` and the line number is 10, resulting URL suffix
+ * will be `#L10`
+ *
+ * Suffixes used by popular services:
+ * - GitHub: `#L`
+ * - GitLab: `#L`
+ * - Bitbucket: `#lines-`
+ *
+ * Default is `#L`.
+ */
+ @Optional
+ @Input
+ val remoteLineSuffix: Property<String> = project.objects.property<String>()
+ .convention("#L")
+
+ override fun build(): SourceLinkDefinitionImpl {
+ return SourceLinkDefinitionImpl(
+ localDirectory = localDirectory.orNull?.canonicalPath ?: project.projectDir.canonicalPath,
+ remoteUrl = remoteUrl.get(),
+ remoteLineSuffix = remoteLineSuffix.get(),
+ )
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/TaskDependencyInternalWithAdditions.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/TaskDependencyInternalWithAdditions.kt
new file mode 100644
index 00000000..d3469f69
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/TaskDependencyInternalWithAdditions.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Task
+import org.gradle.api.internal.tasks.DefaultTaskDependency
+import org.gradle.api.internal.tasks.TaskDependencyInternal
+
+internal operator fun TaskDependencyInternal.plus(tasks: Iterable<Task>): TaskDependencyInternal =
+ TaskDependencyInternalWithAdditions(this, tasks.toSet())
+
+private class TaskDependencyInternalWithAdditions(
+ dependency: TaskDependencyInternal,
+ additionalTaskDependencies: Set<Task>,
+) : DefaultTaskDependency() {
+
+ init {
+ add(dependency, additionalTaskDependencies)
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/automagicTypedProxy.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/automagicTypedProxy.kt
new file mode 100644
index 00000000..8e397a4a
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/automagicTypedProxy.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import java.lang.reflect.InvocationHandler
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import java.lang.reflect.Proxy
+
+
+/**
+ * Warning! Hard reflection magic used here.
+ *
+ * Creates [java.lang.reflect.Proxy] with pass through invocation algorithm,
+ * to create access proxy for [delegate] into [targetClassLoader].
+ */
+internal inline fun <reified T : Any> automagicTypedProxy(targetClassLoader: ClassLoader, delegate: Any): T =
+ automagicProxy(targetClassLoader, T::class.java, delegate) as T
+
+
+/**
+ * Warning! Hard reflection magic used here.
+ *
+ * Creates [java.lang.reflect.Proxy] with pass through invocation algorithm,
+ * to create access proxy for [delegate] into [targetClassLoader].
+ *
+ */
+private fun automagicProxy(targetClassLoader: ClassLoader, targetType: Class<*>, delegate: Any): Any =
+ Proxy.newProxyInstance(
+ targetClassLoader,
+ arrayOf(targetType),
+ DelegatedInvocationHandler(delegate)
+ )
+
+private class DelegatedInvocationHandler(private val delegate: Any) : InvocationHandler {
+
+ @Throws(Throwable::class)
+ override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
+ val delegateMethod = delegate.javaClass.getMethod(method.name, *method.parameterTypes)
+ try {
+ delegateMethod.isAccessible = true
+ return delegateMethod.invoke(delegate, *(args ?: emptyArray()))
+ } catch (ex: InvocationTargetException) {
+ throw ex.targetException
+ }
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/checkChildDokkaTasksIsNotEmpty.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/checkChildDokkaTasksIsNotEmpty.kt
new file mode 100644
index 00000000..64fc1f9f
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/checkChildDokkaTasksIsNotEmpty.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.jetbrains.dokka.DokkaException
+
+internal fun AbstractDokkaParentTask.checkChildDokkaTasksIsNotEmpty() {
+ if (childDokkaTaskPaths.isEmpty()) {
+ throw DokkaException(
+ """
+ The ${this::class.java.simpleName} $path has no configured child tasks.
+ Add some dokka tasks like e.g.:
+
+ tasks.named<AbstractDokkaParentTask>("$name") {
+ addChildTask(..)
+ addChildTasks(subprojects, "...")
+ //...
+ }
+ """.trimIndent()
+ )
+ }
+
+ if (childDokkaTasks.isEmpty()) {
+ throw DokkaException(
+ """
+ The ${this::class.java.simpleName} $path could not find any registered child task.
+ child tasks: $childDokkaTaskPaths
+
+ Please make sure to apply the dokka plugin to all included (sub)-projects individually e.g.:
+
+ // subproject build.gradle.kts
+ plugins {
+ id("org.jetbrains.dokka")
+ }
+
+ or
+
+ // parent build.gradle.kts
+ subprojects {
+ plugins.apply("org.jetbrains.dokka")
+ }
+ """
+ )
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/checkDependentSourceSets.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/checkDependentSourceSets.kt
new file mode 100644
index 00000000..630f2dca
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/checkDependentSourceSets.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.jetbrains.dokka.DokkaSourceSetID
+
+internal fun checkSourceSetDependencies(sourceSets: List<GradleDokkaSourceSetBuilder>) {
+ checkSourceSetDependencies(sourceSets.associateBy { it.sourceSetID })
+}
+
+private fun checkSourceSetDependencies(sourceSets: Map<DokkaSourceSetID, GradleDokkaSourceSetBuilder>) {
+ sourceSets.values.forEach { sourceSet ->
+ sourceSet.dependentSourceSets.get().forEach { dependentSourceSetID ->
+ val dependentSourceSet = requireNotNull(sourceSets[dependentSourceSetID]) {
+ "Dokka source set \"${sourceSet.name}\": Cannot find dependent source set \"$dependentSourceSetID\""
+ }
+
+ if (sourceSet.suppress.get().not() && dependentSourceSet.suppress.get()) {
+ throw IllegalArgumentException(
+ "Dokka source set: \"${sourceSet.name}\": " +
+ "Unsuppressed source set cannot depend on suppressed source set \"$dependentSourceSetID\""
+ )
+ }
+ }
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaBootstrapFactory.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaBootstrapFactory.kt
new file mode 100644
index 00000000..21a2e8d1
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaBootstrapFactory.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.artifacts.Configuration
+import org.jetbrains.dokka.DokkaBootstrap
+import java.net.URLClassLoader
+import kotlin.reflect.KClass
+
+fun DokkaBootstrap(configuration: Configuration, bootstrapClass: KClass<out DokkaBootstrap>): DokkaBootstrap {
+ val runtimeJars = configuration.resolve()
+ val runtimeClassLoader = URLClassLoader(
+ runtimeJars.map { it.toURI().toURL() }.toTypedArray(),
+ ClassLoader.getSystemClassLoader().parent
+ )
+
+ val runtimeClassloaderBootstrapClass = runtimeClassLoader.loadClass(bootstrapClass.qualifiedName)
+ val runtimeClassloaderBootstrapInstance = runtimeClassloaderBootstrapClass.constructors.first().newInstance()
+ return automagicTypedProxy(DokkaPlugin::class.java.classLoader, runtimeClassloaderBootstrapInstance)
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaDefaultOutputDirectory.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaDefaultOutputDirectory.kt
new file mode 100644
index 00000000..d92b84a1
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaDefaultOutputDirectory.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Task
+import java.io.File
+
+internal fun Task.defaultDokkaOutputDirectory(): File {
+ return defaultDokkaOutputDirectory(project.buildDir, name)
+}
+
+internal fun defaultDokkaOutputDirectory(buildDir: File, taskName: String): File {
+ val formatClassifier = taskName.removePrefix("dokka").decapitalize()
+ return File(buildDir, "dokka${File.separator}$formatClassifier")
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaSourceSetIDFactory.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaSourceSetIDFactory.kt
new file mode 100644
index 00000000..b658e5f6
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaSourceSetIDFactory.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.api.Task
+import org.jetbrains.dokka.DokkaSourceSetID
+
+internal fun DokkaSourceSetID(task: Task, sourceSetName: String): DokkaSourceSetID {
+ return DokkaSourceSetID(task.path, sourceSetName)
+}
+
+@Suppress("FunctionName")
+internal fun Task.DokkaSourceSetIdFactory() = NamedDomainObjectFactory<DokkaSourceSetID> { name ->
+ DokkaSourceSetID(this@DokkaSourceSetIdFactory, name)
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/gradleConfigurations.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/gradleConfigurations.kt
new file mode 100644
index 00000000..63424e1e
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/gradleConfigurations.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.attributes.Usage
+import org.gradle.kotlin.dsl.named
+
+internal fun Project.shouldUseK2() =
+ (findProperty("org.jetbrains.dokka.experimental.tryK2") as? String)?.toBoolean() ?: false
+
+internal fun Project.maybeCreateDokkaDefaultPluginConfiguration(): Configuration {
+ return configurations.maybeCreate("dokkaPlugin") {
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_RUNTIME))
+ isCanBeConsumed = false
+ }
+}
+
+internal fun Project.maybeCreateDokkaDefaultRuntimeConfiguration(): Configuration {
+ return configurations.maybeCreate("dokkaRuntime") {
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_RUNTIME))
+ isCanBeConsumed = false
+ }
+}
+
+internal fun Project.maybeCreateDokkaPluginConfiguration(dokkaTaskName: String, additionalDependencies: Collection<Dependency> = emptySet()): Configuration {
+ return project.configurations.maybeCreate("${dokkaTaskName}Plugin") {
+ extendsFrom(maybeCreateDokkaDefaultPluginConfiguration())
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_RUNTIME))
+ isCanBeConsumed = false
+ dependencies.add(
+ if (shouldUseK2()) project.dokkaArtifacts.analysisKotlinSymbols
+ else project.dokkaArtifacts.analysisKotlinDescriptors
+ )
+ dependencies.add(project.dokkaArtifacts.dokkaBase)
+ dependencies.addAll(additionalDependencies)
+ }
+}
+
+internal fun Project.maybeCreateDokkaRuntimeConfiguration(dokkaTaskName: String): Configuration {
+ return project.configurations.maybeCreate("${dokkaTaskName}Runtime") {
+ extendsFrom(maybeCreateDokkaDefaultRuntimeConfiguration())
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_RUNTIME))
+ isCanBeConsumed = false
+ defaultDependencies {
+ add(project.dokkaArtifacts.dokkaCore)
+ }
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/internal/AbstractDokkaTaskExtensions.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/internal/AbstractDokkaTaskExtensions.kt
new file mode 100644
index 00000000..7675c69b
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/internal/AbstractDokkaTaskExtensions.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014-2023 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.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.gradle.AbstractDokkaTask
+import org.jetbrains.dokka.toPrettyJsonString
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.toCompactJsonString
+
+/**
+ * Serializes [DokkaConfiguration] of this [AbstractDokkaTask] as json
+ *
+ * Should be used for short-term debugging only, no guarantees are given for the support of this API.
+ *
+ * Better alternative should be introduced as part of [#2873](https://github.com/Kotlin/dokka/issues/2873).
+ */
+@InternalDokkaApi
+fun AbstractDokkaTask.buildJsonConfiguration(prettyPrint: Boolean = true): String {
+ val configuration = this.buildDokkaConfiguration()
+ return if (prettyPrint) {
+ configuration.toPrettyJsonString()
+ } else {
+ configuration.toCompactJsonString()
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinGradlePluginVersion.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinGradlePluginVersion.kt
new file mode 100644
index 00000000..72f1a626
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinGradlePluginVersion.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle.kotlin
+
+import org.gradle.api.Project
+import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
+
+internal typealias KotlinGradlePluginVersion = KotlinVersion
+
+internal fun Project.getKgpVersion(): KotlinGradlePluginVersion? = parseKotlinVersion(this.getKotlinPluginVersion())
+
+/**
+ * Accepts a full version string that contains the major, minor
+ * and patch versions divided by dots, such as "1.7.10".
+ *
+ * Does NOT parse and store custom suffixes, so `1.8.20-RC2`
+ * or `1.8.20-dev-42` will be viewed as `1.8.20`.
+ */
+internal fun parseKotlinVersion(fullVersionString: String): KotlinVersion? {
+ val versionParts = fullVersionString
+ .split(".", "-", limit = 4)
+ .takeIf { parts -> parts.size >= 3 && parts.subList(0, 3).all { it.isNumeric() } }
+ ?: return null
+
+ return KotlinVersion(
+ major = versionParts[0].toInt(),
+ minor = versionParts[1].toInt(),
+ patch = versionParts[2].toInt()
+ )
+}
+
+private fun String.isNumeric() = this.isNotEmpty() && this.all { it.isDigit() }
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinNativeDistributionAccessor.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinNativeDistributionAccessor.kt
new file mode 100644
index 00000000..3180efef
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinNativeDistributionAccessor.kt
@@ -0,0 +1,39 @@
+@file:Suppress("INVISIBLE_REFERENCE")
+package org.jetbrains.dokka.gradle.kotlin
+
+import java.io.File
+import org.gradle.api.Project
+import org.jetbrains.kotlin.commonizer.KonanDistribution
+import org.jetbrains.kotlin.commonizer.platformLibsDir
+import org.jetbrains.kotlin.commonizer.stdlib
+import org.jetbrains.kotlin.compilerRunner.konanHome
+import org.jetbrains.kotlin.konan.target.KonanTarget
+
+/**
+ * Provides access to the Kotlin/Native distribution components:
+ * * [stdlibDir] -- stdlib directory
+ * * [platformDependencies] -- list of directories to platform dependencies
+ *
+ * It uses Kotlin Gradle Plugin API that is guaranteed to be present in:
+ * 1.5 <= kotlinVersion <= 1.9
+ *
+ * It should not be used with Kotlin versions later than 1.9
+ */
+internal class KotlinNativeDistributionAccessor(
+ project: Project
+) {
+ private val konanDistribution = KonanDistribution(
+ @Suppress("INVISIBLE_MEMBER")
+ project.konanHome
+ )
+
+ val stdlibDir: File = konanDistribution.stdlib
+
+ fun platformDependencies(target: KonanTarget): List<File> = konanDistribution
+ .platformLibsDir
+ .resolve(target.name)
+ .listLibraryFiles()
+
+ private fun File.listLibraryFiles(): List<File> = listFiles().orEmpty()
+ .filter { it.isDirectory || it.extension == "klib" }
+} \ No newline at end of file
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinSourceSetGist.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinSourceSetGist.kt
new file mode 100644
index 00000000..18d7ebb2
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinSourceSetGist.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle.kotlin
+
+import org.gradle.api.Project
+import org.gradle.api.file.FileCollection
+import org.gradle.api.provider.Provider
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+
+internal data class KotlinSourceSetGist(
+ val name: String,
+ val platform: Provider<KotlinPlatformType>,
+ val isMain: Provider<Boolean>,
+ val classpath: Provider<FileCollection>,
+ val sourceRoots: FileCollection,
+ val dependentSourceSetNames: Provider<Set<String>>,
+)
+
+internal fun Project.gistOf(sourceSet: KotlinSourceSet): KotlinSourceSetGist = KotlinSourceSetGist(
+ name = sourceSet.name,
+ platform = project.provider { platformOf(sourceSet) },
+ isMain = project.provider { isMainSourceSet(sourceSet) },
+ classpath = project.provider { classpathOf(sourceSet).filter { it.exists() } },
+ // TODO: Needs to respect filters.
+ // We probably need to change from "sourceRoots" to support "sourceFiles"
+ // https://github.com/Kotlin/dokka/issues/1215
+ sourceRoots = sourceSet.kotlin.sourceDirectories.filter { it.exists() },
+ dependentSourceSetNames = project.provider { sourceSet.dependsOn.map { it.name }.toSet() },
+)
+
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/isMainSourceSet.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/isMainSourceSet.kt
new file mode 100644
index 00000000..b8abaca3
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/isMainSourceSet.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle.kotlin
+
+import com.android.build.gradle.api.ApplicationVariant
+import com.android.build.gradle.api.LibraryVariant
+import org.gradle.api.Project
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
+
+internal fun Project.isMainSourceSet(sourceSet: KotlinSourceSet): Boolean {
+ return isMainSourceSet(allCompilationsOf(sourceSet))
+}
+
+internal fun isMainSourceSet(compilations: List<KotlinCompilation>): Boolean {
+ return compilations.any { compilation -> isMainCompilation(compilation) }
+}
+
+private fun isMainCompilation(compilation: KotlinCompilation): Boolean {
+ try {
+ val androidVariant = compilation.run { this as? KotlinJvmAndroidCompilation }?.androidVariant
+ if (androidVariant != null) {
+ return androidVariant is LibraryVariant || androidVariant is ApplicationVariant
+ }
+ } catch (e: NoSuchMethodError) {
+ // Kotlin Plugin version below 1.4
+ return !compilation.name.toLowerCase().endsWith("test")
+ }
+ return compilation.name == "main"
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinClasspathUtils.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinClasspathUtils.kt
new file mode 100644
index 00000000..778261a7
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinClasspathUtils.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle.kotlin
+
+import org.gradle.api.Project
+import org.gradle.api.file.FileCollection
+import org.jetbrains.dokka.gradle.isAndroidTarget
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinNativeCompilation
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+internal fun Project.classpathOf(sourceSet: KotlinSourceSet): FileCollection {
+ val compilations = compilationsOf(sourceSet)
+ return if (compilations.isNotEmpty()) {
+ compilations
+ .map { compilation -> compilation.compileClasspathOf(project = this) }
+ .reduce(FileCollection::plus)
+ } else {
+ // Dokka suppresses source sets that do no have compilations
+ // since such configuration is invalid, it reports a warning or an error
+ sourceSet.withAllDependentSourceSets()
+ .map { it.kotlin.sourceDirectories }
+ .reduce(FileCollection::plus)
+ }
+}
+
+private fun KotlinCompilation.compileClasspathOf(project: Project): FileCollection {
+ val kgpVersion = project.getKgpVersion()
+
+ // if KGP version < 1.9 or org.jetbrains.dokka.classpath.useOldResolution=true
+ // we will use old (pre 1.9) resolution of classpath
+ if (kgpVersion == null ||
+ kgpVersion < KotlinGradlePluginVersion(1, 9, 0) ||
+ project.classpathProperty("useOldResolution", default = false)
+ ) {
+ return oldCompileClasspathOf(project)
+ }
+
+ return newCompileClasspathOf(project)
+}
+
+private fun KotlinCompilation.newCompileClasspathOf(project: Project): FileCollection {
+ if (this.target.isAndroidTarget()) { // Workaround for https://youtrack.jetbrains.com/issue/KT-33893
+ return this.classpathOf(project)
+ }
+
+ val result = project.objects.fileCollection()
+ result.from({ compileDependencyFiles })
+
+ val kgpVersion = project.getKgpVersion()
+ // Since Kotlin 2.0 native distributiuon dependencies will be included to compileDependencyFiles
+ if (kgpVersion != null && kgpVersion <= KotlinGradlePluginVersion(1, 9, 255)) {
+ if (this is AbstractKotlinNativeCompilation) {
+ val kotlinNativeDistributionAccessor = KotlinNativeDistributionAccessor(project)
+ result.from(kotlinNativeDistributionAccessor.stdlibDir)
+ result.from(kotlinNativeDistributionAccessor.platformDependencies(konanTarget))
+ }
+ }
+
+ return result
+}
+
+private fun KotlinCompilation.oldCompileClasspathOf(project: Project): FileCollection {
+ if (this.target.isAndroidTarget()) { // Workaround for https://youtrack.jetbrains.com/issue/KT-33893
+ return this.classpathOf(project)
+ }
+
+ return this.compileDependencyFiles + platformDependencyFiles(project) + this.classpathOf(project)
+}
+
+private fun KotlinCompilation.classpathOf(project: Project): FileCollection {
+ val kgpVersion = project.getKgpVersion()
+ val kotlinCompile = this.getKotlinCompileTask(kgpVersion) ?: return project.files()
+
+ val shouldKeepBackwardsCompatibility = (kgpVersion != null && kgpVersion < KotlinGradlePluginVersion(1, 7, 0))
+ return if (shouldKeepBackwardsCompatibility) {
+ // removed since 1.9.0, left for compatibility with < Kotlin 1.7
+ val classpathGetter = kotlinCompile::class.members
+ .first { it.name == "getClasspath" }
+ classpathGetter.call(kotlinCompile) as FileCollection
+ } else {
+ kotlinCompile.libraries // introduced in 1.7.0
+ }
+}
+
+private fun KotlinCompilation.getKotlinCompileTask(kgpVersion: KotlinGradlePluginVersion? = null): KotlinCompile? {
+ val shouldKeepBackwardsCompatibility = (kgpVersion != null && kgpVersion < KotlinGradlePluginVersion(1, 8, 0))
+ return if (shouldKeepBackwardsCompatibility) {
+ @Suppress("DEPRECATION") // for `compileKotlinTask` property, deprecated with warning since 1.8.0
+ this.compileKotlinTask as? KotlinCompile
+ } else {
+ this.compileTaskProvider.get() as? KotlinCompile // introduced in 1.8.0
+ }
+}
+
+private fun KotlinCompilation.platformDependencyFiles(project: Project): FileCollection {
+ val excludePlatformDependencyFiles = project.classpathProperty("excludePlatformDependencyFiles", default = false)
+
+ if (excludePlatformDependencyFiles) return project.files()
+ return (this as? AbstractKotlinNativeCompilation)
+ ?.target?.project?.configurations
+ ?.findByName(@Suppress("DEPRECATION") this.defaultSourceSet.implementationMetadataConfigurationName) // KT-58640
+ ?: project.files()
+}
+
+private fun Project.classpathProperty(name: String, default: Boolean): Boolean =
+ (findProperty("org.jetbrains.dokka.classpath.$name") as? String)?.toBoolean() ?: default
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinCompilationUtils.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinCompilationUtils.kt
new file mode 100644
index 00000000..0b1b7419
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinCompilationUtils.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle.kotlin
+
+import org.gradle.api.Project
+import org.jetbrains.dokka.gradle.kotlin
+import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinCommonCompilation
+
+internal typealias KotlinCompilation =
+ org.jetbrains.kotlin.gradle.plugin.KotlinCompilation<KotlinCommonOptions>
+
+internal fun Project.compilationsOf(sourceSet: KotlinSourceSet): List<KotlinCompilation> {
+ //KT-45412 Make sure .kotlinSourceSets and .allKotlinSourceSets include the default source set
+ val compilations = allCompilationsOf(sourceSet).filter { compilation ->
+ sourceSet in compilation.kotlinSourceSets || sourceSet == compilation.defaultSourceSet
+ }
+
+ val hasAdditionalCommonCompatibilityMetadataVariant = compilations.size >= 2
+ && this.isHmppEnabled()
+ && compilations.any { it is KotlinCommonCompilation && it.compilationName == "main" }
+ && compilations.any { it is KotlinCommonCompilation && it.compilationName == "commonMain" }
+
+ return if (hasAdditionalCommonCompatibilityMetadataVariant) {
+ // If the project has `kotlin.mpp.enableCompatibilityMetadataVariant` set to `true`
+ // and it produces a legacy variant for common, we filter it out because one of the dependencies
+ // might be published without it, and it would lead to the following error:
+ //
+ // > Execution failed for task ':project:dokkaHtmlPartial'.
+ // > Could not resolve all files for configuration ':project:metadataCompileClasspath'.
+ // > Could not resolve com.example.dependency:0.1.0.
+ // > The consumer was configured to find a usage of 'kotlin-api' of a library, preferably optimized for
+ // non-jvm, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'common'. However we
+ // cannot choose between the following variants of com.example.dependency:0.1.0:
+ //
+ // This can be reproduced consistently on Ktor of version 2.3.2
+ compilations.filterNot { it is KotlinCommonCompilation && it.compilationName == "main" }
+ } else {
+ compilations
+ }
+}
+
+private fun Project.isHmppEnabled(): Boolean {
+ // [KotlinCommonCompilation.isKlibCompilation] is internal, so we use this property instead.
+ // The property name might seem misleading, but it's set by KGP if HMPP is enabled:
+ // https://github.com/JetBrains/kotlin/blob/1.9.0/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/hierarchicalStructureMigrationHandling.kt#L33
+ return (this.findProperty("kotlin.mpp.enableGranularSourceSetsMetadata") as? String)?.toBoolean()
+ ?: false
+}
+
+internal fun Project.allCompilationsOf(
+ sourceSet: KotlinSourceSet
+): List<KotlinCompilation> {
+ return when (val kotlin = kotlin) {
+ is KotlinMultiplatformExtension -> allCompilationsOf(kotlin, sourceSet)
+ is KotlinSingleTargetExtension<*> -> allCompilationsOf(kotlin, sourceSet)
+ else -> emptyList()
+ }
+}
+
+private fun allCompilationsOf(
+ kotlin: KotlinMultiplatformExtension,
+ sourceSet: KotlinSourceSet
+): List<KotlinCompilation> {
+ val allCompilations = kotlin.targets.flatMap { target -> target.compilations }
+ return allCompilations.filter { compilation ->
+ sourceSet in compilation.allKotlinSourceSets || sourceSet == compilation.defaultSourceSet
+ }
+}
+
+private fun allCompilationsOf(
+ kotlin: KotlinSingleTargetExtension<*>,
+ sourceSet: KotlinSourceSet
+): List<KotlinCompilation> {
+ return kotlin.target.compilations.filter { compilation -> sourceSet in compilation.allKotlinSourceSets }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinSourceSetUtils.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinSourceSetUtils.kt
new file mode 100644
index 00000000..f5afd6cb
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/kotlinSourceSetUtils.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle.kotlin
+
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+
+
+internal fun KotlinSourceSet.withAllDependentSourceSets(): Sequence<KotlinSourceSet> {
+ return sequence {
+ yield(this@withAllDependentSourceSets)
+ for (dependentSourceSet in dependsOn) {
+ yieldAll(dependentSourceSet.withAllDependentSourceSets())
+ }
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/platformOfSourceSet.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/platformOfSourceSet.kt
new file mode 100644
index 00000000..8677d890
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/kotlin/platformOfSourceSet.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle.kotlin
+
+import org.gradle.api.Project
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+
+internal fun Project.platformOf(sourceSet: KotlinSourceSet): KotlinPlatformType {
+ val targetNames = allCompilationsOf(sourceSet).map { compilation -> compilation.target.platformType }.distinct()
+ return when (targetNames.size) {
+ 1 -> targetNames.single()
+ else -> KotlinPlatformType.common
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/sourceSetKotlinGistConfiguration.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/sourceSetKotlinGistConfiguration.kt
new file mode 100644
index 00000000..8f21b9d3
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/sourceSetKotlinGistConfiguration.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.gradle.kotlin.KotlinSourceSetGist
+import org.jetbrains.dokka.gradle.kotlin.gistOf
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+
+fun GradleDokkaSourceSetBuilder.configureWithKotlinSourceSet(sourceSet: KotlinSourceSet) {
+ configureWithKotlinSourceSetGist(project.gistOf(sourceSet))
+}
+
+internal fun GradleDokkaSourceSetBuilder.configureWithKotlinSourceSetGist(sourceSet: KotlinSourceSetGist) {
+ val dependentSourceSetIds = sourceSet.dependentSourceSetNames.map { sourceSetNames ->
+ sourceSetNames.map { sourceSetName -> DokkaSourceSetID(sourceSetName) }
+ }
+
+ this.suppress.convention(sourceSet.isMain.map { !it })
+ this.sourceRoots.from(sourceSet.sourceRoots)
+ this.classpath.from(sourceSet.classpath)
+ this.platform.convention(sourceSet.platform.map { Platform.fromString(it.name) })
+ this.dependentSourceSets.convention(dependentSourceSetIds)
+ this.displayName.convention(sourceSet.platform.map { platform ->
+ sourceSet.name.substringBeforeLast(
+ delimiter = "Main",
+ missingDelimiterValue = platform.name
+ )
+ })
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaLeafTask.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaLeafTask.kt
new file mode 100644
index 00000000..03d40d8b
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaLeafTask.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PackageDirectoryMismatch")
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.internal.plugins.DslObject
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.Nested
+import org.gradle.kotlin.dsl.container
+import org.gradle.work.DisableCachingByDefault
+
+@DisableCachingByDefault(because = "Abstract super-class, not to be instantiated directly")
+abstract class AbstractDokkaLeafTask : AbstractDokkaTask() {
+
+ @get:Internal
+ val dokkaSourceSets: NamedDomainObjectContainer<GradleDokkaSourceSetBuilder> =
+ project.container(GradleDokkaSourceSetBuilder::class, gradleDokkaSourceSetBuilderFactory()).also { container ->
+ DslObject(this).extensions.add("dokkaSourceSets", container)
+ project.kotlinOrNull?.sourceSets?.all sourceSet@{
+ container.register(name) {
+ configureWithKotlinSourceSet(this@sourceSet)
+ }
+ }
+ }
+
+ /**
+ * Only contains source sets that are marked with `isDocumented`.
+ * Non documented source sets are not relevant for Gradle's UP-TO-DATE mechanism, as well
+ * as task dependency graph.
+ */
+ @get:Nested
+ protected val unsuppressedSourceSets: List<GradleDokkaSourceSetBuilder>
+ get() = dokkaSourceSets
+ .toList()
+ .also(::checkSourceSetDependencies)
+ .filterNot { it.suppress.get() }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaParentTask.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaParentTask.kt
new file mode 100644
index 00000000..62e98c30
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaParentTask.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PackageDirectoryMismatch")
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.Nested
+import org.gradle.work.DisableCachingByDefault
+
+private const val DEPRECATION_MESSAGE = """
+ It is an anti-pattern to declare cross-project dependencies as it leads to various build problems.
+ For this reason, this API wil be removed with the introduction of project isolation.
+ When it happens, we will provide a migration guide. In the meantime, you can keep using this API
+ if you have to, but please don't rely on it if possible. If you don't want to document a certain project,
+ don't apply the Dokka plugin for it, or disable individual project tasks using the Gradle API .
+"""
+
+@Suppress("DEPRECATION")
+@DisableCachingByDefault(because = "Abstract super-class, not to be instantiated directly")
+abstract class AbstractDokkaParentTask : AbstractDokkaTask() {
+
+ @get:Internal
+ internal var childDokkaTaskPaths: Set<String> = emptySet()
+ private set
+
+ @get:Nested
+ internal val childDokkaTasks: Set<AbstractDokkaTask>
+ get() = childDokkaTaskPaths
+ .mapNotNull { path -> project.tasks.findByPath(path) }
+ .map(::checkIsAbstractDokkaTask)
+ .toSet()
+
+ /* By task reference */
+ @Deprecated(message = DEPRECATION_MESSAGE, level = DeprecationLevel.WARNING)
+ fun addChildTask(task: AbstractDokkaTask) {
+ childDokkaTaskPaths = childDokkaTaskPaths + task.path
+ }
+
+ @Deprecated(message = DEPRECATION_MESSAGE, level = DeprecationLevel.WARNING)
+ fun removeChildTask(task: AbstractDokkaTask) {
+ childDokkaTaskPaths = childDokkaTaskPaths - task.path
+ }
+
+ /* By path */
+ @Deprecated(message = DEPRECATION_MESSAGE, level = DeprecationLevel.WARNING)
+ fun addChildTask(path: String) {
+ childDokkaTaskPaths = childDokkaTaskPaths + project.absoluteProjectPath(path)
+ }
+
+ @Deprecated(message = DEPRECATION_MESSAGE, level = DeprecationLevel.WARNING)
+ fun removeChildTask(path: String) {
+ childDokkaTaskPaths = childDokkaTaskPaths - project.absoluteProjectPath(path)
+ }
+
+ /* By project reference and name */
+ @Deprecated(message = DEPRECATION_MESSAGE, level = DeprecationLevel.WARNING)
+ fun addChildTasks(projects: Iterable<Project>, childTasksName: String) {
+ projects.forEach { project ->
+ addChildTask(project.absoluteProjectPath(childTasksName))
+ }
+ }
+
+ @Deprecated(message = DEPRECATION_MESSAGE, level = DeprecationLevel.WARNING)
+ fun removeChildTasks(projects: Iterable<Project>, childTasksName: String) {
+ projects.forEach { project ->
+ removeChildTask(project.absoluteProjectPath(childTasksName))
+ }
+ }
+
+ @Deprecated(message = DEPRECATION_MESSAGE, level = DeprecationLevel.WARNING)
+ fun addSubprojectChildTasks(childTasksName: String) {
+ addChildTasks(project.subprojects, childTasksName)
+ }
+
+ @Deprecated(message = DEPRECATION_MESSAGE, level = DeprecationLevel.WARNING)
+ fun removeSubprojectChildTasks(childTasksName: String) {
+ removeChildTasks(project.subprojects, childTasksName)
+ }
+
+ @Deprecated(message = DEPRECATION_MESSAGE, level = DeprecationLevel.WARNING)
+ fun removeChildTasks(project: Project) {
+ childDokkaTaskPaths = childDokkaTaskPaths.filter { path ->
+ parsePath(path).parent != parsePath(project.path)
+ }.toSet()
+ }
+
+ @Deprecated(message = DEPRECATION_MESSAGE, level = DeprecationLevel.WARNING)
+ fun removeChildTasks(projects: Iterable<Project>) {
+ projects.forEach { project -> removeChildTasks(project) }
+ }
+
+ private fun checkIsAbstractDokkaTask(task: Task): AbstractDokkaTask {
+ if (task is AbstractDokkaTask) {
+ return task
+ }
+ throw IllegalArgumentException(
+ "Only tasks of type ${AbstractDokkaTask::class.java.name} can be added as child for " +
+ "${AbstractDokkaParentTask::class.java.name} tasks.\n" +
+ "Found task ${task.path} of type ${task::class.java.name} added to $path"
+ )
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaTask.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaTask.kt
new file mode 100644
index 00000000..169ca050
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/AbstractDokkaTask.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PackageDirectoryMismatch")
+
+package org.jetbrains.dokka.gradle
+
+import groovy.lang.Closure
+import org.gradle.api.Action
+import org.gradle.api.DefaultTask
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.MapProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.*
+import org.gradle.kotlin.dsl.listProperty
+import org.gradle.kotlin.dsl.mapProperty
+import org.gradle.kotlin.dsl.property
+import org.gradle.work.DisableCachingByDefault
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.plugability.ConfigurableBlock
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import java.util.concurrent.atomic.AtomicReference
+import java.util.function.BiConsumer
+import kotlin.reflect.full.createInstance
+import kotlin.reflect.full.memberFunctions
+
+@DisableCachingByDefault(because = "Abstract super-class, not to be instantiated directly")
+abstract class AbstractDokkaTask : DefaultTask() {
+
+ /**
+ * Display name used to refer to the module. Used for ToC, navigation, logging, etc.
+ *
+ * If set for a single-project build or a MultiModule task, will be used as project name.
+ *
+ * Default is Gradle project name.
+ */
+ @Input
+ val moduleName: Property<String> = project.objects.property<String>()
+ .convention(project.name)
+
+ /**
+ * Module version.
+ *
+ * If set for a single-project build or a MultiModule task, will be used
+ * as project version by the versioning plugin.
+ *
+ * Default is Gradle project version.
+ */
+ @Input
+ val moduleVersion: Property<String> = project.objects.property<String>()
+ .convention(project.provider { project.version.toString() })
+
+ /**
+ * Directory to which documentation will be generated, regardless of format.
+ * Can be set on per-task basis.
+ *
+ * Default is `project/buildDir/taskName.removePrefix("dokka").decapitalize()`, so
+ * for `dokkaHtmlMultiModule` task it will be `project/buildDir/htmlMultiModule`
+ */
+ @get:OutputDirectory
+ abstract val outputDirectory: DirectoryProperty
+
+ /**
+ * Configuration for Dokka plugins. This property is not expected to be used directly - if possible, use
+ * [pluginConfiguration] blocks (preferred) or [pluginsMapConfiguration] instead.
+ */
+ @Input
+ val pluginsConfiguration: ListProperty<in DokkaConfiguration.PluginConfiguration> = project.objects.listProperty()
+
+ /**
+ * JSON configuration of Dokka plugins.
+ *
+ * Key is fully qualified Dokka plugin name, value is its configuration in JSON.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * tasks.dokkaHtml {
+ * val dokkaBaseConfiguration = """
+ * {
+ * "customAssets": ["${file("assets/my-image.png")}"],
+ * "customStyleSheets": ["${file("assets/my-styles.css")}"],
+ * "footerMessage": "(c) 2022 MyOrg"
+ * }
+ * """
+ * pluginsMapConfiguration.set(
+ * mapOf("org.jetbrains.dokka.base.DokkaBase" to dokkaBaseConfiguration)
+ * )
+ * }
+ * ```
+ */
+ @Input
+ val pluginsMapConfiguration: MapProperty<String, String> = project.objects.mapProperty()
+
+ /**
+ * Whether to suppress obvious functions.
+ *
+ * A function is considered to be obvious if it is:
+ * - Inherited from `kotlin.Any`, `Kotlin.Enum`, `java.lang.Object` or `java.lang.Enum`,
+ * such as `equals`, `hashCode`, `toString`.
+ * - Synthetic (generated by the compiler) and does not have any documentation, such as
+ * `dataClass.componentN` or `dataClass.copy`.
+ *
+ * Default is `true`
+ */
+ @Input
+ val suppressObviousFunctions: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.suppressObviousFunctions)
+
+ /**
+ * Whether to suppress inherited members that aren't explicitly overridden in a given class.
+ *
+ * Note: this can suppress functions such as `equals`/`hashCode`/`toString`, but cannot suppress
+ * synthetic functions such as `dataClass.componentN` and `dataClass.copy`. Use [suppressObviousFunctions]
+ * for that.
+ *
+ * Default is `false`.
+ */
+ @Input
+ val suppressInheritedMembers: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.suppressInheritedMembers)
+
+ /**
+ * Whether to resolve remote files/links over network.
+ *
+ * This includes package-lists used for generating external documentation links:
+ * for instance, to make classes from standard library clickable.
+ *
+ * Setting this to `true` can significantly speed up build times in certain cases,
+ * but can also worsen documentation quality and user experience, for instance by
+ * not resolving some dependency's class/member links.
+ *
+ * When using offline mode, you can cache fetched files locally and provide them to
+ * Dokka as local paths. For instance, see [GradleExternalDocumentationLinkBuilder].
+ *
+ * Default is `false`.
+ */
+ @Input
+ val offlineMode: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.offlineMode)
+
+ /**
+ * Whether to fail documentation generation if Dokka has emitted a warning or an error.
+ * Will wait until all errors and warnings have been emitted first.
+ *
+ * This setting works well with [GradleDokkaSourceSetBuilder.reportUndocumented]
+ *
+ * Default is `false`.
+ */
+ @Input
+ val failOnWarning: Property<Boolean> = project.objects.property<Boolean>()
+ .convention(DokkaDefaults.failOnWarning)
+
+ @get:Optional
+ @get:InputDirectory
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val cacheRoot: DirectoryProperty
+
+ /**
+ * Type-safe configuration for a Dokka plugin.
+ *
+ * Note: this is available in Kotlin DSL only, if Dokka Gradle plugin was applied through `plugins` block
+ * and the configured plugin can be found on classpath, which may require adding a classpath dependency
+ * to `buildscript` block in case of external plugins. Some Dokka plugins, such as
+ * [org.jetbrains.dokka.base.DokkaBase], are on classpath by default.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * import org.jetbrains.dokka.base.DokkaBase
+ * import org.jetbrains.dokka.base.DokkaBaseConfiguration
+ *
+ * tasks.dokkaHtml {
+ * pluginConfiguration<DokkaBase, DokkaBaseConfiguration> {
+ * footerMessage = "Test"
+ * }
+ * }
+ * ```
+ *
+ * @param P Plugin class that extends [DokkaPlugin]
+ * @param T Plugin configuration class that extends [ConfigurableBlock]
+ */
+ inline fun <reified P : DokkaPlugin, reified T : ConfigurableBlock> pluginConfiguration(block: T.() -> Unit) {
+ val instance = T::class.createInstance().apply(block)
+ val pluginConfiguration = PluginConfigurationImpl(
+ fqPluginName = P::class.qualifiedName!!,
+ serializationFormat = DokkaConfiguration.SerializationFormat.JSON,
+ values = instance.toCompactJsonString()
+ )
+ pluginsConfiguration.add(pluginConfiguration)
+ }
+
+ @Classpath
+ val plugins: Configuration = project.maybeCreateDokkaPluginConfiguration(name)
+
+ @Classpath
+ val runtime: Configuration = project.maybeCreateDokkaRuntimeConfiguration(name)
+
+ final override fun doFirst(action: Action<in Task>): Task = super.doFirst(action)
+
+ final override fun doFirst(action: Closure<*>): Task = super.doFirst(action)
+
+ @TaskAction
+ internal open fun generateDocumentation() {
+ DokkaBootstrap(runtime, DokkaBootstrapImpl::class).apply {
+ configure(buildDokkaConfiguration().toCompactJsonString(), createProxyLogger())
+ val uncaughtExceptionHolder = AtomicReference<Throwable?>()
+ /**
+ * Run in a new thread to avoid memory leaks that are related to ThreadLocal (that keeps `URLCLassLoader`)
+ * Currently, all `ThreadLocal`s leaking are in the compiler/IDE codebase.
+ */
+ Thread { generate() }.apply {
+ setUncaughtExceptionHandler { _, throwable -> uncaughtExceptionHolder.set(throwable) }
+ start()
+ join()
+ }
+ uncaughtExceptionHolder.get()?.let { throw it }
+ }
+ }
+
+ internal abstract fun buildDokkaConfiguration(): DokkaConfigurationImpl
+
+ private fun createProxyLogger(): BiConsumer<String, String> = BiConsumer { level, message ->
+ when (level) {
+ "debug" -> logger.debug(message)
+ "info" -> logger.info(message)
+ "progress" -> logger.lifecycle(message)
+ "warn" -> logger.warn(message)
+ "error" -> logger.error(message)
+ }
+ }
+
+ init {
+ group = JavaBasePlugin.DOCUMENTATION_GROUP
+ // notCompatibleWithConfigurationCache was introduced in Gradle 7.4
+ val containsNotCompatibleWithConfigurationCache = this::class.memberFunctions.any { it.name == "notCompatibleWithConfigurationCache" && it.parameters.firstOrNull()?.name == "reason" }
+ if (containsNotCompatibleWithConfigurationCache) {
+ super.notCompatibleWithConfigurationCache("Dokka tasks are not yet compatible with the Gradle configuration cache. See https://github.com/Kotlin/dokka/issues/1217")
+ }
+ }
+
+ internal fun buildPluginsConfiguration(): List<PluginConfigurationImpl> {
+ val manuallyConfigured = pluginsMapConfiguration.get().entries.map { entry ->
+ PluginConfigurationImpl(
+ entry.key,
+ DokkaConfiguration.SerializationFormat.JSON,
+ entry.value
+ )
+ }
+ return pluginsConfiguration.get().mapNotNull { it as? PluginConfigurationImpl } + manuallyConfigured
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaCollectorTask.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaCollectorTask.kt
new file mode 100644
index 00000000..a45eec33
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaCollectorTask.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PackageDirectoryMismatch")
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.tasks.CacheableTask
+import org.jetbrains.dokka.DokkaConfigurationImpl
+
+@CacheableTask
+abstract class DokkaCollectorTask : AbstractDokkaParentTask() {
+
+ override fun generateDocumentation() {
+ checkChildDokkaTasksIsNotEmpty()
+ super.generateDocumentation()
+ }
+
+ override fun buildDokkaConfiguration(): DokkaConfigurationImpl {
+ val initialDokkaConfiguration = DokkaConfigurationImpl(
+ moduleName = moduleName.get(),
+ outputDir = outputDirectory.asFile.get(),
+ cacheRoot = cacheRoot.asFile.orNull,
+ failOnWarning = failOnWarning.get(),
+ offlineMode = offlineMode.get(),
+ pluginsClasspath = plugins.resolve().toList(),
+ pluginsConfiguration = buildPluginsConfiguration(),
+ suppressObviousFunctions = suppressObviousFunctions.get(),
+ suppressInheritedMembers = suppressInheritedMembers.get(),
+ )
+
+ val subprojectDokkaConfigurations = childDokkaTasks.map { dokkaTask -> dokkaTask.buildDokkaConfiguration() }
+ return subprojectDokkaConfigurations.fold(initialDokkaConfiguration) { acc, it: DokkaConfigurationImpl ->
+ acc.copy(
+ sourceSets = acc.sourceSets + it.sourceSets,
+ pluginsClasspath = acc.pluginsClasspath + it.pluginsClasspath
+ )
+ }
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaMultiModuleTask.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaMultiModuleTask.kt
new file mode 100644
index 00000000..2893704a
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaMultiModuleTask.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PackageDirectoryMismatch")
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.Directory
+import org.gradle.api.internal.tasks.TaskDependencyInternal
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.*
+import org.gradle.kotlin.dsl.property
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.DokkaModuleDescriptionImpl
+import java.io.File
+
+@Suppress("unused") // Shall provide source compatibility if possible
+@Deprecated("Use 'DokkaMultiModuleTask' instead", ReplaceWith("DokkaMultiModuleTask"), DeprecationLevel.ERROR)
+typealias DokkaMultimoduleTask = DokkaMultiModuleTask
+
+private typealias TaskPath = String
+
+@CacheableTask
+abstract class DokkaMultiModuleTask : AbstractDokkaParentTask() {
+
+ /**
+ * List of Markdown files that contain
+ * [module and package documentation](https://kotlinlang.org/docs/dokka-module-and-package-docs.html).
+ *
+ * Contents of specified files will be parsed and embedded into documentation as module and package descriptions.
+ *
+ * Example of such a file:
+ *
+ * ```markdown
+ * # Module kotlin-demo
+ *
+ * The module shows the Dokka usage.
+ *
+ * # Package org.jetbrains.kotlin.demo
+ *
+ * Contains assorted useful stuff.
+ *
+ * ## Level 2 heading
+ *
+ * Text after this heading is also part of documentation for `org.jetbrains.kotlin.demo`
+ *
+ * # Package org.jetbrains.kotlin.demo2
+ *
+ * Useful stuff in another package.
+ * ```
+ */
+ @get:InputFiles
+ @get:Optional
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val includes: ConfigurableFileCollection
+
+ @Internal
+ val fileLayout: Property<DokkaMultiModuleFileLayout> = project.objects.property<DokkaMultiModuleFileLayout>()
+ .convention(DokkaMultiModuleFileLayout.CompactInParent)
+
+ @get:InputFiles
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ internal abstract val sourceChildOutputDirectories: ConfigurableFileCollection
+
+ @get:OutputDirectories
+ internal val targetChildOutputDirectories: Provider<Iterable<Directory>> = project.provider {
+ childDokkaTasks.map { task -> targetChildOutputDirectory(task).get() }
+ }
+
+ @get:Input
+ internal val childDokkaTaskIncludes: Map<TaskPath, Set<File>>
+ get() = childDokkaTasks.filterIsInstance<DokkaTaskPartial>().associate { task ->
+ task.path to task.dokkaSourceSets.flatMap { it.includes }.toSet()
+ }
+
+ // The method contains a reference to internal Gradle API that is nice not to use.
+ // There was an attempt to get rid of it, but it was not successful
+ // See: https://github.com/Kotlin/dokka/pull/2835
+ @Internal
+ override fun getTaskDependencies(): TaskDependencyInternal =
+ super.getTaskDependencies() + childDokkaTasks
+
+
+ override fun generateDocumentation() {
+ checkChildDokkaTasksIsNotEmpty()
+ super.generateDocumentation()
+ }
+
+ override fun buildDokkaConfiguration(): DokkaConfigurationImpl {
+ return DokkaConfigurationImpl(
+ moduleName = moduleName.get(),
+ moduleVersion = moduleVersion.getValidVersionOrNull(),
+ outputDir = outputDirectory.asFile.get(),
+ cacheRoot = cacheRoot.asFile.orNull,
+ pluginsConfiguration = buildPluginsConfiguration(),
+ failOnWarning = failOnWarning.get(),
+ offlineMode = offlineMode.get(),
+ pluginsClasspath = plugins.resolve().toList(),
+ modules = childDokkaTasks.map { dokkaTask ->
+ DokkaModuleDescriptionImpl(
+ name = dokkaTask.moduleName.get(),
+ relativePathToOutputDirectory = targetChildOutputDirectory(dokkaTask).get().asFile.relativeTo(
+ outputDirectory.asFile.get()
+ ),
+ includes = childDokkaTaskIncludes[dokkaTask.path].orEmpty(),
+ sourceOutputDirectory = dokkaTask.outputDirectory.asFile.get(),
+ )
+ },
+ includes = includes.toSet(),
+ )
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTask.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTask.kt
new file mode 100644
index 00000000..551ab62e
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTask.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PackageDirectoryMismatch")
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.tasks.*
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.build
+
+@CacheableTask
+abstract class DokkaTask : AbstractDokkaLeafTask() {
+ override fun buildDokkaConfiguration(): DokkaConfigurationImpl =
+ DokkaConfigurationImpl(
+ moduleName = moduleName.get(),
+ moduleVersion = moduleVersion.getValidVersionOrNull(),
+ outputDir = outputDirectory.asFile.get(),
+ cacheRoot = cacheRoot.asFile.orNull,
+ offlineMode = offlineMode.get(),
+ failOnWarning = failOnWarning.get(),
+ sourceSets = unsuppressedSourceSets.build(),
+ pluginsConfiguration = buildPluginsConfiguration(),
+ pluginsClasspath = plugins.resolve().toList(),
+ suppressObviousFunctions = suppressObviousFunctions.get(),
+ suppressInheritedMembers = suppressInheritedMembers.get(),
+ )
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTaskPartial.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTaskPartial.kt
new file mode 100644
index 00000000..ae7d2066
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTaskPartial.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PackageDirectoryMismatch")
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.tasks.*
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.build
+
+@CacheableTask
+abstract class DokkaTaskPartial : AbstractDokkaLeafTask() {
+
+ override fun buildDokkaConfiguration(): DokkaConfigurationImpl {
+ return DokkaConfigurationImpl(
+ moduleName = moduleName.get(),
+ moduleVersion = moduleVersion.orNull,
+ outputDir = outputDirectory.asFile.get(),
+ cacheRoot = cacheRoot.asFile.orNull,
+ offlineMode = offlineMode.get(),
+ failOnWarning = failOnWarning.get(),
+ sourceSets = unsuppressedSourceSets.build(),
+ pluginsConfiguration = buildPluginsConfiguration(),
+ pluginsClasspath = plugins.resolve().toList(),
+ delayTemplateSubstitution = true,
+ suppressObviousFunctions = suppressObviousFunctions.get(),
+ suppressInheritedMembers = suppressInheritedMembers.get(),
+ )
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt
new file mode 100644
index 00000000..c17653aa
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.Project
+import org.gradle.api.UnknownDomainObjectException
+import org.gradle.util.Path
+import org.gradle.kotlin.dsl.findByType
+import org.gradle.kotlin.dsl.getByType
+import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
+
+
+/** Parse a Gradle path, e.g. `:project:subproject:taskName` */
+internal fun parsePath(path: String): Path = Path.path(path)
+
+internal val Project.kotlinOrNull: KotlinProjectExtension?
+ get() = try {
+ project.extensions.findByType()
+ } catch (e: Throwable) {
+ when (e) {
+ // if the user project doesn't have KGP applied, we won't be able to load the class;
+ // TypeNotPresentException is possible if it's loaded through reified generics.
+ is NoClassDefFoundError, is TypeNotPresentException, is ClassNotFoundException -> null
+ else -> throw e
+ }
+ }
+
+internal val Project.kotlin: KotlinProjectExtension
+ get() = project.extensions.getByType()
+
+internal fun Project.isAndroidProject() = try {
+ project.extensions.getByName("android")
+ true
+} catch (e: UnknownDomainObjectException) {
+ false
+} catch (e: ClassNotFoundException) {
+ false
+}
+
+internal fun KotlinTarget.isAndroidTarget() = this.platformType == KotlinPlatformType.androidJvm
+
+internal fun <T : Any> NamedDomainObjectContainer<T>.maybeCreate(name: String, configuration: T.() -> Unit): T {
+ return findByName(name) ?: create(name, configuration)
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTaskTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTaskTest.kt
new file mode 100644
index 00000000..02b7a0f9
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTaskTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("DEPRECATION")
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.create
+import org.gradle.kotlin.dsl.getByName
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.gradle.utils.subprojects_
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+class AbstractDokkaParentTaskTest {
+
+ private val rootProject = ProjectBuilder.builder().build()
+ private val subproject0 = ProjectBuilder.builder().withName("subproject0").withParent(rootProject).build()
+ private val subproject1 = ProjectBuilder.builder().withName("subproject1").withParent(rootProject).build()
+ private val subSubproject0 = ProjectBuilder.builder().withName("subSubproject0").withParent(subproject0).build()
+
+ init {
+ rootProject.subprojects_ {
+ tasks.create<DokkaTask>("dokkaTask")
+ }
+ }
+
+ private val parentTask = rootProject.tasks.create<TestDokkaParentTask>("parent")
+
+
+ @Test
+ fun `add and remove tasks by reference`() {
+ assertEquals(
+ emptySet(), parentTask.childDokkaTasks,
+ "Expected no childDokkaTasks by default"
+ )
+
+ parentTask.addChildTask(subproject0.dokkaTask)
+ assertEquals(
+ setOf(subproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subproject0.dokkaTask.path} being registered as child task"
+ )
+
+ parentTask.addChildTask(subproject1.dokkaTask)
+ assertEquals(
+ setOf(subproject0.dokkaTask, subproject1.dokkaTask), parentTask.childDokkaTasks,
+ "Expected both dokka tasks being present"
+ )
+
+ parentTask.removeChildTask(subproject0.dokkaTask)
+ assertEquals(
+ setOf(subproject1.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subproject0.dokkaTask.path} being removed from child tasks"
+ )
+
+ parentTask.addChildTask(subSubproject0.dokkaTask)
+ assertEquals(
+ setOf(subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subSubproject0.dokkaTask.path} being added as child task"
+ )
+
+ parentTask.addChildTask(subSubproject0.dokkaTask)
+ assertEquals(
+ setOf(subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected no effect for adding a task twice"
+ )
+ }
+
+ @Test
+ fun `add and remove by absolute path`() {
+ parentTask.addChildTask(":subproject0:dokkaTask")
+ assertEquals(
+ setOf(subproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subproject0.dokkaTask.path} as child task"
+ )
+
+ parentTask.addChildTask(":subproject0:subSubproject0:dokkaTask")
+ assertEquals(
+ setOf(subproject0.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subSubproject0.dokkaTask.path} being added as child task"
+ )
+
+ parentTask.removeChildTask(":subproject0:dokkaTask")
+ assertEquals(
+ setOf(subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subproject0.dokkaTask.path} being removed as child task"
+ )
+ }
+
+ @Test
+ fun `add and remove by relative path`() {
+ parentTask.addChildTask("subproject0:dokkaTask")
+ assertEquals(
+ setOf(subproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subproject0.dokkaTask.path} as child task"
+ )
+
+ parentTask.addChildTask("subproject0:subSubproject0:dokkaTask")
+ assertEquals(
+ setOf(subproject0.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subSubproject0.dokkaTask.path} being added as child task"
+ )
+
+ parentTask.removeChildTask("subproject0:dokkaTask")
+ assertEquals(
+ setOf(subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subproject0.dokkaTask.path} being removed as child task"
+ )
+ }
+
+ @Test
+ fun `add and remove by relative path ob subproject0`() {
+ val parentTask = subproject0.tasks.create<TestDokkaParentTask>("parent")
+
+ parentTask.addChildTask("subSubproject0:dokkaTask")
+ assertEquals(
+ setOf(subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subSubproject0.dokkaTask.path} being registered as child"
+ )
+
+ parentTask.removeChildTask("subSubproject0:dokkaTask")
+ assertEquals(
+ emptySet(), parentTask.childDokkaTasks,
+ "Expected ${subSubproject0.dokkaTask.path} being removed as child"
+ )
+ }
+
+ @Test
+ fun `add and remove by project and name`() {
+ parentTask.addChildTasks(rootProject.subprojects, "dokkaTask")
+ assertEquals(
+ setOf(subproject0.dokkaTask, subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected all subproject tasks being registered as child task"
+ )
+
+ parentTask.removeChildTasks(rootProject.subprojects, "dokkaTask")
+ assertEquals(
+ emptySet(), parentTask.childDokkaTasks,
+ "Expected all tasks being removed"
+ )
+
+ parentTask.addChildTasks(listOf(subproject0), "dokkaTask")
+ assertEquals(
+ setOf(subproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected only ${subproject0.dokkaTask.path} being registered as child"
+ )
+
+ parentTask.addSubprojectChildTasks("dokkaTask")
+ assertEquals(
+ setOf(subproject0.dokkaTask, subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected all subproject tasks being registered as child task"
+ )
+
+ parentTask.removeSubprojectChildTasks("dokkaTask")
+ assertEquals(
+ emptySet(), parentTask.childDokkaTasks,
+ "Expected all tasks being removed"
+ )
+
+ parentTask.addSubprojectChildTasks("dokkaTask")
+ assertEquals(
+ setOf(subproject0.dokkaTask, subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected all subproject tasks being registered as child task"
+ )
+
+ parentTask.removeChildTasks(subproject0)
+ assertEquals(
+ setOf(subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected only ${subproject0.dokkaTask.path} being removed"
+ )
+
+ parentTask.addSubprojectChildTasks("dokkaTask")
+ parentTask.removeChildTasks(listOf(subproject0, subproject1))
+ assertEquals(
+ setOf(subSubproject0.dokkaTask), parentTask.childDokkaTasks,
+ "Expected ${subproject0.dokkaTask.path} and ${subproject1.dokkaTask.path} being removed"
+ )
+ }
+
+ @Test
+ fun `adding invalid path will not throw exception`() {
+ parentTask.addChildTask(":some:stupid:path")
+ parentTask.childDokkaTasks
+ }
+
+ @Test
+ fun `adding non dokka task will throw exception`() {
+ val badTask = rootProject.tasks.create("badTask")
+ parentTask.addChildTask(badTask.path)
+ assertFailsWith<IllegalArgumentException> { parentTask.childDokkaTasks }
+ }
+}
+
+internal abstract class TestDokkaParentTask : AbstractDokkaParentTask() {
+ override fun buildDokkaConfiguration(): DokkaConfigurationImpl {
+ throw NotImplementedError()
+ }
+}
+
+private val Project.dokkaTask: DokkaTask get() = tasks.getByName<DokkaTask>("dokkaTask")
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AndroidAutoConfigurationTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AndroidAutoConfigurationTest.kt
new file mode 100644
index 00000000..eb772df9
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AndroidAutoConfigurationTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import com.android.build.gradle.LibraryExtension
+import org.gradle.api.artifacts.ResolveException
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.kotlin.dsl.configure
+import org.gradle.kotlin.dsl.withType
+import org.gradle.testfixtures.ProjectBuilder
+import kotlin.test.*
+
+class AndroidAutoConfigurationTest {
+
+ private val project = ProjectBuilder.builder().build().also { project ->
+ project.plugins.apply("com.android.library")
+ project.plugins.apply("org.jetbrains.kotlin.android")
+ project.plugins.apply("org.jetbrains.dokka")
+ project.extensions.configure<LibraryExtension> {
+ compileSdkVersion(28)
+ }
+ }
+
+ @Test
+ fun `at least one dokka task created`() {
+ val dokkaTasks = project.tasks.withType<DokkaTask>().toList()
+ assertTrue(dokkaTasks.isNotEmpty(), "Expected at least one dokka task")
+ }
+
+ @Test
+ fun `all default source sets are present in dokka`() {
+ val dokkaTasks = project.tasks.withType<DokkaTask>().toList()
+ dokkaTasks.forEach { task ->
+ val sourceSets = task.dokkaSourceSets.toList()
+ assertEquals(
+ listOf(
+ "androidTest", "androidTestDebug", "debug", "main",
+ "release", "test", "testDebug", "testRelease", "androidTestRelease"
+ ).sorted(),
+ sourceSets.map { it.name }.sorted(),
+ "Expected all default source sets being registered"
+ )
+ }
+ }
+
+ @Ignore // TODO: find where `maven` plugin is used, which was removed in Gradle 8
+ @Test
+ fun `test source sets are suppressed`() {
+ val dokkaTasks = project.tasks.withType<DokkaTask>().toList()
+ project as ProjectInternal
+ project.evaluate()
+ dokkaTasks.flatMap { it.dokkaSourceSets }.forEach { sourceSet ->
+ if ("test" in sourceSet.name.toLowerCase()) {
+ assertTrue(
+ sourceSet.suppress.get(),
+ "Expected source set `${sourceSet.name}` to be suppressed by default"
+ )
+ } else {
+ assertFalse(
+ sourceSet.suppress.get(),
+ "Expected source set `${sourceSet.name}`to not be suppressed by default"
+ )
+ }
+ }
+ }
+
+ @Ignore // TODO: find where `maven` plugin is used, which was removed in Gradle 8
+ @Test
+ fun `source sets have non-empty classpath`() {
+ val dokkaTasks = project.tasks.withType<DokkaTask>().toList()
+ project as ProjectInternal
+ project.evaluate()
+
+ dokkaTasks.flatMap { it.dokkaSourceSets }
+ .filterNot { it.name == "androidTestRelease" && it.suppress.get() } // androidTestRelease has empty classpath, but it makes no sense for suppressed source set
+ .forEach { sourceSet ->
+ /*
+
+ There is no better way of checking for empty classpath at the moment (without resolving dependencies).
+ We assume, that an empty classpath can be resolved
+ We assume, that a non-empty classpath will not be able to resolve (no repositories defined)
+ */
+ assertFailsWith<ResolveException>("SourceSet: " + sourceSet.name) { sourceSet.classpath.files }
+ }
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt
new file mode 100644
index 00000000..c8f58f27
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.jetbrains.dokka.DokkaBootstrap
+import org.jetbrains.dokka.gradle.AutomagicProxyTest.TestInterface
+import java.util.function.BiConsumer
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+
+class AutomagicProxyTest {
+
+ private class TestException(message: String, cause: Throwable?) : Exception(message, cause)
+
+ private fun interface TestInterface {
+ @Throws(Throwable::class)
+ operator fun invoke(): Int
+ }
+
+ @Test
+ fun `simple method invocation`() {
+ val instance = TestInterface { 0 }
+ val proxy = automagicTypedProxy<TestInterface>(instance.javaClass.classLoader, instance)
+ assertEquals(0, proxy())
+ }
+
+ @Test
+ fun `exception throw in DokkaBootstrap is not wrapped inside UndeclaredThrowableException`() {
+ val instanceThrowingTestException = object : DokkaBootstrap {
+ override fun configure(serializedConfigurationJSON: String, logger: BiConsumer<String, String>) = Unit
+ override fun generate() {
+ throw TestException("Test Exception Message", Exception("Cause Exception Message"))
+ }
+ }
+
+ val proxy = automagicTypedProxy<DokkaBootstrap>(
+ instanceThrowingTestException.javaClass.classLoader,
+ instanceThrowingTestException
+ )
+
+ val exception = assertFailsWith<TestException> {
+ proxy.generate()
+ }
+
+ assertEquals("Test Exception Message", exception.message)
+ assertEquals("Cause Exception Message", exception.cause?.message)
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/CheckSourceSetDependenciesTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/CheckSourceSetDependenciesTest.kt
new file mode 100644
index 00000000..92adc0e5
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/CheckSourceSetDependenciesTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.testfixtures.ProjectBuilder
+import java.lang.IllegalArgumentException
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+
+class CheckSourceSetDependenciesTest {
+
+ private val project = ProjectBuilder.builder().build()
+
+ @Test
+ fun `passes when properly configured`() {
+ val sourceSets = listOf(
+ GradleDokkaSourceSetBuilder("common", project),
+ GradleDokkaSourceSetBuilder("jvmAndJsCommon", project).apply {
+ dependsOn("common")
+ },
+ GradleDokkaSourceSetBuilder("jvm", project).apply {
+ dependsOn("jvmAndJsCommon")
+ },
+ GradleDokkaSourceSetBuilder("js", project).apply {
+ dependsOn("jvmAndJsCommon")
+ }
+ )
+ checkSourceSetDependencies(sourceSets)
+ }
+
+ @Test
+ fun `throws exception when dependent source set id cant be found`() {
+ val sourceSets = listOf(
+ GradleDokkaSourceSetBuilder("main", project),
+ GradleDokkaSourceSetBuilder("bad", project).apply {
+ dependsOn("missing")
+ }
+ )
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ checkSourceSetDependencies(sourceSets)
+ }
+
+ assertTrue("bad" in exception.message.orEmpty(), "Expected name of source set mentioned")
+ assertTrue("missing" in exception.message.orEmpty(), "Expected name of missing source set mentioned")
+ }
+
+ @Test
+ fun `throws exception when documented source set depends on suppressed source set`() {
+ val sourceSets = listOf(
+ GradleDokkaSourceSetBuilder("common", project),
+ GradleDokkaSourceSetBuilder("intermediate", project).apply {
+ dependsOn("common")
+ suppress.set(true)
+ },
+ GradleDokkaSourceSetBuilder("jvm", project).apply {
+ dependsOn("intermediate")
+ }
+ )
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ checkSourceSetDependencies(sourceSets)
+ }
+
+ assertTrue("intermediate" in exception.message.orEmpty())
+ assertTrue("jvm" in exception.message.orEmpty())
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/ConfigureWithKotlinSourceSetGistTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/ConfigureWithKotlinSourceSetGistTest.kt
new file mode 100644
index 00000000..55acbf2f
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/ConfigureWithKotlinSourceSetGistTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.artifacts.FileCollectionDependency
+import org.gradle.kotlin.dsl.get
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.gradle.kotlin.KotlinSourceSetGist
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import org.jetbrains.dokka.gradle.utils.withDependencies_
+import kotlin.test.assertTrue
+
+class ConfigureWithKotlinSourceSetGistTest {
+ @Test
+ fun `example gist`() {
+ val project = ProjectBuilder.builder().build()
+
+ val f1Jar = project.file("f1.jar")
+ val f2Jar = project.file("f2.jar")
+ assertTrue(f1Jar.createNewFile())
+ assertTrue(f2Jar.createNewFile())
+
+ val customSourceRoot = project.file("customSourceRoot")
+ assertTrue(customSourceRoot.mkdirs())
+
+ val gist = KotlinSourceSetGist(
+ name = "customName",
+ platform = project.provider { KotlinPlatformType.common },
+ isMain = project.provider { true },
+ classpath = project.provider { project.files(f1Jar, f2Jar) },
+ sourceRoots = project.files(customSourceRoot),
+ dependentSourceSetNames = project.provider { setOf("customRootSourceSet") }
+ )
+
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ sourceSet.configureWithKotlinSourceSetGist(gist)
+
+ assertEquals(
+ "common", sourceSet.build().displayName,
+ "Expected platform being used as default displayName for source set"
+ )
+
+ assertEquals(
+ Platform.common, sourceSet.build().analysisPlatform,
+ "Expected common platform being set"
+ )
+
+ assertEquals(
+ listOf(f1Jar, f2Jar), sourceSet.build().classpath,
+ "Expected classpath being present"
+ )
+
+ assertEquals(
+ setOf(sourceSet.DokkaSourceSetID("customRootSourceSet")), sourceSet.build().dependentSourceSets,
+ "Expected customRootSourceSet being present in dependentSourceSets after build"
+ )
+
+ assertEquals(
+ setOf(customSourceRoot), sourceSet.build().sourceRoots,
+ "Expected customSourceRoot being present in sourceRoots after build"
+ )
+ }
+
+ @Test
+ fun `display name for source set customMain`() {
+ val project = ProjectBuilder.builder().build()
+
+ val gist = KotlinSourceSetGist(
+ name = "customMain",
+ platform = project.provider { KotlinPlatformType.common },
+ isMain = project.provider { true },
+ classpath = project.provider { project.files() },
+ sourceRoots = project.files(),
+ dependentSourceSetNames = project.provider { emptySet() }
+ )
+
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ sourceSet.configureWithKotlinSourceSetGist(gist)
+
+ assertEquals(
+ "custom", sourceSet.build().displayName,
+ "Expected 'Main' being trimmed from source set name and used as display name"
+ )
+ }
+
+ @Suppress("UnstableApiUsage")
+ @Test
+ fun `configuration with kotlin source set is live`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.kotlin.jvm")
+ val kotlin = project.kotlin as KotlinJvmProjectExtension
+ val mainSourceSet = kotlin.sourceSets["main"]
+
+ /* Make sure that the source roots exist on filesystem */
+ mainSourceSet.kotlin.sourceDirectories.elements.get().map { it.asFile }.forEach { it.mkdirs() }
+
+ /* Make sure to remove dependencies that cannot be resolved during test */
+ project.configurations.configureEach {
+ withDependencies_ {
+ removeIf { dependency -> dependency !is FileCollectionDependency }
+ }
+ }
+
+ val dokkaSourceSet = GradleDokkaSourceSetBuilder("main", project)
+ dokkaSourceSet.kotlinSourceSet(mainSourceSet)
+
+ assertEquals(
+ listOf(project.file("src/main/kotlin"), project.file("src/main/java")),
+ dokkaSourceSet.sourceRoots.elements.get().map { it.asFile },
+ "Expected default source roots being present in dokkaSourceSet"
+ )
+
+ val customSourceRoot = project.file("src/main/customRoot")
+ assertTrue(customSourceRoot.mkdirs())
+ mainSourceSet.kotlin.srcDir(customSourceRoot)
+
+ assertEquals(
+ listOf(project.file("src/main/kotlin"), project.file("src/main/java"), project.file("src/main/customRoot")),
+ dokkaSourceSet.sourceRoots.elements.get().map { it.asFile },
+ "Expected customRoot being added to source roots in dokkaSourceSet"
+ )
+ }
+
+ @Test
+ fun `changing classpath`() {
+ val project = ProjectBuilder.builder().build()
+ val dokkaSourceSet = GradleDokkaSourceSetBuilder("main", project)
+ var classpath = project.files()
+
+ dokkaSourceSet.configureWithKotlinSourceSetGist(
+ KotlinSourceSetGist(
+ name = "gist",
+ platform = project.provider { KotlinPlatformType.common },
+ isMain = project.provider { true },
+ dependentSourceSetNames = project.provider { emptySet() },
+ sourceRoots = project.files(),
+ classpath = project.provider { classpath }
+ )
+ )
+
+ dokkaSourceSet.classpath.from("base.jar")
+ classpath.from("f1.jar")
+ classpath.from("f2.jar")
+ assertEquals(
+ setOf(project.file("f1.jar"), project.file("f2.jar"), project.file("base.jar")),
+ dokkaSourceSet.classpath.files,
+ "Expected files from initial gist classpath and manually added file base.jar to be present in classpath"
+ )
+
+ /*
+ Swapping the original file collection in favour of a new one.
+ We expect that the base.jar is still present, as it was configured on the dokka source set.
+ We also expect, that the new files from the new file collection are replacing old ones
+ */
+ classpath = project.files("f3.jar", "f4.jar")
+ assertEquals(
+ setOf(project.file("f3.jar"), project.file("f4.jar"), project.file("base.jar")),
+ dokkaSourceSet.classpath.files,
+ "Expected files from changed gist classpath and manually added file base.jar to be present in classpath"
+ )
+ }
+
+
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationJsonTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationJsonTest.kt
new file mode 100644
index 00000000..c2a05eb5
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationJsonTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.kotlin.dsl.withType
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.PluginConfigurationImpl
+import org.jetbrains.dokka.gradle.utils.create_
+import org.jetbrains.dokka.gradle.utils.externalDocumentationLink_
+import org.jetbrains.dokka.gradle.utils.withDependencies_
+import org.jetbrains.dokka.toCompactJsonString
+import java.io.File
+import java.net.URL
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class DokkaConfigurationJsonTest {
+
+ @Test
+ fun `DokkaTask configuration toJsonString then parseJson`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+ val dokkaTask = project.tasks.withType<DokkaTask>().first()
+ dokkaTask.plugins.withDependencies_ { clear() }
+ dokkaTask.apply {
+ this.failOnWarning.set(true)
+ this.offlineMode.set(true)
+ this.outputDirectory.set(File("customOutputDir"))
+ this.cacheRoot.set(File("customCacheRoot"))
+ this.pluginsConfiguration.add(
+ PluginConfigurationImpl(
+ "A",
+ DokkaConfiguration.SerializationFormat.JSON,
+ """ { "key" : "value1" } """
+ )
+ )
+ this.pluginsConfiguration.add(
+ PluginConfigurationImpl(
+ "B",
+ DokkaConfiguration.SerializationFormat.JSON,
+ """ { "key" : "value2" } """
+ )
+ )
+ this.dokkaSourceSets.create_("main") {
+ displayName.set("customSourceSetDisplayName")
+ reportUndocumented.set(true)
+
+ externalDocumentationLink_ {
+ packageListUrl.set(URL("http://some.url"))
+ url.set(URL("http://some.other.url"))
+ }
+ perPackageOption {
+ includeNonPublic.set(true)
+ reportUndocumented.set(true)
+ skipDeprecated.set(true)
+ documentedVisibilities.set(setOf(DokkaConfiguration.Visibility.PRIVATE))
+ }
+ }
+ }
+
+ val sourceConfiguration = dokkaTask.buildDokkaConfiguration()
+ val configurationJson = sourceConfiguration.toCompactJsonString()
+ val parsedConfiguration = DokkaConfigurationImpl(configurationJson)
+
+ assertEquals(sourceConfiguration, parsedConfiguration)
+ println(parsedConfiguration)
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationSerializableTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationSerializableTest.kt
new file mode 100644
index 00000000..02fd728b
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationSerializableTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.kotlin.dsl.withType
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.PluginConfigurationImpl
+import org.jetbrains.dokka.gradle.utils.create_
+import org.jetbrains.dokka.gradle.utils.externalDocumentationLink_
+import org.jetbrains.dokka.gradle.utils.withDependencies_
+import org.junit.jupiter.api.io.TempDir
+import java.io.File
+import java.io.ObjectInputStream
+import java.io.ObjectOutputStream
+import java.net.URL
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class DokkaConfigurationSerializableTest {
+
+ @Test
+ fun `DokkaTask configuration write to file then parse`(@TempDir tempDirectory: File) {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+ val dokkaTask = project.tasks.withType<DokkaTask>().first()
+ dokkaTask.plugins.withDependencies_ { clear() }
+ dokkaTask.apply {
+ this.failOnWarning.set(true)
+ this.offlineMode.set(true)
+ this.outputDirectory.set(File("customOutputDir"))
+ this.cacheRoot.set(File("customCacheRoot"))
+ this.pluginsConfiguration.add(
+ PluginConfigurationImpl(
+ "A",
+ DokkaConfiguration.SerializationFormat.JSON,
+ """ { "key" : "value1" } """
+ )
+ )
+ this.pluginsConfiguration.add(
+ PluginConfigurationImpl(
+ "B",
+ DokkaConfiguration.SerializationFormat.JSON,
+ """ { "key" : "value2" } """
+ )
+ )
+ this.dokkaSourceSets.create_("main") {
+ displayName.set("customSourceSetDisplayName")
+ reportUndocumented.set(true)
+
+ externalDocumentationLink_ {
+ packageListUrl.set(URL("http://some.url"))
+ url.set(URL("http://some.other.url"))
+ }
+
+ perPackageOption {
+ includeNonPublic.set(true)
+ reportUndocumented.set(true)
+ skipDeprecated.set(true)
+ documentedVisibilities.set(setOf(DokkaConfiguration.Visibility.PRIVATE))
+ }
+ }
+ }
+
+ val sourceConfiguration = dokkaTask.buildDokkaConfiguration()
+ val configurationFile = tempDirectory.resolve("config.bin")
+ ObjectOutputStream(configurationFile.outputStream()).use { stream ->
+ stream.writeObject(sourceConfiguration)
+ }
+ val parsedConfiguration = ObjectInputStream(configurationFile.inputStream()).use { stream ->
+ stream.readObject() as DokkaConfiguration
+ }
+
+ assertEquals(sourceConfiguration, parsedConfiguration)
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayoutTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayoutTest.kt
new file mode 100644
index 00000000..8acd3547
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayoutTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.kotlin.dsl.create
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.DokkaException
+import org.jetbrains.dokka.gradle.DokkaMultiModuleFileLayout.CompactInParent
+import org.jetbrains.dokka.gradle.DokkaMultiModuleFileLayout.NoCopy
+import java.io.File
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+
+class DokkaMultiModuleFileLayoutTest {
+
+ @Test
+ fun `no copy`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+
+ val child = project.tasks.create<DokkaTask>("child")
+ val parent = project.tasks.create<DokkaMultiModuleTask>("parent")
+ child.outputDirectory.set(File("some/path"))
+
+ assertEquals(
+ File("some/path"),
+ NoCopy.targetChildOutputDirectory(parent, child).get().asFile.relativeTo(project.projectDir),
+ "Expected original file path returned"
+ )
+ }
+
+ @Test
+ fun `compact in parent`() {
+ val rootProject = ProjectBuilder.builder().build()
+
+ val parentProject = ProjectBuilder.builder().withName("parent").withParent(rootProject).build()
+ parentProject.plugins.apply("org.jetbrains.dokka")
+
+ val intermediateProject = ProjectBuilder.builder().withName("intermediate").withParent(parentProject).build()
+ val childProject = ProjectBuilder.builder().withName("child").withParent(intermediateProject).build()
+ childProject.plugins.apply("org.jetbrains.dokka")
+
+ val parentTask = parentProject.tasks.create<DokkaMultiModuleTask>("parentTask")
+ val childTask = childProject.tasks.create<DokkaTask>("childTask")
+
+ val targetOutputDirectory = CompactInParent.targetChildOutputDirectory(parentTask, childTask)
+ assertEquals(
+ parentTask.outputDirectory.get().asFile.resolve("intermediate/child"),
+ targetOutputDirectory.get().asFile,
+ "Expected nested file structure representing project structure"
+ )
+ }
+
+ @Test
+ fun copyChildOutputDirectory() {
+ /* Prepare */
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+
+ val childTask = project.tasks.create<DokkaTask>("child")
+ val parentTask = project.tasks.create<DokkaMultiModuleTask>("parent")
+
+ val sourceOutputDirectory = childTask.outputDirectory.get().asFile
+ sourceOutputDirectory.mkdirs()
+ sourceOutputDirectory.resolve("some.file").writeText("some text")
+ val subFolder = sourceOutputDirectory.resolve("subFolder")
+ subFolder.mkdirs()
+ subFolder.resolve("other.file").writeText("other text")
+
+ parentTask.fileLayout.set(DokkaMultiModuleFileLayout { parent, _ ->
+ parent.project.provider { parent.project.layout.projectDirectory.dir("target/output") }
+ })
+ parentTask.copyChildOutputDirectory(childTask)
+
+ /* Assertions */
+ val targetOutputDirectory = project.file("target/output")
+ assertTrue(
+ targetOutputDirectory.exists() && targetOutputDirectory.isDirectory,
+ "Expected target output directory ${targetOutputDirectory.path} to exist"
+ )
+
+ val targetSomeFile = targetOutputDirectory.resolve("some.file")
+ assertTrue(
+ targetSomeFile.exists() && targetSomeFile.isFile,
+ "Expected sample file to exist in target output directory"
+ )
+
+ assertEquals(
+ "some text", targetSomeFile.readText(),
+ "Expected content to be written into sample file"
+ )
+
+ val targetSubFolder = targetOutputDirectory.resolve("subFolder")
+ assertTrue(
+ targetSubFolder.exists() && targetSubFolder.isDirectory,
+ "Expected sub folder being present in target output directory"
+ )
+
+ val targetOtherFile = targetSubFolder.resolve("other.file")
+ assertTrue(
+ targetOtherFile.exists() && targetOtherFile.isFile,
+ "Expected nested 'other.file' being copied into target"
+ )
+
+ assertEquals(
+ "other text", targetOtherFile.readText(),
+ "Expected content to be written into 'other.file'"
+ )
+ }
+
+ @Test
+ fun `copyChildOutputDirectory target output directory within itself throws DokkaException`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+
+ val childTask = project.tasks.create<DokkaTask>("child")
+ val parentTask = project.tasks.create<DokkaMultiModuleTask>("parent")
+ parentTask.fileLayout.set(DokkaMultiModuleFileLayout { _, child ->
+ child.outputDirectory.dir("subfolder")
+ })
+ assertFailsWith<DokkaException> { parentTask.copyChildOutputDirectory(childTask) }
+ }
+
+ @Test
+ fun `copyChildOutputDirectory NoCopy`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+
+ val childTask = project.tasks.create<DokkaTask>("child")
+ val parentTask = project.tasks.create<DokkaMultiModuleTask>("parent")
+ parentTask.fileLayout.set(NoCopy)
+ parentTask.copyChildOutputDirectory(childTask)
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaPluginApplyTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaPluginApplyTest.kt
new file mode 100644
index 00000000..d41ba672
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaPluginApplyTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.TaskContainer
+import org.gradle.kotlin.dsl.withType
+import org.gradle.testfixtures.ProjectBuilder
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+class DokkaPluginApplyTest {
+
+ @Test
+ fun `one task per format is registered`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+
+ assertTrue(
+ project.tasks.findByName("dokkaHtml") is DokkaTask,
+ "Expected DokkaTask: dokkaHtml"
+ )
+
+ assertTrue(
+ project.tasks.findByName("dokkaGfm") is DokkaTask,
+ "Expected DokkaTask: dokkaGfm"
+ )
+
+ assertTrue(
+ project.tasks.findByName("dokkaJekyll") is DokkaTask,
+ "Expected DokkaTask: dokkaJekyll"
+ )
+
+ assertTrue(
+ project.tasks.findByName("dokkaJavadoc") is DokkaTask,
+ "Expected DokkaTask: dokkaJavadoc"
+ )
+ }
+
+ @Test
+ fun `dokka plugin configurations extend dokkaPlugin`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+
+ val dokkaPluginsConfiguration = project.maybeCreateDokkaDefaultPluginConfiguration()
+
+ project.tasks.withType<DokkaTask>().forEach { dokkaTask ->
+ assertSame(
+ dokkaTask.plugins.extendsFrom.single(), dokkaPluginsConfiguration,
+ "Expected dokka plugins configuration to extend default ${dokkaPluginsConfiguration.name} configuration"
+ )
+ }
+ }
+
+ @Test
+ fun `all dokka tasks are part of the documentation group`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+ assertDokkaTasksHaveDocumentationGroup(project.tasks)
+ }
+
+ @Test
+ fun `all dokka tasks are part of the documentation group in a multi module setup`() {
+ val root = ProjectBuilder.builder().withName("root").build()
+ val child = ProjectBuilder.builder().withName("child").withParent(root).build()
+ root.plugins.apply("org.jetbrains.dokka")
+ child.plugins.apply("org.jetbrains.dokka")
+ assertDokkaTasksHaveDocumentationGroup(root.tasks)
+ assertDokkaTasksHaveDocumentationGroup(child.tasks)
+ }
+
+ @Test
+ fun `old dokka tasks are part of the deprecated group in a multi module setup`() {
+ val root = ProjectBuilder.builder().withName("root").build()
+ val child = ProjectBuilder.builder().withName("child").withParent(root).build()
+ root.plugins.apply("org.jetbrains.dokka")
+ child.plugins.apply("org.jetbrains.dokka")
+ assertOldDokkaTasksHaveDeprecatedGroup(root.tasks)
+ assertOldDokkaTasksHaveDeprecatedGroup(child.tasks)
+ }
+
+ @Test
+ fun `all dokka tasks provide a task description`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+ assertDokkaTasksHaveDescription(project.tasks)
+ }
+
+ @Test
+ fun `all dokka tasks provide a task description in a multi module setup`() {
+ val root = ProjectBuilder.builder().withName("root").build()
+ val child = ProjectBuilder.builder().withName("child").withParent(root).build()
+ root.plugins.apply("org.jetbrains.dokka")
+ child.plugins.apply("org.jetbrains.dokka")
+ assertDokkaTasksHaveDescription(root.tasks)
+ assertDokkaTasksHaveDescription(child.tasks)
+ }
+
+ @Test
+ fun `parent dokka tasks have children configured`() {
+ val root = ProjectBuilder.builder().withName("root").build()
+ val child = ProjectBuilder.builder().withName("child").withParent(root).build()
+ root.plugins.apply("org.jetbrains.dokka")
+ child.plugins.apply("org.jetbrains.dokka")
+
+ val parentTasks = root.tasks.withType<AbstractDokkaParentTask>()
+ assertTrue(parentTasks.isNotEmpty(), "Expected at least one parent task being created")
+
+ parentTasks.toList().forEach { parentTask ->
+ assertEquals(1, parentTask.childDokkaTasks.size, "Expected one child dokka task")
+ assertEquals(
+ child, parentTask.childDokkaTasks.single().project,
+ "Expected child dokka task from child project"
+ )
+ }
+ }
+}
+
+private fun assertDokkaTasksHaveDocumentationGroup(taskContainer: TaskContainer) {
+ taskContainer.withType<AbstractDokkaTask>().forEach { dokkaTask ->
+ assertEquals(
+ JavaBasePlugin.DOCUMENTATION_GROUP,
+ dokkaTask.group,
+ "Expected task: ${dokkaTask.path} group to be \"${JavaBasePlugin.DOCUMENTATION_GROUP}\""
+ )
+ }
+}
+
+private fun assertOldDokkaTasksHaveDeprecatedGroup(taskContainer: TaskContainer) {
+ taskContainer.names.filter { "Multimodule" in it }.forEach { dokkaTaskName ->
+ val dokkaTask = taskContainer.getByName(dokkaTaskName)
+ val expectedGroup = "deprecated"
+ assertEquals(
+ expectedGroup,
+ dokkaTask.group,
+ "Expected task: ${dokkaTask.path} group to be \"${expectedGroup}\""
+ )
+ }
+}
+
+private fun assertDokkaTasksHaveDescription(taskContainer: TaskContainer) {
+ taskContainer.withType<AbstractDokkaTask>().forEach { dokkaTask ->
+ assertTrue(
+ @Suppress("UselessCallOnNotNull") // Task.description is nullable, but not inherited as Kotlin sees it.
+ dokkaTask.description.orEmpty().isNotEmpty(),
+ "Expected description for task ${dokkaTask.name}"
+ )
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt
new file mode 100644
index 00000000..b973ba53
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.jetbrains.dokka.DokkaSourceSetID
+
+@Suppress("TestFunctionName")
+fun GradleDokkaSourceSetBuilder(name: String, project: Project, sourceSetScopeId: String = "${project.path}:test"):
+ GradleDokkaSourceSetBuilder {
+ return GradleDokkaSourceSetBuilder(name, project) { DokkaSourceSetID(sourceSetScopeId, it) }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderTest.kt
new file mode 100644
index 00000000..76f6f58e
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilderTest.kt
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.closureOf
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.*
+import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
+import java.net.URL
+import kotlin.test.*
+
+class GradleDokkaSourceSetBuilderTest {
+
+ private val project = ProjectBuilder.builder().withName("root").build()
+
+ @Test
+ fun sourceSetId() {
+ val sourceSet = GradleDokkaSourceSetBuilder("myName", project, "scopeId")
+ assertEquals(
+ DokkaSourceSetID("scopeId", "myName"), sourceSet.sourceSetID,
+ "Expected sourceSet.sourceSetID to match output of DokkaSourceSetID factory function"
+ )
+
+ assertEquals(
+ "scopeId/myName", sourceSet.sourceSetID.toString(),
+ "Expected SourceSetId's string representation"
+ )
+ }
+
+ @Test
+ fun classpath() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ sourceSet.classpath.from(project.file("path/to/file.jar"))
+ sourceSet.classpath.from(project.file("path/to/other.jar"))
+
+ assertEquals(
+ listOf(project.file("path/to/file.jar"), project.file("path/to/other.jar")), sourceSet.classpath.toList(),
+ "Expected both file paths being present in classpath"
+ )
+
+ assertEquals(
+ listOf(project.file("path/to/file.jar"), project.file("path/to/other.jar")),
+ sourceSet.build().classpath.toList(),
+ "Expected both file paths being present in built classpath"
+ )
+ }
+
+ @Test
+ fun displayName() {
+ val sourceSet = GradleDokkaSourceSetBuilder("myName", project)
+ assertNull(
+ sourceSet.displayName.orNull,
+ "Expected no ${GradleDokkaSourceSetBuilder::displayName.name} being set by default"
+ )
+
+ assertEquals(
+ "myName", sourceSet.build().displayName,
+ "Expected source set name being used for ${DokkaConfiguration.DokkaSourceSet::displayName.name} " +
+ "after building source set with no ${GradleDokkaSourceSetBuilder::displayName.name} being set"
+ )
+
+ sourceSet.displayName.set("displayName")
+ assertEquals(
+ "displayName", sourceSet.build().displayName,
+ "Expected previously set ${GradleDokkaSourceSetBuilder::displayName.name} to be present after build"
+ )
+ }
+
+ @Test
+ fun `displayName default for sourceSet ending with Main`() {
+ val sourceSet = GradleDokkaSourceSetBuilder("jvmMain", project)
+ assertEquals(
+ "jvm", sourceSet.build().displayName,
+ "Expected 'Main' being stripped for source set display name after build"
+ )
+ }
+
+ @Test
+ fun sourceRoots() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ sourceSet.sourceRoots.from(project.file("root1"))
+ sourceSet.sourceRoot(project.file("root2"))
+ sourceSet.sourceRoot(project.file("root3").absolutePath)
+ sourceSet.sourceRoot("root4")
+
+ assertEquals(
+ listOf("root1", "root2", "root3", "root4").map(project::file).toSet(),
+ sourceSet.build().sourceRoots,
+ "Expected all files being present"
+ )
+
+ sourceSet.build().sourceRoots.forEach { root ->
+ assertTrue(
+ root.startsWith(project.projectDir),
+ "Expected all roots to be inside the projectDir\n" +
+ "projectDir: ${project.projectDir}\n" +
+ "root: ${root.absolutePath})"
+ )
+ }
+ }
+
+ @Test
+ fun dependentSourceSets() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertEquals(emptySet(), sourceSet.build().dependentSourceSets, "Expected no dependent sourceSets by default")
+
+ sourceSet.dependentSourceSets.add(sourceSet.DokkaSourceSetID("s1"))
+ sourceSet.dependsOn("s2")
+ sourceSet.dependsOn(sourceSet.DokkaSourceSetID("s3"))
+ sourceSet.dependsOn(GradleDokkaSourceSetBuilder("s4", project))
+ sourceSet.dependsOn(GradleDokkaSourceSetBuilder("s5", project).build())
+ sourceSet.dependsOn(createDefaultKotlinSourceSet("s6"))
+ sourceSet.dependsOn(DefaultAndroidSourceSet("s7", project, false))
+
+ assertEquals(
+ listOf(":/s1", ":/s2", ":/s3", ":/s4", ":/s5", ":/s6", ":/s7"),
+ sourceSet.build().dependentSourceSets.map { it.toString() },
+ "Expected all source sets being registered"
+ )
+ }
+
+ private fun createDefaultKotlinSourceSet(displayName: String): DefaultKotlinSourceSet {
+ return project.objects.newInstance(DefaultKotlinSourceSet::class.java, project, displayName)
+ }
+
+ @Test
+ fun samples() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertEquals(emptySet(), sourceSet.build().samples, "Expected no default samples")
+ sourceSet.samples.from(project.file("s1"))
+ sourceSet.samples.from(project.file("s2"))
+ assertEquals(
+ setOf(project.file("s1"), project.file("s2")), sourceSet.build().samples,
+ "Expected all samples being present after build"
+ )
+ }
+
+ @Test
+ fun includes() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertEquals(emptySet(), sourceSet.build().includes, "Expected no default includees")
+ sourceSet.includes.from(project.file("i1"))
+ sourceSet.includes.from(project.file("i2"))
+ assertEquals(
+ setOf(project.file("i1"), project.file("i2")), sourceSet.build().includes,
+ "Expected all includes being present after build"
+ )
+ }
+
+ @Test
+ @Suppress("DEPRECATION")
+ fun includeNonPublic() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertEquals(
+ DokkaDefaults.includeNonPublic, sourceSet.build().includeNonPublic,
+ "Expected default value for ${GradleDokkaSourceSetBuilder::includeNonPublic.name}"
+ )
+
+ sourceSet.includeNonPublic.set(!DokkaDefaults.includeNonPublic)
+ assertEquals(
+ !DokkaDefaults.includeNonPublic, sourceSet.build().includeNonPublic,
+ "Expected flipped value for ${GradleDokkaSourceSetBuilder::includeNonPublic.name}"
+ )
+ }
+
+ @Test
+ fun documentedVisibilities() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertEquals(
+ DokkaDefaults.documentedVisibilities, sourceSet.build().documentedVisibilities,
+ "Expected default value for ${GradleDokkaSourceSetBuilder::documentedVisibilities.name}"
+ )
+
+ val visibilities = setOf(DokkaConfiguration.Visibility.PRIVATE, DokkaConfiguration.Visibility.INTERNAL)
+ sourceSet.documentedVisibilities.set(visibilities)
+ assertEquals(
+ visibilities, sourceSet.build().documentedVisibilities,
+ "Expected to see previously set value for ${GradleDokkaSourceSetBuilder::includeNonPublic.name}"
+ )
+ }
+
+ @Test
+ fun reportUndocumented() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertEquals(
+ DokkaDefaults.reportUndocumented, sourceSet.build().reportUndocumented,
+ "Expected default value for ${GradleDokkaSourceSetBuilder::reportUndocumented.name}"
+ )
+
+ sourceSet.reportUndocumented.set(!DokkaDefaults.reportUndocumented)
+ assertEquals(
+ !DokkaDefaults.reportUndocumented, sourceSet.build().reportUndocumented,
+ "Expected flipped value for ${GradleDokkaSourceSetBuilder::reportUndocumented.name}"
+ )
+ }
+
+ @Test
+ fun jdkVersion() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertEquals(
+ DokkaDefaults.jdkVersion, sourceSet.build().jdkVersion,
+ "Expected default value for ${GradleDokkaSourceSetBuilder::jdkVersion.name}"
+ )
+
+ sourceSet.jdkVersion.set(DokkaDefaults.jdkVersion + 1)
+ assertEquals(
+ DokkaDefaults.jdkVersion + 1, sourceSet.build().jdkVersion,
+ "Expected increased value for ${GradleDokkaSourceSetBuilder::jdkVersion.name}"
+ )
+ }
+
+ @Test
+ fun sourceLinks() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertEquals(emptySet(), sourceSet.build().sourceLinks, "Expected no default source links")
+
+ sourceSet.sourceLinks.add(
+ GradleSourceLinkBuilder(project).apply {
+ this.remoteLineSuffix.set("ls1")
+ this.localDirectory.set(project.file("p1"))
+ this.remoteUrl.set(URL("https://u1"))
+ })
+
+ sourceSet.sourceLink {
+ remoteLineSuffix.set("ls2")
+ localDirectory.set(project.file("p2"))
+ remoteUrl.set(URL("https://u2"))
+ }
+
+ sourceSet.sourceLink(project.closureOf<GradleSourceLinkBuilder> {
+ this.remoteLineSuffix.set("ls3")
+ this.localDirectory.set(project.file("p3"))
+ this.remoteUrl.set(URL("https://u3"))
+ })
+
+ assertEquals(
+ setOf(
+ SourceLinkDefinitionImpl(
+ remoteLineSuffix = "ls1",
+ localDirectory = project.file("p1").absolutePath,
+ remoteUrl = URL("https://u1")
+ ),
+ SourceLinkDefinitionImpl(
+ remoteLineSuffix = "ls2",
+ localDirectory = project.file("p2").absolutePath,
+ remoteUrl = URL("https://u2")
+ ),
+ SourceLinkDefinitionImpl(
+ remoteLineSuffix = "ls3",
+ localDirectory = project.file("p3").absolutePath,
+ remoteUrl = URL("https://u3")
+ )
+ ),
+ sourceSet.build().sourceLinks,
+ "Expected all source links being present after build"
+ )
+ }
+
+ @Test
+ fun perPackageOptions() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertEquals(emptyList(), sourceSet.build().perPackageOptions, "Expected no default per package options")
+
+ sourceSet.perPackageOptions.add(GradlePackageOptionsBuilder(project).apply {
+ this.matchingRegex.set("p1.*")
+ })
+
+ sourceSet.perPackageOption {
+ matchingRegex.set("p2.*")
+ }
+
+ sourceSet.perPackageOption(project.closureOf<GradlePackageOptionsBuilder> {
+ this.matchingRegex.set("p3.*")
+ })
+
+ assertEquals(
+ listOf("p1.*", "p2.*", "p3.*").map { matchingRegex ->
+ PackageOptionsImpl(
+ matchingRegex = matchingRegex,
+ includeNonPublic = DokkaDefaults.includeNonPublic,
+ documentedVisibilities = DokkaDefaults.documentedVisibilities,
+ reportUndocumented = DokkaDefaults.reportUndocumented,
+ skipDeprecated = DokkaDefaults.skipDeprecated,
+ suppress = DokkaDefaults.suppress
+ )
+ },
+ sourceSet.build().perPackageOptions,
+ "Expected all package options being present after build"
+ )
+ }
+
+ @Test
+ fun externalDocumentationLink() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ sourceSet.noAndroidSdkLink.set(true)
+ sourceSet.noJdkLink.set(true)
+ sourceSet.noStdlibLink.set(true)
+ assertEquals(
+ emptySet(), sourceSet.build().externalDocumentationLinks,
+ "Expected no default external documentation links"
+ )
+
+ sourceSet.externalDocumentationLinks.add(
+ GradleExternalDocumentationLinkBuilder(project).apply {
+ this.url.set(URL("https://u1"))
+ this.packageListUrl.set(URL("https://pl1"))
+ }
+ )
+
+ sourceSet.externalDocumentationLink {
+ url.set(URL("https://u2"))
+ }
+
+ sourceSet.externalDocumentationLink(project.closureOf<GradleExternalDocumentationLinkBuilder> {
+ url.set(URL("https://u3"))
+ })
+
+ sourceSet.externalDocumentationLink(url = "https://u4", packageListUrl = "https://pl4")
+ sourceSet.externalDocumentationLink(url = URL("https://u5"))
+
+ assertEquals(
+ setOf(
+ ExternalDocumentationLinkImpl(URL("https://u1"), URL("https://pl1")),
+ ExternalDocumentationLinkImpl(URL("https://u2"), URL("https://u2/package-list")),
+ ExternalDocumentationLinkImpl(URL("https://u3"), URL("https://u3/package-list")),
+ ExternalDocumentationLinkImpl(URL("https://u4"), URL("https://pl4")),
+ ExternalDocumentationLinkImpl(URL("https://u5"), URL("https://u5/package-list"))
+ ),
+ sourceSet.build().externalDocumentationLinks,
+ "Expected all external documentation links being present after build"
+ )
+ }
+
+ @Test
+ fun languageVersion() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertNull(sourceSet.build().languageVersion, "Expected no language version being set by default")
+
+ sourceSet.languageVersion.set("JAVA_20")
+ assertEquals(
+ "JAVA_20", sourceSet.build().languageVersion,
+ "Expected previously set language version to be present after build"
+ )
+ }
+
+ @Test
+ fun apiVersion() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertNull(sourceSet.build().apiVersion, "Expected no api version being set by default")
+
+ sourceSet.apiVersion.set("20")
+ assertEquals(
+ "20", sourceSet.build().apiVersion,
+ "Expected previously set api version to be present after build"
+ )
+ }
+
+ @Test
+ fun noStdlibLink() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertFalse(sourceSet.noStdlibLink.get(), "Expected 'noStdlibLink' to be set to false by default")
+
+ assertEquals(1, sourceSet.build().externalDocumentationLinks.count {
+ "https://kotlinlang.org/api" in it.url.toURI().toString()
+ }, "Expected kotlin stdlib in external documentation links")
+
+ sourceSet.noStdlibLink.set(true)
+
+ assertEquals(
+ 0, sourceSet.build().externalDocumentationLinks.count {
+ "https://kotlinlang.org/api" in it.url.toURI().toString()
+ }, "Expected no stdlib in external documentation link"
+ )
+ }
+
+ @Test
+ fun noJdkLink() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertFalse(sourceSet.noJdkLink.get(), "Expected 'noJdkLink' to be set to false by default")
+
+ assertEquals(1, sourceSet.build().externalDocumentationLinks.count {
+ "https://docs.oracle.com/" in it.url.toURI().toString()
+ }, "Expected java jdk in external documentation links")
+
+ sourceSet.noJdkLink.set(true)
+
+ assertEquals(
+ 0, sourceSet.build().externalDocumentationLinks.count {
+ "https://docs.oracle.com/" in it.url.toURI().toString()
+ }, "Expected no java jdk in external documentation link"
+ )
+ }
+
+
+ @Test
+ fun noAndroidSdkLink() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertFalse(sourceSet.noAndroidSdkLink.get(), "Expected 'noAndroidSdkLink' to be set to false by default")
+
+ assertEquals(0, sourceSet.build().externalDocumentationLinks.count {
+ "https://developer.android.com/reference" in it.url.toURI().toString()
+ }, "Expected no android sdk in external documentation links (without android plugin)")
+
+ assertEquals(0, sourceSet.build().externalDocumentationLinks.count {
+ "https://developer.android.com/reference/androidx" in it.packageListUrl.toURI().toString()
+ }, "Expected no androidx in external documentation links (without android plugin)")
+
+
+ project.plugins.apply("com.android.library")
+
+ assertEquals(1, sourceSet.build().externalDocumentationLinks.count {
+ "https://developer.android.com/reference/kotlin/package-list" in it.packageListUrl.toURI().toString()
+ }, "Expected android sdk in external documentation links")
+
+ assertEquals(1, sourceSet.build().externalDocumentationLinks.count {
+ "https://developer.android.com/reference/kotlin/androidx/package-list" in it.packageListUrl.toURI()
+ .toString()
+ }, "Expected androidx in external documentation links")
+
+
+ sourceSet.noAndroidSdkLink.set(true)
+
+ assertEquals(0, sourceSet.build().externalDocumentationLinks.count {
+ "https://developer.android.com/reference" in it.url.toURI().toString()
+ }, "Expected no android sdk in external documentation links")
+
+ assertEquals(0, sourceSet.build().externalDocumentationLinks.count {
+ "https://developer.android.com/reference/kotlin/androidx/package-list" in it.packageListUrl.toURI()
+ .toString()
+ }, "Expected no androidx in external documentation links")
+ }
+
+ @Test
+ fun suppressedFiles() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertTrue(sourceSet.build().suppressedFiles.isEmpty(), "Expected no suppressed files by default")
+
+ sourceSet.suppressedFiles.from(project.file("f1"))
+ sourceSet.suppressedFiles.from("f2")
+
+ assertEquals(
+ setOf(project.file("f1"), project.file("f2")), sourceSet.build().suppressedFiles,
+ "Expected all suppressed files to be present after build"
+ )
+ }
+
+ @Test
+ fun suppressedFilesByDefault() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertTrue(sourceSet.build().suppressedFiles.isEmpty(), "Expected no suppressed files by default")
+
+ val file = project.buildDir.resolve("generated").also { it.mkdirs() }
+ file.resolve("suppressed.kt").writeText("class A")
+
+ sourceSet.sourceRoots.from(project.buildDir.resolve("generated"))
+
+ val suppressedConfiguration = sourceSet.build()
+ sourceSet.suppressGeneratedFiles.set(false)
+ val unsuppressedConfiguration = sourceSet.build()
+
+ assertEquals(
+ setOf(
+ project.buildDir.resolve("generated"),
+ project.buildDir.resolve("generated").resolve("suppressed.kt")
+ ), suppressedConfiguration.suppressedFiles,
+ "Expected all suppressed files to be present after build"
+ )
+
+ assertTrue(
+ unsuppressedConfiguration.suppressedFiles.isEmpty(),
+ "Expected no files to be suppressed by default"
+ )
+ }
+
+ @Test
+ fun platform() {
+ val sourceSet = GradleDokkaSourceSetBuilder("", project)
+ assertEquals(Platform.DEFAULT, sourceSet.build().analysisPlatform, "Expected default platform if not specified")
+
+ sourceSet.platform.set(Platform.common)
+ assertEquals(
+ Platform.common, sourceSet.build().analysisPlatform,
+ "Expected previously set analysis platform being present after build"
+ )
+ }
+}
+
+@Suppress("TestFunctionName")
+private fun GradleDokkaSourceSetBuilder(name: String, project: Project) =
+ GradleDokkaSourceSetBuilder(name, project, project.path)
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt
new file mode 100644
index 00000000..f95dfdc2
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.kotlin.dsl.withType
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.gradle.utils.configureEach_
+import org.jetbrains.dokka.gradle.utils.create_
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
+import java.io.File
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class KotlinDslDokkaTaskConfigurationTest {
+ @Test
+ fun `configure dokka task`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+ project.tasks.withType<DokkaTask>().configureEach_ {
+ outputDirectory.set(File("test"))
+ }
+
+ project.tasks.withType(DokkaTask::class.java).forEach { dokkaTask ->
+ assertEquals(File("test"), dokkaTask.outputDirectory.get().asFile.relativeTo(project.projectDir))
+ }
+ }
+
+ @Test
+ fun `sourceSet dependsOn by String`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+
+ project.tasks.withType(DokkaTask::class.java).forEach { dokkaTask ->
+ dokkaTask.dokkaSourceSets.run {
+ val commonMain = create("commonMain")
+ val jvmMain = create_("jvmMain") {
+ dependsOn("commonMain")
+ }
+
+ assertEquals(
+ 0, commonMain.dependentSourceSets.get().size,
+ "Expected no dependent source set in commonMain"
+ )
+
+ assertEquals(
+ 1, jvmMain.dependentSourceSets.get().size,
+ "Expected only one dependent source set in jvmMain"
+ )
+
+ assertEquals(
+ commonMain.sourceSetID, jvmMain.dependentSourceSets.get().single(),
+ "Expected jvmMain to depend on commonMain"
+ )
+
+ assertEquals(
+ DokkaSourceSetID(dokkaTask, "commonMain"), commonMain.sourceSetID
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `sourceSet dependsOn by DokkaSourceSet`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+
+ project.tasks.withType(DokkaTask::class.java).first().run {
+ dokkaSourceSets.run {
+ val commonMain = create("commonMain")
+ val jvmMain = create("jvmMain") {
+ dependsOn(commonMain)
+ }
+
+ assertEquals(
+ commonMain.sourceSetID, jvmMain.dependentSourceSets.get().single()
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `sourceSet dependsOn by KotlinSourceSet`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+ project.plugins.apply("org.jetbrains.kotlin.jvm")
+
+ val kotlin = project.extensions.getByName("kotlin") as KotlinJvmProjectExtension
+
+ project.tasks.withType(DokkaTask::class.java).first().apply {
+ dokkaSourceSets.run {
+ val special = create("special") {
+ dependsOn(kotlin.sourceSets.getByName("main"))
+ }
+
+ assertEquals(
+ DokkaSourceSetID(this@apply, "main"), special.dependentSourceSets.get().single()
+ )
+ }
+ }
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinSourceSetGistTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinSourceSetGistTest.kt
new file mode 100644
index 00000000..5c99502a
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinSourceSetGistTest.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle
+
+import org.jetbrains.dokka.gradle.utils.withDependencies_
+import org.gradle.api.artifacts.FileCollectionDependency
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.kotlin.dsl.get
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.gradle.kotlin.gistOf
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import java.io.File
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class KotlinSourceSetGistTest {
+
+ @Test
+ fun `main source set with kotlin jvm`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.kotlin.jvm")
+ val kotlin = project.kotlin as KotlinJvmProjectExtension
+
+ val mainSourceSet = kotlin.sourceSets.getByName("main")
+ val mainSourceSetGist = project.gistOf(mainSourceSet)
+
+ assertEquals(
+ "main", mainSourceSetGist.name,
+ "Expected correct source set name"
+ )
+
+ assertEquals(
+ KotlinPlatformType.jvm, mainSourceSetGist.platform.get(),
+ "Expected correct platform"
+ )
+
+ assertTrue(
+ mainSourceSetGist.isMain.get(),
+ "Expected main sources to be marked as 'isMain'"
+ )
+
+ assertEquals(
+ emptySet(), mainSourceSetGist.dependentSourceSetNames.get(),
+ "Expected no dependent source sets"
+ )
+ }
+
+ @Test
+ fun `test source set with kotlin jvm`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.kotlin.jvm")
+ val kotlin = project.kotlin as KotlinJvmProjectExtension
+
+ val testSourceSet = kotlin.sourceSets.getByName("test")
+ val testSourceSetGist = project.gistOf(testSourceSet)
+
+ assertFalse(
+ testSourceSetGist.isMain.get(),
+ "Expected test source set not being marked as 'isMain'"
+ )
+
+ assertEquals(
+ emptySet(),
+ testSourceSetGist.dependentSourceSetNames.get(),
+ "Expected no dependent source sets"
+ )
+ }
+
+ @Test
+ fun `sourceRoots of main source set with kotlin jvm`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.kotlin.jvm")
+ val kotlin = project.kotlin as KotlinJvmProjectExtension
+ val mainSourceSet = kotlin.sourceSets.getByName("main")
+ val mainSourceSetGist = project.gistOf(mainSourceSet)
+
+ assertEquals(
+ emptySet(), mainSourceSetGist.sourceRoots.files,
+ "Expected no sourceRoots, because default source root does not exist on filesystem yet"
+ )
+
+ // Create default source root on filesystem
+ val defaultSourceRoot = project.file("src/main/kotlin")
+ defaultSourceRoot.mkdirs()
+
+ assertEquals(
+ setOf(defaultSourceRoot), mainSourceSetGist.sourceRoots.files,
+ "Expected default source root in source roots, since it is present on the filesystem"
+ )
+
+ // Create custom source set (and make sure it exists on filesystem)
+ val customSourceRoot = project.file("src/main/custom").also(File::mkdirs)
+ mainSourceSet.kotlin.srcDir(customSourceRoot)
+
+ assertEquals(
+ setOf(defaultSourceRoot, customSourceRoot), mainSourceSetGist.sourceRoots.files,
+ "Expected recently registered custom source root to be present"
+ )
+
+ // removing default source root
+ mainSourceSet.kotlin.setSrcDirs(listOf(customSourceRoot))
+
+ assertEquals(
+ setOf(customSourceRoot), mainSourceSetGist.sourceRoots.files,
+ "Expected only custom source root being present in source roots"
+ )
+ }
+
+ @Suppress("UnstableApiUsage")
+ @Test
+ fun `classpath of main source set with kotlin jvm`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.kotlin.jvm")
+ val kotlin = project.kotlin as KotlinJvmProjectExtension
+ val mainSourceSet = kotlin.sourceSets.getByName("main")
+ val mainSourceSetGist = project.gistOf(mainSourceSet)
+
+ /* Only work with file dependencies */
+ project.configurations.forEach { configuration ->
+ configuration.withDependencies_ {
+ removeIf { dependency ->
+ dependency !is FileCollectionDependency
+ }
+ }
+ }
+
+ val implementationJar = project.file("implementation.jar")
+ val compileOnlyJar = project.file("compileOnly.jar")
+ val apiJar = project.file("api.jar")
+ val runtimeOnlyJar = project.file("runtimeOnly.jar")
+
+
+ mainSourceSet.dependencies {
+ implementation(project.files(implementationJar))
+ compileOnly(project.files(compileOnlyJar))
+ api(project.files(apiJar))
+ runtimeOnly(project.files(runtimeOnlyJar))
+ }
+
+ assertEquals(
+ emptySet(), mainSourceSetGist.classpath.get().files,
+ "Expected no files on the classpath, since no file exists"
+ )
+
+ /* Creating dependency files */
+ assertTrue(implementationJar.createNewFile())
+ assertTrue(compileOnlyJar.createNewFile())
+ assertTrue(apiJar.createNewFile())
+ assertTrue(runtimeOnlyJar.createNewFile())
+
+ assertEquals(
+ setOf(implementationJar, compileOnlyJar, apiJar), mainSourceSetGist.classpath.get().files,
+ "Expected implementation, compileOnly and api dependencies on classpath"
+ )
+ }
+
+ @Test
+ fun `common, jvm and macos source sets with kotlin multiplatform`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.kotlin.multiplatform")
+ val kotlin = project.kotlin as KotlinMultiplatformExtension
+ kotlin.jvm()
+ kotlin.macosX64("macos")
+
+ (project as DefaultProject).evaluate()
+ val commonMainSourceSet = kotlin.sourceSets.getByName("commonMain")
+ val commonMainSourceSetGist = project.gistOf(commonMainSourceSet)
+
+ val jvmMainSourceSet = kotlin.sourceSets.getByName("jvmMain")
+ val jvmMainSourceSetGist = project.gistOf(jvmMainSourceSet)
+
+ val macosMainSourceSet = kotlin.sourceSets.getByName("macosMain")
+ val macosMainSourceSetGist = project.gistOf(macosMainSourceSet)
+
+ assertEquals(
+ "commonMain", commonMainSourceSetGist.name,
+ "Expected correct source set name"
+ )
+
+ assertEquals(
+ "jvmMain", jvmMainSourceSetGist.name,
+ "Expected correct source set name"
+ )
+
+ assertEquals(
+ "macosMain", macosMainSourceSetGist.name,
+ "Expected correct source set name"
+ )
+
+ assertEquals(
+ KotlinPlatformType.common, commonMainSourceSetGist.platform.get(),
+ "Expected common platform for commonMain source set"
+ )
+
+ assertEquals(
+ KotlinPlatformType.jvm, jvmMainSourceSetGist.platform.get(),
+ "Expected jvm platform for jvmMain source set"
+ )
+
+ assertEquals(
+ KotlinPlatformType.native, macosMainSourceSetGist.platform.get(),
+ "Expected native platform for macosMain source set"
+ )
+
+ assertTrue(
+ commonMainSourceSetGist.isMain.get(),
+ "Expected commonMain to be marked with 'isMain'"
+ )
+
+ assertTrue(
+ jvmMainSourceSetGist.isMain.get(),
+ "Expected jvmMain to be marked with 'isMain'"
+ )
+
+ assertTrue(
+ macosMainSourceSetGist.isMain.get(),
+ "Expected macosMain to be marked with 'isMain'"
+ )
+
+ assertFalse(
+ project.gistOf(kotlin.sourceSets["commonTest"]).isMain.get(),
+ "Expected commonTest not being marked with 'isMain'"
+ )
+
+ assertFalse(
+ project.gistOf(kotlin.sourceSets["jvmTest"]).isMain.get(),
+ "Expected jvmTest not being marked with 'isMain'"
+ )
+
+ assertFalse(
+ project.gistOf(kotlin.sourceSets["macosTest"]).isMain.get(),
+ "Expected macosTest not being marked with 'isMain'"
+ )
+
+ // requires `project.evaluate()`
+ assertEquals(
+ setOf("commonMain"), jvmMainSourceSetGist.dependentSourceSetNames.get(),
+ "Expected jvmMain to depend on commonMain by default"
+ )
+ }
+
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinGradlePluginVersionTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinGradlePluginVersionTest.kt
new file mode 100644
index 00000000..e0731687
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/kotlin/KotlinGradlePluginVersionTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle.kotlin
+
+import kotlin.test.*
+
+class KotlinGradlePluginVersionTest {
+
+ @Test
+ fun `should parse versions`() {
+ assertParsedVersion(input = "1.7.22", expectedMajor = 1, expectedMinor = 7, expectedPatch = 22)
+ assertParsedVersion(input = "2.0.0", expectedMajor = 2, expectedMinor = 0, expectedPatch = 0)
+ assertParsedVersion(input = "1.8.22-RC2", expectedMajor = 1, expectedMinor = 8, expectedPatch = 22)
+ assertParsedVersion(input = "1.5.22-Beta", expectedMajor = 1, expectedMinor = 5, expectedPatch = 22)
+ assertParsedVersion(input = "2.7.22-mercury-500", expectedMajor = 2, expectedMinor = 7, expectedPatch = 22)
+ assertParsedVersion(input = "1.7.100-dev-800", expectedMajor = 1, expectedMinor = 7, expectedPatch = 100)
+ }
+
+ @Test
+ fun `should return null on non parsable string`() {
+ assertNull(parse("1.7"))
+ assertNull(parse("1"))
+ assertNull(parse("6"))
+ assertNull(parse("17.0"))
+ assertNull(parse("1..7.0"))
+ assertNull(parse("1.7-Beta"))
+ assertNull(parse("1.7.0Beta"))
+ }
+
+ @Test
+ fun `should compare simple versions`() {
+ assertEquals(0, KotlinGradlePluginVersion(1, 7, 0).compareTo(KotlinGradlePluginVersion(1, 7, 0)))
+
+ assertTrue(KotlinGradlePluginVersion(1, 6, 10) >= KotlinGradlePluginVersion(1, 6, 10))
+ assertTrue(KotlinGradlePluginVersion(1, 6, 10) < KotlinGradlePluginVersion(1, 6, 20))
+ assertTrue(KotlinGradlePluginVersion(1, 6, 10) > KotlinGradlePluginVersion(1, 6, 0))
+
+ assertTrue(KotlinGradlePluginVersion(1, 4, 32) <= KotlinGradlePluginVersion(1, 4, 32))
+ assertTrue(KotlinGradlePluginVersion(1, 4, 32) < KotlinGradlePluginVersion(1, 6, 20))
+ assertTrue(KotlinGradlePluginVersion(1, 4, 32) > KotlinGradlePluginVersion(1, 3, 0))
+
+ assertTrue(KotlinGradlePluginVersion(2, 1, 0) > KotlinGradlePluginVersion(1, 8, 0))
+ assertTrue(KotlinGradlePluginVersion(1, 5, 31) < KotlinGradlePluginVersion(1, 7, 0))
+ }
+
+ @Test
+ fun `should compare custom dev versions with trailing strings`() {
+ assertEquals(0, KotlinGradlePluginVersion(1, 7, 0).compareTo(parseNotNull("1.7.0")))
+
+ assertTrue(KotlinGradlePluginVersion(1, 6, 10) >= parseNotNull("1.6.10-Beta"))
+ assertTrue(KotlinGradlePluginVersion(1, 6, 10) < parseNotNull("1.6.20"))
+ assertTrue(KotlinGradlePluginVersion(1, 6, 10) > parseNotNull("1.6.0-RC2"))
+
+ assertTrue(KotlinGradlePluginVersion(1, 4, 32) <= parseNotNull("1.4.32-dev-142"))
+ assertTrue(KotlinGradlePluginVersion(1, 4, 32) < parseNotNull("1.6.20-Beta"))
+ assertTrue(KotlinGradlePluginVersion(1, 4, 32) > parseNotNull("1.3.0-RC"))
+
+ assertTrue(KotlinGradlePluginVersion(2, 1, 0) > parseNotNull("1.8.0-mercury-500"))
+ }
+
+ private fun assertParsedVersion(
+ input: String,
+ expectedMajor: Int,
+ expectedMinor: Int,
+ expectedPatch: Int
+ ) {
+ val kgpVersion = parseNotNull(input)
+ assertEquals(expectedMajor, kgpVersion.major)
+ assertEquals(expectedMinor, kgpVersion.minor)
+ assertEquals(expectedPatch, kgpVersion.patch)
+ assertEquals(KotlinGradlePluginVersion(expectedMajor, expectedMinor, expectedPatch), kgpVersion)
+ }
+
+ private fun parseNotNull(input: String): KotlinGradlePluginVersion = assertNotNull(parse(input))
+
+ private fun parse(input: String): KotlinGradlePluginVersion? = parseKotlinVersion(input)
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaCollectorTaskTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaCollectorTaskTest.kt
new file mode 100644
index 00000000..07cb04d2
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaCollectorTaskTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle.tasks
+
+import org.gradle.kotlin.dsl.create
+import org.gradle.kotlin.dsl.withType
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.DokkaException
+import org.jetbrains.dokka.gradle.AbstractDokkaTask
+import org.jetbrains.dokka.gradle.DokkaCollectorTask
+import org.jetbrains.dokka.gradle.DokkaTask
+import org.jetbrains.dokka.gradle.utils.all_
+import org.jetbrains.dokka.gradle.utils.allprojects_
+import org.jetbrains.dokka.gradle.utils.configureEach_
+import org.jetbrains.dokka.gradle.utils.withDependencies_
+import java.io.File
+import kotlin.test.*
+
+class DokkaCollectorTaskTest {
+
+ @Test
+ fun buildDokkaConfiguration() {
+ val rootProject = ProjectBuilder.builder().build()
+ val childProject = ProjectBuilder.builder().withParent(rootProject).build()
+ childProject.plugins.apply("org.jetbrains.kotlin.jvm")
+
+ rootProject.allprojects_ {
+ plugins.apply("org.jetbrains.dokka")
+ tasks.withType<AbstractDokkaTask>().configureEach_ {
+ plugins.withDependencies_ { clear() }
+ }
+ tasks.withType<DokkaTask>().configureEach_ {
+ dokkaSourceSets.configureEach_ {
+ classpath.setFrom(emptyList<Any>())
+ }
+ }
+ }
+
+ val collectorTasks = rootProject.tasks.withType<DokkaCollectorTask>()
+ collectorTasks.configureEach_ {
+ moduleName.set("custom Module Name")
+ outputDirectory.set(File("customOutputDirectory"))
+ cacheRoot.set(File("customCacheRoot"))
+ failOnWarning.set(true)
+ offlineMode.set(true)
+ }
+
+ assertTrue(collectorTasks.isNotEmpty(), "Expected at least one collector task")
+
+ collectorTasks.forEach { task ->
+ val dokkaConfiguration = task.buildDokkaConfiguration()
+ assertEquals(
+ DokkaConfigurationImpl(
+ moduleName = "custom Module Name",
+ outputDir = rootProject.projectDir.resolve("customOutputDirectory"),
+ cacheRoot = rootProject.projectDir.resolve("customCacheRoot"),
+ failOnWarning = true,
+ offlineMode = true,
+ sourceSets = task.childDokkaTasks
+ .map { it.buildDokkaConfiguration() }
+ .map { it.sourceSets }
+ .reduce { acc, list -> acc + list },
+ pluginsClasspath = task.childDokkaTasks
+ .map { it.plugins.resolve().toList() }
+ .reduce { acc, mutableSet -> acc + mutableSet }
+ ),
+ dokkaConfiguration,
+ )
+ }
+ }
+
+ @Test
+ fun `verify that cacheRoot is optional, and not required to build DokkaConfiguration`() {
+ val rootProject = ProjectBuilder.builder().build()
+ val childProject = ProjectBuilder.builder().withParent(rootProject).build()
+ childProject.plugins.apply("org.jetbrains.kotlin.jvm")
+
+ rootProject.allprojects_ {
+ plugins.apply("org.jetbrains.dokka")
+ tasks.withType<AbstractDokkaTask>().configureEach_ {
+ plugins.withDependencies_ { clear() }
+ }
+ tasks.withType<DokkaTask>().configureEach_ {
+ dokkaSourceSets.configureEach_ {
+ classpath.setFrom(emptyList<Any>())
+ }
+ }
+ }
+
+ val collectorTasks = rootProject.tasks.withType<DokkaCollectorTask>()
+ collectorTasks.configureEach_ {
+ cacheRoot.set(null as File?)
+ }
+
+ assertTrue(collectorTasks.isNotEmpty(), "Expected at least one collector task")
+
+ collectorTasks.forEach { task ->
+ val dokkaConfiguration = task.buildDokkaConfiguration()
+ assertNull(dokkaConfiguration.cacheRoot, "Expect that cacheRoot is null")
+ }
+ }
+
+ @Test
+ fun `with no child tasks throws DokkaException`() {
+ val project = ProjectBuilder.builder().build()
+ val collectorTask = project.tasks.create<DokkaCollectorTask>("collector")
+ project.configurations.all_ { withDependencies_ { clear() } }
+ assertFailsWith<DokkaException> { collectorTask.generateDocumentation() }
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaMultiModuleTaskTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaMultiModuleTaskTest.kt
new file mode 100644
index 00000000..a95af583
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaMultiModuleTaskTest.kt
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("UnstableApiUsage", "DEPRECATION", "PackageDirectoryMismatch")
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.kotlin.dsl.create
+import org.gradle.kotlin.dsl.withType
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.gradle.utils.allprojects_
+import org.jetbrains.dokka.gradle.utils.configureEach_
+import org.jetbrains.dokka.gradle.utils.create_
+import org.jetbrains.dokka.gradle.utils.withDependencies_
+import java.io.File
+import kotlin.test.*
+
+class DokkaMultiModuleTaskTest {
+
+ private val rootProject = ProjectBuilder.builder()
+ .withName("root")
+ .build()
+
+ private val childProject = ProjectBuilder.builder()
+ .withName("child")
+ .withProjectDir(rootProject.projectDir.resolve("child"))
+ .withParent(rootProject).build()
+
+ private val childDokkaTask = childProject.tasks.create<DokkaTaskPartial>("childDokkaTask")
+
+ private val multiModuleTask = rootProject.tasks.create<DokkaMultiModuleTask>("multiModuleTask").apply {
+ addChildTask(childDokkaTask)
+ }
+
+ init {
+ rootProject.plugins.apply("org.jetbrains.dokka")
+ childProject.plugins.apply("org.jetbrains.dokka")
+ rootProject.allprojects_ {
+ tasks.withType<AbstractDokkaTask>().configureEach_ {
+ plugins.withDependencies_ { clear() }
+ }
+ }
+ }
+
+ @Test
+ fun `child project is withing root project`() {
+ assertEquals(
+ rootProject.projectDir, childProject.projectDir.parentFile,
+ "Expected child project being inside the root project"
+ )
+
+ assertEquals(
+ childProject.projectDir.name, "child",
+ "Expected folder of child project to be called 'child'"
+ )
+ }
+
+ @Test
+ fun buildDokkaConfiguration() {
+ val include1 = childDokkaTask.project.file("include1.md")
+ val include2 = childDokkaTask.project.file("include2.md")
+ val topLevelInclude = multiModuleTask.project.file("README.md")
+
+ childDokkaTask.apply {
+ dokkaSourceSets.create("main")
+ dokkaSourceSets.create("test")
+ dokkaSourceSets.configureEach_ {
+ includes.from(include1, include2)
+ }
+ }
+
+ multiModuleTask.apply {
+ moduleVersion.set("1.5.0")
+ moduleName.set("custom Module Name")
+ outputDirectory.set(project.buildDir.resolve("customOutputDirectory"))
+ cacheRoot.set(File("customCacheRoot"))
+ pluginsConfiguration.add(
+ PluginConfigurationImpl(
+ "pluginA",
+ DokkaConfiguration.SerializationFormat.JSON,
+ """ { "key" : "value2" } """
+ )
+ )
+ failOnWarning.set(true)
+ offlineMode.set(true)
+ includes.from(listOf(topLevelInclude))
+ }
+
+ val dokkaConfiguration = multiModuleTask.buildDokkaConfiguration()
+ assertEquals(
+ DokkaConfigurationImpl(
+ moduleName = "custom Module Name",
+ moduleVersion = "1.5.0",
+ outputDir = multiModuleTask.project.buildDir.resolve("customOutputDirectory"),
+ cacheRoot = multiModuleTask.project.projectDir.resolve("customCacheRoot"),
+ pluginsConfiguration = mutableListOf(
+ PluginConfigurationImpl(
+ "pluginA",
+ DokkaConfiguration.SerializationFormat.JSON,
+ """ { "key" : "value2" } """
+ )
+ ),
+ pluginsClasspath = emptyList(),
+ failOnWarning = true,
+ offlineMode = true,
+ includes = setOf(topLevelInclude),
+ modules = listOf(
+ DokkaModuleDescriptionImpl(
+ name = "child",
+ relativePathToOutputDirectory = File("child"),
+ includes = setOf(include1, include2),
+ sourceOutputDirectory = childDokkaTask.outputDirectory.get().asFile
+ )
+ )
+ ),
+ dokkaConfiguration
+ )
+ }
+
+ @Test
+ fun `multimodule task should not include unspecified version`() {
+ childDokkaTask.apply {
+ dokkaSourceSets.create("main")
+ dokkaSourceSets.create("test")
+ }
+
+ multiModuleTask.apply {
+ moduleVersion.set("unspecified")
+ }
+
+ val dokkaConfiguration = multiModuleTask.buildDokkaConfiguration()
+ assertNull(dokkaConfiguration.moduleVersion)
+ }
+
+ @Test
+ fun `setting dokkaTaskNames declares proper task dependencies`() {
+ val dependenciesInitial = multiModuleTask.taskDependencies.getDependencies(multiModuleTask).toSet()
+ assertEquals(1, dependenciesInitial.size, "Expected one dependency")
+ val dependency = dependenciesInitial.single()
+
+ assertTrue(
+ dependency is DokkaTaskPartial,
+ "Expected dependency to be of Type ${DokkaTaskPartial::class.simpleName}"
+ )
+ assertEquals(childProject, dependency.project, "Expected dependency from child project")
+
+
+ val customDokkaTask = childProject.tasks.create<DokkaTask>("customDokkaTask")
+
+ multiModuleTask.addSubprojectChildTasks("customDokkaTask")
+ val dependenciesAfter = multiModuleTask.taskDependencies.getDependencies(multiModuleTask).toSet()
+
+ assertEquals(2, dependenciesAfter.size, "Expected two dependencies")
+ assertTrue(customDokkaTask in dependenciesAfter, "Expected 'customDokkaTask' in dependencies")
+
+ }
+
+ @Test
+ fun `multimodule task with no child tasks throws DokkaException`() {
+ val project = ProjectBuilder.builder().build()
+ val multimodule = project.tasks.create<DokkaMultiModuleTask>("multimodule")
+ project.configurations.configureEach_ { withDependencies_ { clear() } }
+ assertFailsWith<DokkaException> { multimodule.generateDocumentation() }
+ }
+
+ @Test
+ fun childDokkaTaskIncludes() {
+ val childDokkaTaskInclude1 = childProject.file("include1")
+ val childDokkaTaskInclude2 = childProject.file("include2")
+ val childDokkaTaskInclude3 = childProject.file("include3")
+
+ childDokkaTask.apply {
+ dokkaSourceSets.create_("main") {
+ includes.from(childDokkaTaskInclude1, childDokkaTaskInclude2)
+ }
+ dokkaSourceSets.create_("main2") {
+ includes.from(childDokkaTaskInclude3)
+ }
+ }
+
+ val secondChildDokkaTaskInclude = childProject.file("include4")
+ val secondChildDokkaTask = childProject.tasks.create<DokkaTaskPartial>("secondChildDokkaTask") {
+ dokkaSourceSets.create_("main") {
+ includes.from(secondChildDokkaTaskInclude)
+ }
+ }
+ multiModuleTask.addChildTask(secondChildDokkaTask)
+
+ assertEquals(
+ mapOf(
+ ":child:childDokkaTask" to setOf(
+ childDokkaTaskInclude1,
+ childDokkaTaskInclude2,
+ childDokkaTaskInclude3
+ ),
+ ":child:secondChildDokkaTask" to setOf(secondChildDokkaTaskInclude)
+ ),
+ multiModuleTask.childDokkaTaskIncludes
+ )
+ }
+
+ @Test
+ fun sourceChildOutputDirectories() {
+ val parent = ProjectBuilder.builder().build()
+ parent.plugins.apply("org.jetbrains.dokka")
+ val child = ProjectBuilder.builder().withName("child").withParent(parent).build()
+ child.plugins.apply("org.jetbrains.dokka")
+
+ val parentTask = parent.tasks.create<DokkaMultiModuleTask>("parent")
+ val childTask = child.tasks.create<DokkaTask>("child")
+
+ parentTask.addChildTask(childTask)
+ childTask.outputDirectory.set(child.file("custom/output"))
+
+ assertEquals(
+ listOf(parent.file("child/custom/output")),
+ parentTask.sourceChildOutputDirectories.files.toList(),
+ "Expected child output directory being present"
+ )
+ }
+
+ @Test
+ fun targetChildOutputDirectories() {
+ val parent = ProjectBuilder.builder().build()
+ val child = ProjectBuilder.builder().withName("child").withParent(parent).build()
+
+ val parentTask = parent.tasks.create<DokkaMultiModuleTask>("parent")
+ val childTask = child.tasks.create<DokkaTask>("child")
+
+ parentTask.addChildTask(childTask)
+
+ parentTask.fileLayout.set(DokkaMultiModuleFileLayout { taskParent, taskChild ->
+ taskParent.project.layout.buildDirectory.dir(taskChild.name)
+ })
+
+ assertEquals(
+ listOf(parent.project.buildDir.resolve("child")),
+ parentTask.targetChildOutputDirectories.get().map { it.asFile },
+ "Expected child target output directory being present"
+ )
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTaskTest.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTaskTest.kt
new file mode 100644
index 00000000..d86fa268
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/tasks/DokkaTaskTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PackageDirectoryMismatch")
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.kotlin.dsl.create
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.gradle.utils.all_
+import org.jetbrains.dokka.gradle.utils.register_
+import org.jetbrains.dokka.gradle.utils.withDependencies_
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+class DokkaTaskTest {
+ @Test
+ fun `no suppressed source sets are present after in built configuration`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+
+ val task = project.tasks.create<DokkaTask>("dokkaTask")
+ project.configurations.all_ { withDependencies_ { clear() } }
+
+ task.dokkaSourceSets.register("main")
+ task.dokkaSourceSets.register("jvm")
+ task.dokkaSourceSets.register_("test") {
+ suppress.set(true)
+ }
+
+ assertEquals(
+ listOf("main", "jvm").sorted(),
+ task.buildDokkaConfiguration().sourceSets.map { it.sourceSetID.sourceSetName }.sorted(),
+ "Expected only unsuppressed source sets `main` and `test` to be present in built configuration"
+ )
+ }
+
+ @Test
+ fun `module version is not present if not specified`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+
+ val task = project.tasks.create<DokkaTask>("dokkaTask")
+ project.configurations.all_ { withDependencies_ { clear() } }
+
+ task.dokkaSourceSets.register("main")
+ assertNull(task.buildDokkaConfiguration().moduleVersion)
+ }
+}
diff --git a/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/utils/samWithReceiverWorkarounds.kt b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/utils/samWithReceiverWorkarounds.kt
new file mode 100644
index 00000000..198f189e
--- /dev/null
+++ b/dokka-runners/runner-gradle-plugin-classic/src/test/kotlin/org/jetbrains/dokka/gradle/utils/samWithReceiverWorkarounds.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.gradle.utils
+
+import org.gradle.api.*
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.DependencySet
+import org.jetbrains.dokka.gradle.GradleDokkaSourceSetBuilder
+import org.jetbrains.dokka.gradle.GradleExternalDocumentationLinkBuilder
+
+
+/**
+ * Workarounds because `SamWithReceiver` not working in test sources
+ * https://youtrack.jetbrains.com/issue/KTIJ-14684
+ *
+ * The `SamWithReceiver` plugin is automatically applied by the `kotlin-dsl` plugin.
+ * It converts all [org.gradle.api.Action] so the parameter is the receiver:
+ *
+ * ```
+ * // with SamWithReceiver ✅
+ * tasks.configureEach {
+ * val task: Task = this
+ * }
+ *
+ * // without SamWithReceiver
+ * tasks.configureEach { it ->
+ * val task: Task = it
+ * }
+ * ```
+ *
+ * This is nice because it means that the Dokka Gradle Plugin more closely matches `build.gradle.kts` files.
+ *
+ * However, [IntelliJ is bugged](https://youtrack.jetbrains.com/issue/KTIJ-14684) and doesn't
+ * acknowledge that `SamWithReceiver` has been applied in test sources. The code works and compiles,
+ * but IntelliJ shows red errors.
+ *
+ * These functions are workarounds, and should be removed ASAP.
+ */
+@Suppress("unused")
+private object Explain
+
+fun Project.subprojects_(configure: Project.() -> Unit) =
+ subprojects(configure)
+
+@Suppress("SpellCheckingInspection")
+fun Project.allprojects_(configure: Project.() -> Unit) =
+ allprojects(configure)
+
+fun <T> DomainObjectCollection<T>.configureEach_(configure: T.() -> Unit) =
+ configureEach(configure)
+
+fun <T> DomainObjectCollection<T>.all_(configure: T.() -> Unit) =
+ all(configure)
+
+fun Configuration.withDependencies_(action: DependencySet.() -> Unit): Configuration =
+ withDependencies(action)
+
+
+fun <T> NamedDomainObjectContainer<T>.create_(name: String, configure: T.() -> Unit): T =
+ create(name, configure)
+
+fun <T> NamedDomainObjectContainer<T>.register_(
+ name: String,
+ configure: T.() -> Unit
+): NamedDomainObjectProvider<T> =
+ register(name, configure)
+
+
+fun GradleDokkaSourceSetBuilder.externalDocumentationLink_(
+ action: GradleExternalDocumentationLinkBuilder.() -> Unit
+) = externalDocumentationLink(action)
diff --git a/dokka-runners/runner-maven-plugin/api/runner-maven-plugin.api b/dokka-runners/runner-maven-plugin/api/runner-maven-plugin.api
new file mode 100644
index 00000000..069e7744
--- /dev/null
+++ b/dokka-runners/runner-maven-plugin/api/runner-maven-plugin.api
@@ -0,0 +1,141 @@
+public abstract class org/jetbrains/dokka/maven/AbstractDokkaMojo : org/apache/maven/plugin/AbstractMojo {
+ public fun <init> (Ljava/util/List;)V
+ public fun execute ()V
+ public final fun getApiVersion ()Ljava/lang/String;
+ public final fun getCacheRoot ()Ljava/lang/String;
+ public final fun getClasspath ()Ljava/util/List;
+ public final fun getDisplayName ()Ljava/lang/String;
+ public final fun getDocumentedVisibilities ()Ljava/util/Set;
+ public final fun getDokkaPlugins ()Ljava/util/List;
+ public final fun getExternalDocumentationLinks ()Ljava/util/List;
+ public final fun getFailOnWarning ()Z
+ public final fun getIncludeNonPublic ()Z
+ public final fun getIncludes ()Ljava/util/List;
+ public final fun getJdkVersion ()I
+ public final fun getLanguageVersion ()Ljava/lang/String;
+ protected final fun getMavenProject ()Lorg/apache/maven/project/MavenProject;
+ public final fun getModuleName ()Ljava/lang/String;
+ public final fun getNoJdkLink ()Z
+ public final fun getNoStdlibLink ()Z
+ public final fun getOfflineMode ()Z
+ protected abstract fun getOutDir ()Ljava/lang/String;
+ public final fun getPerPackageOptions ()Ljava/util/List;
+ public final fun getPlatform ()Ljava/lang/String;
+ public final fun getReportUndocumented ()Z
+ public final fun getSamples ()Ljava/util/List;
+ protected final fun getSession ()Lorg/apache/maven/execution/MavenSession;
+ public final fun getSkip ()Z
+ public final fun getSkipDeprecated ()Z
+ public final fun getSkipEmptyPackages ()Z
+ public final fun getSourceDirectories ()Ljava/util/List;
+ public final fun getSourceLinks ()Ljava/util/List;
+ public final fun getSourceSetName ()Ljava/lang/String;
+ public final fun getSuppressInheritedMembers ()Z
+ public final fun getSuppressObviousFunctions ()Z
+ public final fun getSuppressedFiles ()Ljava/util/List;
+ public final fun setApiVersion (Ljava/lang/String;)V
+ public final fun setCacheRoot (Ljava/lang/String;)V
+ public final fun setClasspath (Ljava/util/List;)V
+ public final fun setDisplayName (Ljava/lang/String;)V
+ public final fun setDocumentedVisibilities (Ljava/util/Set;)V
+ public final fun setDokkaPlugins (Ljava/util/List;)V
+ public final fun setExternalDocumentationLinks (Ljava/util/List;)V
+ public final fun setFailOnWarning (Z)V
+ public final fun setIncludeNonPublic (Z)V
+ public final fun setIncludes (Ljava/util/List;)V
+ public final fun setJdkVersion (I)V
+ public final fun setLanguageVersion (Ljava/lang/String;)V
+ protected final fun setMavenProject (Lorg/apache/maven/project/MavenProject;)V
+ public final fun setModuleName (Ljava/lang/String;)V
+ public final fun setNoJdkLink (Z)V
+ public final fun setNoStdlibLink (Z)V
+ public final fun setOfflineMode (Z)V
+ public final fun setPerPackageOptions (Ljava/util/List;)V
+ public final fun setPlatform (Ljava/lang/String;)V
+ public final fun setReportUndocumented (Z)V
+ public final fun setSamples (Ljava/util/List;)V
+ protected final fun setSession (Lorg/apache/maven/execution/MavenSession;)V
+ public final fun setSkip (Z)V
+ public final fun setSkipDeprecated (Z)V
+ public final fun setSkipEmptyPackages (Z)V
+ public final fun setSourceDirectories (Ljava/util/List;)V
+ public final fun setSourceLinks (Ljava/util/List;)V
+ public final fun setSourceSetName (Ljava/lang/String;)V
+ public final fun setSuppressInheritedMembers (Z)V
+ public final fun setSuppressObviousFunctions (Z)V
+ public final fun setSuppressedFiles (Ljava/util/List;)V
+}
+
+public final class org/jetbrains/dokka/maven/DokkaJavadocJarMojo : org/jetbrains/dokka/maven/AbstractDokkaMojo {
+ public fun <init> ()V
+ public fun execute ()V
+ public final fun getOutputDir ()Ljava/lang/String;
+ public final fun setOutputDir (Ljava/lang/String;)V
+}
+
+public final class org/jetbrains/dokka/maven/DokkaJavadocMojo : org/jetbrains/dokka/maven/AbstractDokkaMojo {
+ public fun <init> ()V
+ public final fun getOutputDir ()Ljava/lang/String;
+ public final fun setOutputDir (Ljava/lang/String;)V
+}
+
+public final class org/jetbrains/dokka/maven/DokkaMojo : org/jetbrains/dokka/maven/AbstractDokkaMojo {
+ public fun <init> ()V
+ public final fun getOutputDir ()Ljava/lang/String;
+ public final fun setOutputDir (Ljava/lang/String;)V
+}
+
+public final class org/jetbrains/dokka/maven/ExternalDocumentationLinkBuilder {
+ public fun <init> ()V
+ public final fun build ()Lorg/jetbrains/dokka/ExternalDocumentationLinkImpl;
+ public final fun getPackageListUrl ()Ljava/net/URL;
+ public final fun getUrl ()Ljava/net/URL;
+ public final fun setPackageListUrl (Ljava/net/URL;)V
+ public final fun setUrl (Ljava/net/URL;)V
+}
+
+public class org/jetbrains/dokka/maven/HelpMojo : org/apache/maven/plugin/AbstractMojo {
+ public fun <init> ()V
+ public fun execute ()V
+}
+
+public final class org/jetbrains/dokka/maven/MavenDokkaLogger : org/jetbrains/dokka/utilities/DokkaLogger {
+ public fun <init> (Lorg/apache/maven/plugin/logging/Log;)V
+ public fun debug (Ljava/lang/String;)V
+ public fun error (Ljava/lang/String;)V
+ public fun getErrorsCount ()I
+ public final fun getLog ()Lorg/apache/maven/plugin/logging/Log;
+ public fun getWarningsCount ()I
+ public fun info (Ljava/lang/String;)V
+ public fun progress (Ljava/lang/String;)V
+ public fun setErrorsCount (I)V
+ public fun setWarningsCount (I)V
+ public fun warn (Ljava/lang/String;)V
+}
+
+public final class org/jetbrains/dokka/maven/PackageOptions : org/jetbrains/dokka/DokkaConfiguration$PackageOptions {
+ public fun <init> ()V
+ public fun getDocumentedVisibilities ()Ljava/util/Set;
+ public fun getIncludeNonPublic ()Z
+ public fun getMatchingRegex ()Ljava/lang/String;
+ public fun getReportUndocumented ()Ljava/lang/Boolean;
+ public fun getSkipDeprecated ()Z
+ public fun getSuppress ()Z
+ public fun setDocumentedVisibilities (Ljava/util/Set;)V
+ public fun setIncludeNonPublic (Z)V
+ public fun setMatchingRegex (Ljava/lang/String;)V
+ public fun setReportUndocumented (Z)V
+ public fun setSkipDeprecated (Z)V
+ public fun setSuppress (Z)V
+}
+
+public final class org/jetbrains/dokka/maven/SourceLinkMapItem {
+ public fun <init> ()V
+ public final fun getLineSuffix ()Ljava/lang/String;
+ public final fun getPath ()Ljava/lang/String;
+ public final fun getUrl ()Ljava/lang/String;
+ public final fun setLineSuffix (Ljava/lang/String;)V
+ public final fun setPath (Ljava/lang/String;)V
+ public final fun setUrl (Ljava/lang/String;)V
+}
+
diff --git a/dokka-runners/runner-maven-plugin/build.gradle.kts b/dokka-runners/runner-maven-plugin/build.gradle.kts
new file mode 100644
index 00000000..f0de8dd5
--- /dev/null
+++ b/dokka-runners/runner-maven-plugin/build.gradle.kts
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import dokkabuild.overridePublicationArtifactId
+
+plugins {
+ id("dokkabuild.kotlin-jvm")
+ id("dokkabuild.publish-jvm")
+ id("dokkabuild.setup-maven-cli")
+ alias(libs.plugins.kotlinx.binaryCompatibilityValidator)
+}
+
+overridePublicationArtifactId("dokka-maven-plugin")
+
+dependencies {
+ // this version is required, so that it will be available in the POM of plugin
+ implementation("org.jetbrains.dokka:dokka-core:$version")
+
+ implementation(libs.apacheMaven.core)
+ implementation(libs.apacheMaven.pluginApi)
+ implementation(libs.apacheMaven.pluginAnnotations)
+ implementation(libs.apacheMaven.archiver)
+}
+
+val mavenPluginTaskGroup = "maven plugin"
+
+val generatePom by tasks.registering(Sync::class) {
+ description = "Generate pom.xml for Maven Plugin Plugin"
+ group = mavenPluginTaskGroup
+
+ inputs.property("dokka_version", project.version)
+
+ val pomTemplateFile = layout.projectDirectory.file("pom.template.xml")
+
+ val mavenVersion = mavenCliSetup.mavenVersion.orNull
+ val mavenPluginToolsVersion = mavenCliSetup.mavenPluginToolsVersion.orNull
+
+ from(pomTemplateFile) {
+ rename { it.replace(".template.xml", ".xml") }
+
+ expand(
+ "mavenVersion" to mavenVersion,
+ "dokka_version" to project.version,
+ "mavenPluginToolsVersion" to mavenPluginToolsVersion,
+ )
+ }
+
+ into(temporaryDir)
+}
+
+val prepareHelpMojoDir by tasks.registering(Sync::class) {
+ description = "Prepare files for generating the Maven Plugin HelpMojo"
+ group = mavenPluginTaskGroup
+
+ into(layout.buildDirectory.dir("maven-help-mojo"))
+ from(generatePom)
+}
+
+val helpMojo by tasks.registering(Exec::class) {
+ description = "Generate the Maven Plugin HelpMojo"
+ group = mavenPluginTaskGroup
+
+ dependsOn(tasks.installMavenBinary, prepareHelpMojoDir)
+
+ workingDir(prepareHelpMojoDir.map { it.destinationDir })
+ executable(mavenCliSetup.mvn.get())
+ args("-e", "-B", "org.apache.maven.plugins:maven-plugin-plugin:helpmojo")
+
+ outputs.dir(workingDir)
+}
+
+val helpMojoSources by tasks.registering(Sync::class) {
+ description = "Sync the HelpMojo source files into a SourceSet SrcDir"
+ group = mavenPluginTaskGroup
+ from(helpMojo) {
+ eachFile {
+ // drop 2 leading directories
+ relativePath = RelativePath(true, *relativePath.segments.drop(2).toTypedArray())
+ }
+ }
+ includeEmptyDirs = false
+ into(temporaryDir)
+ include("**/*.java")
+}
+
+val helpMojoResources by tasks.registering(Sync::class) {
+ description = "Sync the HelpMojo resource files into a SourceSet SrcDir"
+ group = mavenPluginTaskGroup
+ from(helpMojo)
+ into(temporaryDir)
+ include("**/**")
+ exclude("**/*.java")
+}
+
+sourceSets.main {
+ // use the generated HelpMojo as compilation input, so Gradle will automatically generate the mojo
+ java.srcDirs(helpMojoSources)
+ resources.srcDirs(helpMojoResources)
+}
+
+val preparePluginDescriptorDir by tasks.registering(Sync::class) {
+ description = "Prepare files for generating the Maven Plugin descriptor"
+ group = mavenPluginTaskGroup
+
+ into(layout.buildDirectory.dir("maven-plugin-descriptor"))
+
+ from(tasks.compileKotlin) { into("classes/java/main") }
+ from(tasks.compileJava) { into("classes/java/main") }
+ from(helpMojoResources)
+}
+
+val pluginDescriptor by tasks.registering(Exec::class) {
+ description = "Generate the Maven Plugin descriptor"
+ group = mavenPluginTaskGroup
+
+ dependsOn(tasks.installMavenBinary, preparePluginDescriptorDir)
+
+ workingDir(preparePluginDescriptorDir.map { it.destinationDir })
+ executable(mavenCliSetup.mvn.get())
+ args("-e", "-B", "org.apache.maven.plugins:maven-plugin-plugin:descriptor")
+
+ outputs.dir("$workingDir/classes/java/main/META-INF/maven")
+}
+
+tasks.jar {
+ metaInf {
+ from(pluginDescriptor) {
+ into("maven")
+ }
+ }
+ manifest {
+ attributes("Class-Path" to configurations.runtimeClasspath.map { configuration ->
+ configuration.resolve().joinToString(" ") { it.name }
+ })
+ }
+}
diff --git a/dokka-runners/runner-maven-plugin/gradle.properties b/dokka-runners/runner-maven-plugin/gradle.properties
new file mode 100644
index 00000000..5715ee14
--- /dev/null
+++ b/dokka-runners/runner-maven-plugin/gradle.properties
@@ -0,0 +1,9 @@
+#
+# Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+group=org.jetbrains.dokka
+version=1.9.20-SNAPSHOT
+
+org.jetbrains.dokka.javaToolchain.mainCompiler=8
+org.jetbrains.dokka.javaToolchain.testLauncher=8
+org.jetbrains.dokka.kotlinLanguageLevel=1.4
diff --git a/dokka-runners/runner-maven-plugin/pom.template.xml b/dokka-runners/runner-maven-plugin/pom.template.xml
new file mode 100644
index 00000000..b4795112
--- /dev/null
+++ b/dokka-runners/runner-maven-plugin/pom.template.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.jetbrains.dokka</groupId>
+ <artifactId>dokka-maven-plugin</artifactId>
+ <version>${dokka_version}</version>
+ <packaging>maven-plugin</packaging>
+ <properties>
+ <maven.version>${mavenVersion}</maven.version>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-plugin-plugin</artifactId>
+ <version>${mavenPluginToolsVersion}</version>
+ <configuration>
+ <helpPackageName>org.jetbrains.dokka.maven</helpPackageName>
+ <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
+ </configuration>
+ <executions>
+ <execution>
+ <id>default-descriptor</id>
+ <goals>
+ <goal>descriptor</goal>
+ </goals>
+ <phase>process-classes</phase>
+ </execution>
+ <execution>
+ <id>help-descriptor</id>
+ <goals>
+ <goal>helpmojo</goal>
+ </goals>
+ <phase>process-classes</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <directory>./</directory>
+ <outputDirectory>./classes/java/main</outputDirectory>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin-tools</groupId>
+ <artifactId>maven-plugin-annotations</artifactId>
+ <version>${mavenPluginToolsVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/dokka-runners/runner-maven-plugin/settings.gradle.kts b/dokka-runners/runner-maven-plugin/settings.gradle.kts
new file mode 100644
index 00000000..63bf3743
--- /dev/null
+++ b/dokka-runners/runner-maven-plugin/settings.gradle.kts
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+rootProject.name = "runner-maven-plugin"
+
+pluginManagement {
+ includeBuild("../../build-logic")
+
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ }
+
+ versionCatalogs {
+ create("libs") {
+ from(files("../../gradle/libs.versions.toml"))
+ }
+ }
+}
+
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
diff --git a/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/DokkaMojo.kt b/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/DokkaMojo.kt
new file mode 100644
index 00000000..d14fea9c
--- /dev/null
+++ b/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/DokkaMojo.kt
@@ -0,0 +1,615 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.maven
+
+import org.apache.maven.archiver.MavenArchiveConfiguration
+import org.apache.maven.archiver.MavenArchiver
+import org.apache.maven.artifact.DefaultArtifact
+import org.apache.maven.artifact.handler.DefaultArtifactHandler
+import org.apache.maven.artifact.resolver.ArtifactResolutionRequest
+import org.apache.maven.artifact.resolver.ArtifactResolutionResult
+import org.apache.maven.artifact.resolver.ResolutionErrorHandler
+import org.apache.maven.execution.MavenSession
+import org.apache.maven.model.Dependency
+import org.apache.maven.plugin.AbstractMojo
+import org.apache.maven.plugin.MojoExecutionException
+import org.apache.maven.plugins.annotations.*
+import org.apache.maven.project.MavenProject
+import org.apache.maven.project.MavenProjectHelper
+import org.apache.maven.repository.RepositorySystem
+import org.codehaus.plexus.archiver.Archiver
+import org.codehaus.plexus.archiver.jar.JarArchiver
+import org.codehaus.plexus.archiver.util.DefaultFileSet
+import org.codehaus.plexus.util.xml.Xpp3Dom
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink
+import java.io.File
+import java.net.URL
+
+public abstract class AbstractDokkaMojo(
+ private val defaultDokkaPlugins: List<Dependency>
+) : AbstractMojo() {
+
+ @Parameter(defaultValue = "\${project}", readonly = true, required = true)
+ protected var mavenProject: MavenProject? = null
+
+ /**
+ * The current build session instance. This is used for
+ * dependency resolver API calls via repositorySystem.
+ */
+ @Parameter(defaultValue = "\${session}", required = true, readonly = true)
+ protected var session: MavenSession? = null
+
+ @Component
+ private var repositorySystem: RepositorySystem? = null
+
+ @Component
+ private var resolutionErrorHandler: ResolutionErrorHandler? = null
+
+ @Parameter(defaultValue = "JVM")
+ public var displayName: String = "JVM"
+
+ @Parameter
+ public var sourceSetName: String = "JVM"
+
+ /**
+ * Source code roots to be analyzed and documented.
+ * Accepts directories and individual `.kt` / `.java` files.
+ *
+ * Default is `{project.compileSourceRoots}`.
+ */
+ @Parameter(required = true, defaultValue = "\${project.compileSourceRoots}")
+ public var sourceDirectories: List<String> = emptyList()
+
+ /**
+ * List of directories or files that contain sample functions which are referenced via
+ * [@sample](https://kotlinlang.org/docs/kotlin-doc.html#sample-identifier) KDoc tag.
+ */
+ @Parameter
+ public var samples: List<String> = emptyList()
+
+ /**
+ * List of Markdown files that contain
+ * [module and package documentation](https://kotlinlang.org/docs/dokka-module-and-package-docs.html).
+ *
+ * Contents of specified files will be parsed and embedded into documentation as module and package descriptions.
+ *
+ * Example of such a file:
+ *
+ * ```markdown
+ * # Module kotlin-demo
+ *
+ * The module shows the Dokka usage.
+ *
+ * # Package org.jetbrains.kotlin.demo
+ *
+ * Contains assorted useful stuff.
+ *
+ * ## Level 2 heading
+ *
+ * Text after this heading is also part of documentation for `org.jetbrains.kotlin.demo`
+ *
+ * # Package org.jetbrains.kotlin.demo2
+ *
+ * Useful stuff in another package.
+ * ```
+ */
+ @Parameter
+ public var includes: List<String> = emptyList()
+
+ /**
+ * Classpath for analysis and interactive samples.
+ *
+ * Useful if some types that come from dependencies are not resolved/picked up automatically.
+ * Property accepts both `.jar` and `.klib` files.
+ *
+ * Default is `{project.compileClasspathElements}`.
+ */
+ @Parameter(required = true, defaultValue = "\${project.compileClasspathElements}")
+ public var classpath: List<String> = emptyList()
+
+ /**
+ * Specifies the location of the project source code on the Web. If provided, Dokka generates
+ * "source" links for each declaration. See [SourceLinkMapItem] for more details.
+ */
+ @Parameter
+ public var sourceLinks: List<SourceLinkMapItem> = emptyList()
+
+ /**
+ * Display name used to refer to the project/module. Used for ToC, navigation, logging, etc.
+ *
+ * Default is `{project.artifactId}`.
+ */
+ @Parameter(required = true, defaultValue = "\${project.artifactId}")
+ public var moduleName: String = ""
+
+ /**
+ * Whether to skip documentation generation.
+ *
+ * Default is `false`.
+ */
+ @Parameter(required = false, defaultValue = "false")
+ public var skip: Boolean = false
+
+ /**
+ * JDK version to use when generating external documentation links for Java types.
+ *
+ * For instance, if you use [java.util.UUID] from JDK in some public declaration signature,
+ * and this property is set to `8`, Dokka will generate an external documentation link
+ * to [JDK 8 Javadocs](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) for it.
+ *
+ * Default is JDK 8.
+ */
+ @Parameter(required = false, defaultValue = "${DokkaDefaults.jdkVersion}")
+ public var jdkVersion: Int = DokkaDefaults.jdkVersion
+
+ /**
+ * Whether to document declarations annotated with [Deprecated].
+ *
+ * Can be overridden on package level by setting [PackageOptions.skipDeprecated].
+ *
+ * Default is `false`.
+ */
+ @Parameter
+ public var skipDeprecated: Boolean = DokkaDefaults.skipDeprecated
+
+ /**
+ * Whether to skip packages that contain no visible declarations after
+ * various filters have been applied.
+ *
+ * For instance, if [skipDeprecated] is set to `true` and your package contains only
+ * deprecated declarations, it will be considered to be empty.
+ *
+ * Default is `true`.
+ */
+ @Parameter
+ public var skipEmptyPackages: Boolean = DokkaDefaults.skipEmptyPackages
+
+ /**
+ * Whether to emit warnings about visible undocumented declarations, that is declarations without KDocs
+ * after they have been filtered by [documentedVisibilities].
+ *
+ * This setting works well with [failOnWarning].
+ *
+ * Can be overridden for a specific package by setting [PackageOptions.reportUndocumented].
+ *
+ * Default is `false`.
+ */
+ @Parameter
+ public var reportUndocumented: Boolean = DokkaDefaults.reportUndocumented
+
+ /**
+ * Allows to customize documentation generation options on a per-package basis.
+ *
+ * @see PackageOptions for details
+ */
+ @Parameter
+ public var perPackageOptions: List<PackageOptions> = emptyList()
+
+ /**
+ * Allows linking to Dokka/Javadoc documentation of the project's dependencies.
+ *
+ * @see ExternalDocumentationLinkBuilder for details
+ */
+ @Parameter
+ public var externalDocumentationLinks: List<ExternalDocumentationLinkBuilder> = emptyList()
+
+ /**
+ * Whether to generate external documentation links that lead to API reference
+ * documentation for Kotlin's standard library when declarations from it are used.
+ *
+ * Default is `false`, meaning links will be generated.
+ */
+ @Parameter(defaultValue = "${DokkaDefaults.noStdlibLink}")
+ public var noStdlibLink: Boolean = DokkaDefaults.noStdlibLink
+
+ /**
+ * Whether to generate external documentation links to JDK's Javadocs
+ * when declarations from it are used.
+ *
+ * The version of JDK Javadocs is determined by [jdkVersion] property.
+ *
+ * Default is `false`, meaning links will be generated.
+ */
+ @Parameter(defaultValue = "${DokkaDefaults.noJdkLink}")
+ public var noJdkLink: Boolean = DokkaDefaults.noJdkLink
+
+ /**
+ * Whether to resolve remote files/links over network.
+ *
+ * This includes package-lists used for generating external documentation links:
+ * for instance, to make classes from standard library clickable.
+ *
+ * Setting this to `true` can significantly speed up build times in certain cases,
+ * but can also worsen documentation quality and user experience, for instance by
+ * not resolving some dependency's class/member links.
+ *
+ * When using offline mode, you can cache fetched files locally and provide them to
+ * Dokka as local paths. For instance, see [ExternalDocumentationLinkBuilder].
+ *
+ * Default is `false`.
+ */
+ @Parameter(defaultValue = "${DokkaDefaults.offlineMode}")
+ public var offlineMode: Boolean = DokkaDefaults.offlineMode
+
+ /**
+ * [Kotlin language version](https://kotlinlang.org/docs/compatibility-modes.html)
+ * used for setting up analysis and [@sample](https://kotlinlang.org/docs/kotlin-doc.html#sample-identifier)
+ * environment.
+ *
+ * By default, the latest language version available to Dokka's embedded compiler will be used.
+ */
+ @Parameter
+ public var languageVersion: String? = null
+
+ /**
+ * [Kotlin API version](https://kotlinlang.org/docs/compatibility-modes.html)
+ * used for setting up analysis and [@sample](https://kotlinlang.org/docs/kotlin-doc.html#sample-identifier)
+ * environment.
+ *
+ * By default, it will be deduced from [languageVersion].
+ */
+ @Parameter
+ public var apiVersion: String? = null
+
+ /**
+ * Directories or individual files that should be suppressed, meaning declarations from them
+ * will be not documented.
+ */
+ @Parameter
+ public var suppressedFiles: List<String> = emptyList()
+
+ /**
+ * Set of visibility modifiers that should be documented.
+ *
+ * This can be used if you want to document protected/internal/private declarations,
+ * as well as if you want to exclude public declarations and only document internal API.
+ *
+ * Can be configured on per-package basis, see [PackageOptions.documentedVisibilities].
+ *
+ * Default is [DokkaConfiguration.Visibility.PUBLIC].
+ */
+ @Parameter(property = "visibility")
+ public var documentedVisibilities: Set<DokkaConfiguration.Visibility> = DokkaDefaults.documentedVisibilities
+ // hack to set the default value for lists, didn't find any other safe way
+ // maven seems to overwrite Kotlin's default initialization value, so it doesn't matter what you put there
+ get() = field.ifEmpty { DokkaDefaults.documentedVisibilities }
+
+ /**
+ * Whether to fail documentation generation if Dokka has emitted a warning or an error.
+ * Will wait until all errors and warnings have been emitted first.
+ *
+ * This setting works well with [reportUndocumented]
+ *
+ * Default is `false`.
+ */
+ @Parameter
+ public var failOnWarning: Boolean = DokkaDefaults.failOnWarning
+
+ /**
+ * Whether to suppress obvious functions.
+ *
+ * A function is considered to be obvious if it is:
+ * - Inherited from `kotlin.Any`, `Kotlin.Enum`, `java.lang.Object` or `java.lang.Enum`,
+ * such as `equals`, `hashCode`, `toString`.
+ * - Synthetic (generated by the compiler) and does not have any documentation, such as
+ * `dataClass.componentN` or `dataClass.copy`.
+ *
+ * Default is `true`
+ */
+ @Parameter(defaultValue = "${DokkaDefaults.suppressObviousFunctions}")
+ public var suppressObviousFunctions: Boolean = DokkaDefaults.suppressObviousFunctions
+
+ /**
+ * Whether to suppress inherited members that aren't explicitly overridden in a given class.
+ *
+ * Note: this can suppress functions such as `equals`/`hashCode`/`toString`, but cannot suppress
+ * synthetic functions such as `dataClass.componentN` and `dataClass.copy`. Use [suppressObviousFunctions]
+ * for that.
+ *
+ * Default is `false`.
+ */
+ @Parameter(defaultValue = "${DokkaDefaults.suppressInheritedMembers}")
+ public var suppressInheritedMembers: Boolean = DokkaDefaults.suppressInheritedMembers
+
+ /**
+ * Dokka plugins to be using during documentation generation.
+ *
+ * Example:
+ *
+ * ```xml
+ * <dokkaPlugins>
+ * <plugin>
+ * <groupId>org.jetbrains.dokka</groupId>
+ * <artifactId>gfm-plugin</artifactId>
+ * <version>1.7.20</version>
+ * </plugin>
+ * </dokkaPlugins>
+ * ```
+ */
+ @Parameter
+ public var dokkaPlugins: List<Dependency> = emptyList()
+ get() = field + defaultDokkaPlugins
+
+ @Parameter
+ public var cacheRoot: String? = null
+
+ @Parameter
+ public var platform: String = ""
+
+ /**
+ * Deprecated. Use [documentedVisibilities] instead.
+ */
+ @Parameter
+ public var includeNonPublic: Boolean = DokkaDefaults.includeNonPublic
+
+ protected abstract fun getOutDir(): String
+
+ override fun execute() {
+ if (skip) {
+ log.info("Dokka skip parameter is true so no dokka output will be produced")
+ return
+ }
+
+ sourceLinks.forEach {
+ if (it.path.contains("\\")) {
+ throw MojoExecutionException("Incorrect path property, only Unix based path allowed.")
+ }
+ }
+
+ if (moduleName.contains(',')) {
+ // To figure out why this is needed and if it is still relevant, see the comment here:
+ // https://github.com/Kotlin/dokka/issues/3011#issuecomment-1568620493
+ throw IllegalArgumentException("Module name cannot contain commas as it is used internally as a delimiter.")
+ }
+
+ fun defaultLinks(config: DokkaSourceSetImpl): Set<ExternalDocumentationLinkImpl> {
+ val links = mutableSetOf<ExternalDocumentationLinkImpl>()
+ if (!config.noJdkLink)
+ links += ExternalDocumentationLink.jdk(jdkVersion)
+
+ if (!config.noStdlibLink)
+ links += ExternalDocumentationLink.kotlinStdlib()
+ return links
+ }
+
+ val sourceSet = DokkaSourceSetImpl(
+ displayName = displayName,
+ sourceSetID = DokkaSourceSetID(moduleName, sourceSetName),
+ classpath = classpath.map(::File),
+ sourceRoots = sourceDirectories.map(::File).toSet(),
+ dependentSourceSets = emptySet(),
+ samples = samples.map(::File).toSet(),
+ includes = includes.map(::File).toSet(),
+ includeNonPublic = includeNonPublic,
+ documentedVisibilities = documentedVisibilities,
+ reportUndocumented = reportUndocumented,
+ skipEmptyPackages = skipEmptyPackages,
+ skipDeprecated = skipDeprecated,
+ jdkVersion = jdkVersion,
+ sourceLinks = sourceLinks.map { SourceLinkDefinitionImpl(File(it.path).canonicalPath, URL(it.url), it.lineSuffix) }.toSet(),
+ perPackageOptions = perPackageOptions.map {
+ @Suppress("DEPRECATION") // for includeNonPublic, preserve backwards compatibility
+ PackageOptionsImpl(
+ matchingRegex = it.matchingRegex,
+ includeNonPublic = it.includeNonPublic,
+ documentedVisibilities = it.documentedVisibilities,
+ reportUndocumented = it.reportUndocumented,
+ skipDeprecated = it.skipDeprecated,
+ suppress = it.suppress
+ )
+ },
+ externalDocumentationLinks = externalDocumentationLinks.map { it.build() }.toSet(),
+ languageVersion = languageVersion,
+ apiVersion = apiVersion,
+ noStdlibLink = noStdlibLink,
+ noJdkLink = noJdkLink,
+ suppressedFiles = suppressedFiles.map(::File).toSet(),
+ analysisPlatform = if (platform.isNotEmpty()) Platform.fromString(platform) else Platform.DEFAULT,
+ ).let {
+ it.copy(
+ externalDocumentationLinks = defaultLinks(it) + it.externalDocumentationLinks
+ )
+ }
+
+ val logger = MavenDokkaLogger(log)
+
+ val pluginsConfiguration =
+ (mavenProject?.getPlugin("org.jetbrains.dokka:dokka-maven-plugin")?.configuration as? Xpp3Dom)
+ ?.getChild("pluginsConfiguration")?.children?.map {
+ PluginConfigurationImpl(
+ it.name,
+ DokkaConfiguration.SerializationFormat.XML,
+ it.toString()
+ )
+ }.orEmpty()
+
+ val configuration = DokkaConfigurationImpl(
+ moduleName = moduleName,
+ outputDir = File(getOutDir()),
+ offlineMode = offlineMode,
+ cacheRoot = cacheRoot?.let(::File),
+ sourceSets = listOf(sourceSet),
+ // TODO [beresnev] analysis switcher
+ pluginsClasspath = getArtifactByMaven("org.jetbrains.dokka", "analysis-kotlin-descriptors", dokkaVersion) +
+ getArtifactByMaven("org.jetbrains.dokka", "dokka-base", dokkaVersion) +
+ dokkaPlugins.map { getArtifactByMaven(it.groupId, it.artifactId, it.version ?: dokkaVersion) }
+ .flatten(),
+ pluginsConfiguration = pluginsConfiguration.toMutableList(),
+ modules = emptyList(),
+ failOnWarning = failOnWarning,
+ suppressObviousFunctions = suppressObviousFunctions,
+ suppressInheritedMembers = suppressInheritedMembers,
+ // looks like maven has different life cycle compared to gradle,
+ // so finalizing coroutines after each module pass causes an error.
+ // see https://github.com/Kotlin/dokka/issues/2457
+ finalizeCoroutines = false,
+ )
+
+ val gen = DokkaGenerator(configuration, logger)
+
+ gen.generate()
+ }
+
+ private fun getArtifactByMaven(
+ groupId: String,
+ artifactId: String,
+ version: String
+ ): List<File> {
+
+ val request = ArtifactResolutionRequest().apply {
+ isResolveRoot = true
+ isResolveTransitively = true
+ localRepository = session!!.localRepository
+ remoteRepositories = mavenProject!!.pluginArtifactRepositories
+ isOffline = session!!.isOffline
+ isForceUpdate = session!!.request.isUpdateSnapshots
+ servers = session!!.request.servers
+ mirrors = session!!.request.mirrors
+ proxies = session!!.request.proxies
+ artifact = DefaultArtifact(
+ groupId, artifactId, version, "compile", "jar", null,
+ DefaultArtifactHandler("jar")
+ )
+ }
+
+ log.debug("Resolving $groupId:$artifactId:$version ...")
+
+ val result: ArtifactResolutionResult = repositorySystem!!.resolve(request)
+ resolutionErrorHandler!!.throwErrors(request, result)
+ return result.artifacts.map { it.file }
+ }
+
+ private val dokkaVersion: String by lazy {
+ mavenProject?.pluginArtifacts?.firstOrNull { it.groupId == "org.jetbrains.dokka" && it.artifactId == "dokka-maven-plugin" }?.version
+ ?: throw IllegalStateException("Not found dokka plugin")
+ }
+}
+
+@Mojo(
+ name = "dokka",
+ defaultPhase = LifecyclePhase.PRE_SITE,
+ threadSafe = true,
+ requiresDependencyResolution = ResolutionScope.COMPILE,
+ requiresProject = true
+)
+public class DokkaMojo : AbstractDokkaMojo(emptyList()) {
+
+ /**
+ * Directory to which documentation will be generated.
+ *
+ * Default is `{project.basedir}/target/dokka`.
+ */
+ @Parameter(required = true, defaultValue = "\${project.basedir}/target/dokka")
+ public var outputDir: String = ""
+
+ override fun getOutDir(): String = outputDir
+}
+
+@Mojo(
+ name = "javadoc",
+ defaultPhase = LifecyclePhase.PRE_SITE,
+ threadSafe = true,
+ requiresDependencyResolution = ResolutionScope.COMPILE,
+ requiresProject = true
+)
+public class DokkaJavadocMojo : AbstractDokkaMojo(listOf(javadocDependency)) {
+
+ /**
+ * Directory to which documentation will be generated.
+ *
+ * Default is `{project.basedir}/target/dokkaJavadoc`.
+ */
+ @Parameter(required = true, defaultValue = "\${project.basedir}/target/dokkaJavadoc")
+ public var outputDir: String = ""
+
+ override fun getOutDir(): String = outputDir
+}
+
+@Mojo(
+ name = "javadocJar",
+ defaultPhase = LifecyclePhase.PRE_SITE,
+ threadSafe = true,
+ requiresDependencyResolution = ResolutionScope.COMPILE,
+ requiresProject = true
+)
+public class DokkaJavadocJarMojo : AbstractDokkaMojo(listOf(javadocDependency)) {
+
+ /**
+ * Directory to which documentation jar will be generated.
+ *
+ * Default is `{project.basedir}/target/dokkaJavadocJar`.
+ */
+ @Parameter(required = true, defaultValue = "\${project.basedir}/target/dokkaJavadocJar")
+ public var outputDir: String = ""
+
+ /**
+ * Specifies the directory where the generated jar file will be put.
+ */
+ @Parameter(property = "project.build.directory")
+ private var jarOutputDirectory: String? = null
+
+ /**
+ * Specifies the filename that will be used for the generated jar file. Please note that `-javadoc`
+ * or `-test-javadoc` will be appended to the file name.
+ */
+ @Parameter(property = "project.build.finalName")
+ private var finalName: String? = null
+
+ /**
+ * Specifies whether to attach the generated artifact to the project helper.
+ */
+ @Parameter(property = "attach", defaultValue = "true")
+ private val attach: Boolean = false
+
+ /**
+ * The archive configuration to use.
+ * See [Maven Archiver Reference](https://maven.apache.org/shared/maven-archiver/index.html)
+ */
+ @Parameter
+ private val archive = MavenArchiveConfiguration()
+
+ @Parameter(property = "maven.javadoc.classifier", defaultValue = "javadoc", required = true)
+ private var classifier: String? = null
+
+ @Component
+ private var projectHelper: MavenProjectHelper? = null
+
+ @Component(role = Archiver::class, hint = "jar")
+ private var jarArchiver: JarArchiver? = null
+
+ override fun getOutDir(): String = outputDir
+
+ override fun execute() {
+ super.execute()
+ if (!File(outputDir).exists()) {
+ log.warn("No javadoc generated so no javadoc jar will be generated")
+ return
+ }
+ val outputFile = generateArchive("$finalName-$classifier.jar")
+ if (attach) {
+ projectHelper?.attachArtifact(mavenProject, "javadoc", classifier, outputFile)
+ }
+ }
+
+ private fun generateArchive(jarFileName: String): File {
+ val javadocJar = File(jarOutputDirectory, jarFileName)
+
+ val archiver = MavenArchiver()
+ archiver.archiver = jarArchiver
+ archiver.setOutputFile(javadocJar)
+ archiver.archiver.addFileSet(DefaultFileSet().apply { directory = File(outputDir) })
+
+ archive.isAddMavenDescriptor = false
+ archiver.createArchive(session, mavenProject, archive)
+
+ return javadocJar
+ }
+}
+
+private val javadocDependency = Dependency().apply {
+ groupId = "org.jetbrains.dokka"
+ artifactId = "javadoc-plugin"
+}
diff --git a/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/ExternalDocumentationLinkBuilder.kt b/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/ExternalDocumentationLinkBuilder.kt
new file mode 100644
index 00000000..5eb4f269
--- /dev/null
+++ b/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/ExternalDocumentationLinkBuilder.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.maven
+
+import org.apache.maven.plugins.annotations.Parameter
+import org.jetbrains.dokka.ExternalDocumentationLink
+import org.jetbrains.dokka.ExternalDocumentationLinkImpl
+import java.net.URL
+
+/**
+ * Configuration block that allows creating links leading to externally hosted
+ * documentation of your dependencies.
+ *
+ * For instance, if you are using types from `kotlinx.serialization`, by default
+ * they will be unclickable in your documentation, as if unresolved. However,
+ * since API reference for `kotlinx.serialization` is also built by Dokka and is
+ * [published on kotlinlang.org](https://kotlinlang.org/api/kotlinx.serialization/),
+ * you can configure external documentation links for it, allowing Dokka to generate
+ * documentation links for used types, making them clickable and appear resolved.
+ *
+ * Example:
+ *
+ * ```xml
+ * <externalDocumentationLinks>
+ * <link>
+ * <url>https://kotlinlang.org/api/latest/jvm/stdlib/</url>
+ * <packageListUrl>file:/${project.basedir}/stdlib.package.list</packageListUrl>
+ * </link>
+ * </externalDocumentationLinks>
+ * ```
+ */
+public class ExternalDocumentationLinkBuilder {
+
+ /**
+ * Root URL of documentation to link with. **Must** contain a trailing slash.
+ *
+ * Dokka will do its best to automatically find `package-list` for the given URL, and link
+ * declarations together.
+ *
+ * It automatic resolution fails or if you want to use locally cached files instead,
+ * consider providing [packageListUrl].
+ *
+ * Example:
+ *
+ * ```xml
+ * <url>https://kotlinlang.org/api/latest/jvm/stdlib/</url>
+ * ```
+ */
+ @Parameter(name = "url", required = true)
+ public var url: URL? = null
+
+ /**
+ * Specifies the exact location of a `package-list` instead of relying on Dokka
+ * automatically resolving it. Can also be a locally cached file to avoid network calls.
+ *
+ * Example:
+ *
+ * ```xml
+ * <packageListUrl>file:/${project.basedir}/stdlib.package.list</packageListUrl>
+ * ```
+ */
+ @Parameter(name = "packageListUrl", required = true)
+ public var packageListUrl: URL? = null
+
+ public fun build(): ExternalDocumentationLinkImpl = ExternalDocumentationLink(url, packageListUrl)
+}
diff --git a/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/MavenDokkaLogger.kt b/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/MavenDokkaLogger.kt
new file mode 100644
index 00000000..1bc39d10
--- /dev/null
+++ b/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/MavenDokkaLogger.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.maven
+
+import org.apache.maven.plugin.logging.Log
+import org.jetbrains.dokka.utilities.DokkaLogger
+import java.util.concurrent.atomic.AtomicInteger
+
+public class MavenDokkaLogger(
+ public val log: Log
+) : DokkaLogger {
+ private val warningsCounter = AtomicInteger()
+ private val errorsCounter = AtomicInteger()
+
+ override var warningsCount: Int
+ get() = warningsCounter.get()
+ set(value) = warningsCounter.set(value)
+
+ override var errorsCount: Int
+ get() = errorsCounter.get()
+ set(value) = errorsCounter.set(value)
+
+ override fun debug(message: String) {
+ log.debug(message)
+ }
+
+ override fun info(message: String) {
+ log.info(message)
+ }
+
+ override fun progress(message: String) {
+ log.info(message)
+ }
+
+ override fun warn(message: String) {
+ this.log.warn(message).also { warningsCounter.incrementAndGet() }
+ }
+
+ override fun error(message: String) {
+ log.error(message).also { errorsCounter.incrementAndGet() }
+ }
+}
diff --git a/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/PackageOptions.kt b/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/PackageOptions.kt
new file mode 100644
index 00000000..875fb047
--- /dev/null
+++ b/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/PackageOptions.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.maven
+
+import org.apache.maven.plugins.annotations.Parameter
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaDefaults
+
+/**
+ * Configuration block that allows setting some options for specific packages
+ * matched by [matchingRegex].
+ *
+ * Example:
+ *
+ * ```xml
+ * <configuration>
+ * <perPackageOptions>
+ * <packageOptions>
+ * <matchingRegex>.*api.*</matchingRegex>
+ * <suppress>false</suppress>
+ * <reportUndocumented>false</reportUndocumented>
+ * <skipDeprecated>false</skipDeprecated>
+ * <documentedVisibilities>
+ * <visibility>PUBLIC</visibility>
+ * <visibility>PROTECTED</visibility>
+ * </documentedVisibilities>
+ * </packageOptions>
+ * </perPackageOptions>
+ * </configuration>
+ * ```
+ */
+public class PackageOptions : DokkaConfiguration.PackageOptions {
+
+ /**
+ * Regular expression that is used to match the package.
+ *
+ * If multiple packages match the same `matchingRegex`, the longest `matchingRegex` will be used.
+ *
+ * Default is any string: `.*`.
+ */
+ @Parameter
+ override var matchingRegex: String = ".*"
+
+ /**
+ * Whether this package should be skipped when generating documentation.
+ *
+ * Default is `false`.
+ */
+ @Parameter
+ override var suppress: Boolean = DokkaDefaults.suppress
+
+ /**
+ * List of visibility modifiers that should be documented.
+ *
+ * This can be used if you want to document protected/internal/private declarations within a
+ * specific package, as well as if you want to exclude public declarations and only document internal API.
+ *
+ * Default is [DokkaConfiguration.Visibility.PUBLIC].
+ */
+ @Parameter(property = "visibility")
+ override var documentedVisibilities: Set<DokkaConfiguration.Visibility> = DokkaDefaults.documentedVisibilities
+
+ /**
+ * Whether to document declarations annotated with [Deprecated].
+ *
+ * Can be set on project level with [AbstractDokkaMojo.skipDeprecated].
+ *
+ * Default is `false`.
+ */
+ @Parameter
+ override var skipDeprecated: Boolean = DokkaDefaults.skipDeprecated
+
+ /**
+ * Whether to emit warnings about visible undocumented declarations, that is declarations from
+ * this package and without KDocs, after they have been filtered by [documentedVisibilities].
+ *
+ * This setting works well with [AbstractDokkaMojo.failOnWarning].
+ *
+ * Default is `false`.
+ */
+ @Parameter
+ override var reportUndocumented: Boolean = DokkaDefaults.reportUndocumented
+
+ @Parameter
+ @Deprecated("Use [documentedVisibilities] property for a more flexible control over documented visibilities")
+ override var includeNonPublic: Boolean = DokkaDefaults.includeNonPublic
+}
diff --git a/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/SourceLinkMapItem.kt b/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/SourceLinkMapItem.kt
new file mode 100644
index 00000000..ee2354aa
--- /dev/null
+++ b/dokka-runners/runner-maven-plugin/src/main/kotlin/org/jetbrains/dokka/maven/SourceLinkMapItem.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.maven
+
+import org.apache.maven.plugins.annotations.Parameter
+
+/**
+ * Configuration block that allows adding a `source` link to each signature
+ * which leads to [path] with a specific line number (configurable by setting [lineSuffix]),
+ * letting documentation readers find source code for each declaration.
+ *
+ * Example:
+ *
+ * ```xml
+ * <sourceLinks>
+ * <link>
+ * <path>${project.basedir}/src</path>
+ * <url>https://github.com/kotlin/dokka/tree/master/src</url>
+ * <lineSuffix>#L</lineSuffix>
+ * </link>
+ * </sourceLinks>
+ * ```
+ */
+public class SourceLinkMapItem {
+
+ /**
+ * Path to the local source directory. The path must be relative to the root of current project.
+ *
+ * Example:
+ *
+ * ```xml
+ * <path>${project.basedir}/src</path>
+ * ```
+ */
+ @Parameter(name = "path", required = true)
+ public var path: String = ""
+
+ /**
+ * URL of source code hosting service that can be accessed by documentation readers,
+ * like GitHub, GitLab, Bitbucket, etc. This URL will be used to generate
+ * source code links of declarations.
+ *
+ * Example:
+ *
+ * ```xml
+ * <url>https://github.com/username/projectname/tree/master/src</url>
+ * ```
+ */
+ @Parameter(name = "url", required = true)
+ public var url: String = ""
+
+ /**
+ * Suffix used to append source code line number to the URL. This will help readers navigate
+ * not only to the file, but to the specific line number of the declaration.
+ *
+ * The number itself will be appended to the specified suffix. For instance,
+ * if this property is set to `#L` and the line number is 10, resulting URL suffix
+ * will be `#L10`
+ *
+ * Suffixes used by popular services:
+ * - GitHub: `#L`
+ * - GitLab: `#L`
+ * - Bitbucket: `#lines-`
+ */
+ @Parameter(name = "lineSuffix")
+ public var lineSuffix: String? = null
+}