aboutsummaryrefslogtreecommitdiff
path: root/runners/gradle-plugin
diff options
context:
space:
mode:
authorPaweł Marks <pmarks@virtuslab.com>2020-07-17 16:36:09 +0200
committerPaweł Marks <pmarks@virtuslab.com>2020-07-17 16:36:09 +0200
commit6996b1135f61c7d2cb60b0652c6a2691dda31990 (patch)
treed568096c25e31c28d14d518a63458b5a7526b896 /runners/gradle-plugin
parentde56cab76f556e5b4af0b8c8cb08d8b482b86d0a (diff)
parent1c3530dcbb50c347f80bef694829dbefe89eca77 (diff)
downloaddokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.tar.gz
dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.tar.bz2
dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.zip
Merge branch 'dev-0.11.0'
Diffstat (limited to 'runners/gradle-plugin')
-rw-r--r--runners/gradle-plugin/build.gradle108
-rw-r--r--runners/gradle-plugin/build.gradle.kts63
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt45
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt222
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt17
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaBootstrapFactory.kt18
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTask.kt58
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt68
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetIDFactory.kt10
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt330
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt23
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ReflectDsl.kt72
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt234
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/defaultDokkaOutputDirectory.kt13
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaConfigurations.kt41
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt72
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt24
-rw-r--r--runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt48
-rw-r--r--runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaTasksTest.kt65
-rw-r--r--runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt97
20 files changed, 1072 insertions, 556 deletions
diff --git a/runners/gradle-plugin/build.gradle b/runners/gradle-plugin/build.gradle
deleted file mode 100644
index ceb03bae..00000000
--- a/runners/gradle-plugin/build.gradle
+++ /dev/null
@@ -1,108 +0,0 @@
-import com.gradle.publish.DependenciesBuilder
-
-apply plugin: 'java'
-apply plugin: 'kotlin'
-
-
-apply plugin: 'com.github.johnrengelman.shadow'
-apply plugin: "com.gradle.plugin-publish"
-
-sourceCompatibility = 1.8
-
-tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
- kotlinOptions {
- freeCompilerArgs += "-Xjsr305=strict"
- languageVersion = language_version
- apiVersion = language_version
- jvmTarget = "1.8"
- }
-}
-
-repositories {
- jcenter()
- google()
-}
-
-dependencies {
- testCompile group: 'junit', name: 'junit', version: '4.12'
-
- shadow group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlin_for_gradle_runtime_version
- shadow group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_for_gradle_runtime_version
-
- compile project(":integration")
-
- compileOnly "org.jetbrains.kotlin:kotlin-gradle-plugin"
- compileOnly("com.android.tools.build:gradle:3.0.0")
- compileOnly("com.android.tools.build:gradle-core:3.0.0")
- compileOnly("com.android.tools.build:builder-model:3.0.0")
- compileOnly gradleApi()
- compileOnly localGroovy()
- implementation "com.google.code.gson:gson:$gson_version"
-}
-
-task sourceJar(type: Jar) {
- from sourceSets.main.allSource
-}
-
-processResources {
- eachFile {
- if (it.name == "org.jetbrains.dokka.properties") {
- it.filter { line ->
- line.replace("<version>", dokka_version)
- }
- }
- }
-}
-
-shadowJar {
- baseName = 'dokka-gradle-plugin'
- classifier = ''
-}
-
-apply plugin: 'maven-publish'
-
-publishing {
- publications {
- dokkaGradlePlugin(MavenPublication) { publication ->
- artifactId = 'dokka-gradle-plugin'
-
- artifact sourceJar {
- classifier "sources"
- }
-
- project.shadow.component(publication)
- }
- }
-}
-
-bintrayPublication(project, ['dokkaGradlePlugin'])
-
-configurations.archives.artifacts.clear()
-artifacts {
- archives shadowJar
-}
-
-pluginBundle {
- website = 'https://www.kotlinlang.org/'
- vcsUrl = 'https://github.com/kotlin/dokka.git'
- description = 'Dokka, the Kotlin documentation tool'
- tags = ['dokka', 'kotlin', 'kdoc', 'android']
-
- plugins {
- dokkaGradlePlugin {
- id = 'org.jetbrains.dokka'
- displayName = 'Dokka plugin'
- }
- }
-
- withDependencies { List<Dependency> list ->
- list.clear()
- def builder = new DependenciesBuilder()
- builder.addUniqueScopedDependencies(list, configurations.shadow, "compile")
- }
-
- mavenCoordinates {
- groupId = "org.jetbrains.dokka"
- artifactId = "dokka-gradle-plugin"
- }
-} \ No newline at end of file
diff --git a/runners/gradle-plugin/build.gradle.kts b/runners/gradle-plugin/build.gradle.kts
new file mode 100644
index 00000000..0222f5e0
--- /dev/null
+++ b/runners/gradle-plugin/build.gradle.kts
@@ -0,0 +1,63 @@
+import org.jetbrains.configureBintrayPublication
+import org.jetbrains.dokkaVersion
+
+plugins {
+ `java-gradle-plugin`
+}
+
+repositories {
+ google()
+}
+
+dependencies {
+ implementation(project(":core"))
+ compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin")
+ compileOnly("com.android.tools.build:gradle:3.0.0")
+ compileOnly("com.android.tools.build:gradle-core:3.0.0")
+ compileOnly("com.android.tools.build:builder-model:3.0.0")
+ compileOnly(gradleApi())
+ compileOnly(gradleKotlinDsl())
+ testImplementation(gradleApi())
+ testImplementation(gradleKotlinDsl())
+ testImplementation(kotlin("test-junit"))
+ testImplementation("org.jetbrains.kotlin:kotlin-gradle-plugin")
+
+ constraints {
+ val kotlin_version: String by project
+ compileOnly("org.jetbrains.kotlin:kotlin-reflect:${kotlin_version}") {
+ because("kotlin-gradle-plugin and :core both depend on this")
+ }
+ }
+}
+
+val sourceJar by tasks.registering(Jar::class) {
+ archiveClassifier.set("sources")
+ from(sourceSets["main"].allSource)
+}
+
+gradlePlugin {
+ plugins {
+ create("dokkaGradlePlugin") {
+ id = "org.jetbrains.dokka"
+ implementationClass = "org.jetbrains.dokka.gradle.DokkaPlugin"
+ version = dokkaVersion
+ }
+ }
+}
+
+publishing {
+ publications {
+ register<MavenPublication>("pluginMaven") {
+ artifactId = "dokka-gradle-plugin"
+ }
+
+ register<MavenPublication>("dokkaGradlePluginForIntegrationTests") {
+ artifactId = "dokka-gradle-plugin"
+ from(components["java"])
+ version = "for-integration-tests-SNAPSHOT"
+ }
+ }
+}
+
+
+configureBintrayPublication("dokkaGradlePluginPluginMarkerMaven", "pluginMaven")
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt
new file mode 100644
index 00000000..846f021c
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt
@@ -0,0 +1,45 @@
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.attributes.Usage
+import org.gradle.api.tasks.Classpath
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.TaskAction
+import org.gradle.kotlin.dsl.dependencies
+import org.jetbrains.dokka.DokkaBootstrap
+import org.jetbrains.dokka.plugability.Configurable
+
+
+abstract class AbstractDokkaTask : DefaultTask(), Configurable {
+ @Input
+ var outputDirectory: String = defaultDokkaOutputDirectory().absolutePath
+
+ @Input
+ override val pluginsConfiguration: Map<String, String> = mutableMapOf()
+
+ @Classpath
+ val plugins: Configuration = project.maybeCreateDokkaPluginConfiguration(name)
+
+ @Classpath
+ val runtime: Configuration = project.maybeCreateDokkaRuntimeConfiguration(name)
+
+ @TaskAction
+ protected fun run() {
+ val kotlinColorsEnabledBefore = System.getProperty(DokkaTask.COLORS_ENABLED_PROPERTY) ?: "false"
+ System.setProperty(DokkaTask.COLORS_ENABLED_PROPERTY, "false")
+ try {
+ generate()
+ } finally {
+ System.setProperty(DokkaTask.COLORS_ENABLED_PROPERTY, kotlinColorsEnabledBefore)
+ }
+ }
+
+ protected abstract fun generate()
+
+ protected fun DokkaBootstrap(bootstrapClassFQName: String): DokkaBootstrap {
+ return DokkaBootstrap(runtime, bootstrapClassFQName)
+ }
+}
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt
index 39672b9a..1bfd2c78 100644
--- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt
@@ -1,9 +1,5 @@
package org.jetbrains.dokka.gradle
-import com.android.build.gradle.*
-import com.android.build.gradle.api.BaseVariant
-import com.android.builder.core.BuilderConstants
-import org.gradle.api.NamedDomainObjectCollection
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.UnknownDomainObjectException
@@ -13,90 +9,71 @@ import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.dokka.ReflectDsl
-import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
-import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
import java.io.File
import java.io.Serializable
class ConfigurationExtractor(private val project: Project) {
-
- fun extractConfiguration(targetName: String, variantNames: List<String>) =
- if (project.isMultiplatformProject()) {
- extractFromMultiPlatform(targetName, variantNames)
- } else {
- extractFromSinglePlatform(variantNames)
+ fun extractConfiguration(sourceSetName: String): PlatformData? {
+ val projectExtension = project.extensions.findByType(KotlinProjectExtension::class.java) ?: run {
+ project.logger.error("Missing kotlin project extension")
+ return null
}
- private fun extractFromSinglePlatform(variantNames: List<String>): PlatformData? {
- val target: KotlinTarget
- try {
- target = project.extensions.getByType(KotlinSingleTargetExtension::class.java).target
- } catch (e: Throwable) {
- when (e) {
- is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException ->
- return null
- else -> throw e
- }
+ val sourceSet = projectExtension.sourceSets.findByName(sourceSetName) ?: run {
+ project.logger.error("No source set with name '$sourceSetName' found")
+ return null
}
- return try {
- PlatformData(
- null,
- accumulateClassPaths(variantNames, target),
- accumulateSourceSets(variantNames, target),
- getPlatformName(target.platformType)
- )
- } catch (e: NoSuchMethodError) {
+ val compilation = try {
+ when (projectExtension) {
+ is KotlinMultiplatformExtension -> {
+ val targets = projectExtension.targets.flatMap { it.compilations }
+ targets.find { it.name == sourceSetName }
+ ?: targets.find { it.kotlinSourceSets.contains(sourceSet) }
+ }
+ is KotlinSingleTargetExtension -> projectExtension.target.compilations.find {
+ it.kotlinSourceSets.contains(sourceSet)
+ }
+ else -> null
+ }
+ } catch (e: NoClassDefFoundError) { // Old Kotlin plugin versions
null
}
+
+ val sourceRoots = sourceSet.sourceFiles
+ val classpath = compilation?.classpath
+ ?: sourceRoots + sourceSet.allParentSourceFiles()
+
+ return PlatformData(
+ sourceSetName,
+ classpath.filter { it.exists() },
+ sourceRoots,
+ sourceSet.dependsOn.map { it.name },
+ compilation?.target?.platformType?.name ?: "common"
+ )
}
- private fun extractFromMultiPlatform(targetName: String, variantNames: List<String>): PlatformData? =
- try {
- project.extensions.getByType(KotlinMultiplatformExtension::class.java).targets
- } catch (e: Throwable) {
- when (e) {
- is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException ->
- null
- else -> throw e
- }
- }?.let {
- val fixedName = if (targetName.toLowerCase() == "common") "metadata" else targetName.toLowerCase()
- it.find { target -> target.name.toLowerCase() == fixedName }?.let { target ->
- PlatformData(
- fixedName,
- accumulateClassPaths(variantNames, target),
- accumulateSourceSets(variantNames, target),
- target.platformType.toString()
- )
- }
- }
+ private fun KotlinSourceSet.allParentSourceFiles(): List<File> =
+ sourceFiles + dependsOn.flatMap { it.allParentSourceFiles() }
fun extractFromJavaPlugin(): PlatformData? =
project.convention.findPlugin(JavaPluginConvention::class.java)
?.run { sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME)?.allSource?.srcDirs }
- ?.let { PlatformData(null, emptyList(), it.toList(), "") }
+ ?.let { PlatformData(null, emptyList(), it.toList(), emptyList(), "") }
- fun extractFromKotlinTasks(kotlinTasks: List<Task>): PlatformData? =
+ fun extractFromKotlinTasks(kotlinTasks: List<Task>): List<PlatformData> =
try {
- kotlinTasks.map { extractFromKotlinTask(it) }.let { platformDataList ->
- PlatformData(
- null,
- platformDataList.flatMap { it.classpath },
- platformDataList.flatMap { it.sourceRoots },
- ""
- )
- }
+ kotlinTasks.map { extractFromKotlinTask(it) }
} catch (e: Throwable) {
when (e) {
is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException ->
- extractFromKotlinTasksTheHardWay(kotlinTasks)
+ listOfNotNull(extractFromKotlinTasksTheHardWay(kotlinTasks))
else -> throw e
}
}
@@ -110,10 +87,18 @@ class ConfigurationExtractor(private val project: Project) {
when (e) {
is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException ->
project.extensions.getByType(KotlinMultiplatformExtension::class.java).targets
- .firstNotNullResult { target -> target.compilations.find { it.compileKotlinTask == task } }
+ .flatMap { it.compilations }.firstOrNull { it.compileKotlinTask == task }
else -> throw e
}
- }.let { PlatformData(task.name, getClasspath(it), getSourceSet(it), it?.platformType?.toString() ?: "") }
+ }.let { compilation ->
+ PlatformData(
+ task.name,
+ compilation?.classpath.orEmpty(),
+ compilation?.sourceFiles.orEmpty(),
+ compilation?.dependentSourceSets?.map { it.name }.orEmpty(),
+ compilation?.platformType?.toString() ?: ""
+ )
+ }
private fun extractFromKotlinTasksTheHardWay(kotlinTasks: List<Task>): PlatformData? {
val allClasspath = mutableSetOf<File>()
@@ -134,8 +119,8 @@ class ConfigurationExtractor(private val project: Project) {
val taskClasspath: Iterable<File> =
(it["getClasspath", AbstractCompile::class].takeIfIsFunc()?.invoke()
- ?: it["compileClasspath", abstractKotlinCompileClz].takeIfIsProp()?.v()
- ?: it["getClasspath", abstractKotlinCompileClz]())
+ ?: it["compileClasspath", abstractKotlinCompileClz].takeIfIsProp()?.v()
+ ?: it["getClasspath", abstractKotlinCompileClz]())
if (taskClasspath is FileCollection) {
allClasspathFileCollection += taskClasspath
@@ -152,97 +137,42 @@ class ConfigurationExtractor(private val project: Project) {
}
classpath.addAll(project.files(allClasspath).toList())
- return PlatformData(null, classpath, allSourceRoots.toList(), "")
+ return PlatformData(null, classpath, allSourceRoots.toList(), emptyList(), "")
}
- private fun getSourceSet(target: KotlinTarget, variantName: String): List<File> =
- if (target.isAndroidTarget())
- getSourceSet(getCompilation(target, variantName))
- else
- getSourceSet(getMainCompilation(target))
-
- private fun getClasspath(target: KotlinTarget, variantName: String): List<File> =
- if (target.isAndroidTarget())
- getClasspathFromAndroidTask(getCompilation(target, variantName))
- else
- getClasspath(getMainCompilation(target))
-
- private fun getSourceSet(compilation: KotlinCompilation<*>?): List<File> = compilation
- ?.allKotlinSourceSets
- ?.flatMap { it.kotlin.sourceDirectories }
- ?.filter { it.exists() }
- .orEmpty()
-
- private fun getClasspath(compilation: KotlinCompilation<*>?): List<File> = compilation
- ?.compileDependencyFiles
- ?.files
- ?.toList()
- ?.filter { it.exists() }
- .orEmpty()
-
- // This is a workaround for KT-33893
- private fun getClasspathFromAndroidTask(compilation: KotlinCompilation<*>): List<File> = (compilation
- .compileKotlinTask as? KotlinCompile)
- ?.classpath?.files?.toList() ?: getClasspath(compilation)
-
- private fun getMainCompilation(target: KotlinTarget) =
- getCompilation(target, getMainCompilationName(target))
-
- private fun getCompilation(target: KotlinTarget, name: String) =
- target.compilations.getByName(name)
-
- private fun getMainCompilationName(target: KotlinTarget) = if (target.isAndroidTarget())
- getVariants(project).filter { it.buildType.name == BuilderConstants.RELEASE }.map { it.name }.first()
- else
- KotlinCompilation.MAIN_COMPILATION_NAME
-
- private fun getVariants(project: Project): Set<BaseVariant> {
- val androidExtension = project.extensions.getByName("android")
- val baseVariants = when (androidExtension) {
- is AppExtension -> androidExtension.applicationVariants.toSet()
- is LibraryExtension -> {
- androidExtension.libraryVariants.toSet() +
- if (androidExtension is FeatureExtension) {
- androidExtension.featureVariants.toSet()
- } else {
- emptySet<BaseVariant>()
- }
- }
- is TestExtension -> androidExtension.applicationVariants.toSet()
- else -> emptySet()
- }
- val testVariants = if (androidExtension is TestedExtension) {
- androidExtension.testVariants.toSet() + androidExtension.unitTestVariants.toSet()
- } else {
- emptySet<BaseVariant>()
- }
+ private val KotlinCompilation<*>.sourceFiles: List<File>
+ get() = kotlinSourceSets.flatMap { it.sourceFiles }
- return baseVariants + testVariants
- }
+ private val KotlinSourceSet.sourceFiles: List<File>
+ get() = kotlin.sourceDirectories.filter { it.exists() }.toList()
- private fun getPlatformName(platform: KotlinPlatformType): String =
- if (platform == KotlinPlatformType.androidJvm) KotlinPlatformType.jvm.toString() else platform.toString()
+ private val KotlinCompilation<*>.dependentSourceSets: Set<KotlinSourceSet>
+ get() = (allKotlinSourceSets - kotlinSourceSets)
- private fun accumulateClassPaths(variantNames: List<String>, target: KotlinTarget) =
- if (variantNames.isNotEmpty()) {
- variantNames.flatMap { getClasspath(target, it) }.distinct()
+ private val KotlinCompilation<*>.classpath: List<File>
+ get() = if (target.isAndroidTarget()) {
+ getClasspathFromAndroidTask(this)
} else {
- if (target.isAndroidTarget())
- getClasspathFromAndroidTask(getMainCompilation(target))
- else
- getClasspath(getMainCompilation(target))
+ getClasspathFromRegularTask(this)
}
- private fun accumulateSourceSets(variantNames: List<String>, target: KotlinTarget) =
- if (variantNames.isNotEmpty())
- variantNames.flatMap { getSourceSet(target, it) }.distinct()
- else
- getSourceSet(getMainCompilation(target))
+ // This is a workaround for KT-33893
+ private fun getClasspathFromAndroidTask(compilation: KotlinCompilation<*>): List<File> = (compilation
+ .compileKotlinTask as? KotlinCompile)
+ ?.classpath?.files?.toList() ?: getClasspathFromRegularTask(compilation)
+
+ private fun getClasspathFromRegularTask(compilation: KotlinCompilation<*>): List<File> =
+ compilation
+ .compileDependencyFiles
+ .files
+ .toList()
+ .filter { it.exists() }
data class PlatformData(
val name: String?,
val classpath: List<File>,
val sourceRoots: List<File>,
+ val dependentSourceSets: List<String>,
val platform: String
) : Serializable
-} \ No newline at end of file
+}
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt
new file mode 100644
index 00000000..90d51015
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt
@@ -0,0 +1,17 @@
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.jetbrains.dokka.DokkaVersion
+
+internal val Project.dokkaArtifacts get() = DokkaArtifacts(this)
+
+internal class DokkaArtifacts(private val project: Project) {
+ private fun fromModuleName(name: String) =
+ project.dependencies.create("org.jetbrains.dokka:$name:${DokkaVersion.version}")
+
+ 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 jekyllPlugin get() = fromModuleName("jekyll-plugin")
+}
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaBootstrapFactory.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaBootstrapFactory.kt
new file mode 100644
index 00000000..df29c19b
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaBootstrapFactory.kt
@@ -0,0 +1,18 @@
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.artifacts.Configuration
+import org.jetbrains.dokka.DokkaBootstrap
+import java.net.URLClassLoader
+
+
+fun DokkaBootstrap(configuration: Configuration, bootstrapClassFQName: String): DokkaBootstrap {
+ val runtimeJars = configuration.resolve()
+ val runtimeClassLoader = URLClassLoader(
+ runtimeJars.map { it.toURI().toURL() }.toTypedArray(),
+ ClassLoader.getSystemClassLoader().parent
+ )
+
+ val bootstrapClass = runtimeClassLoader.loadClass(bootstrapClassFQName)
+ val bootstrapInstance = bootstrapClass.constructors.first().newInstance()
+ return automagicTypedProxy(DokkaPlugin::class.java.classLoader, bootstrapInstance)
+}
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTask.kt
new file mode 100644
index 00000000..37952ea8
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTask.kt
@@ -0,0 +1,58 @@
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaBasePlugin.DOCUMENTATION_GROUP
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.TaskAction
+import java.lang.IllegalStateException
+
+open class DokkaCollectorTask : DefaultTask() {
+
+ @Input
+ var modules: List<String> = emptyList()
+
+ @Input
+ var outputDirectory: String = defaultDokkaOutputDirectory().absolutePath
+
+ private lateinit var configuration: GradleDokkaConfigurationImpl
+
+ @Input
+ var dokkaTaskNames: Set<String> = setOf()
+
+ @TaskAction
+ fun collect() {
+ val configurations = project.subprojects
+ .filter { subProject -> subProject.name in modules }
+ .flatMap { subProject -> dokkaTaskNames.mapNotNull(subProject.tasks::findByName) }
+ .filterIsInstance<DokkaTask>()
+ .mapNotNull { dokkaTask -> dokkaTask.getConfigurationOrNull() }
+
+
+ val initial = GradleDokkaConfigurationImpl().apply {
+ outputDir = outputDirectory
+ cacheRoot = configurations.first().cacheRoot
+ }
+
+ // TODO this certainly not the ideal solution
+ configuration = configurations.fold(initial) { acc, it: GradleDokkaConfigurationImpl ->
+ if (acc.cacheRoot != it.cacheRoot)
+ throw IllegalStateException("Dokka task configurations differ on core argument cacheRoot")
+ acc.sourceSets = acc.sourceSets + it.sourceSets
+ acc.pluginsClasspath = (acc.pluginsClasspath + it.pluginsClasspath).distinct()
+ acc
+ }
+ project.tasks.withType(DokkaTask::class.java).configureEach { it.enforcedConfiguration = configuration }
+ }
+
+ init {
+ // TODO: This this certainly not the ideal solution
+ dokkaTaskNames.forEach { dokkaTaskName ->
+ finalizedBy(dokkaTaskName)
+ }
+
+ group = DOCUMENTATION_GROUP
+ }
+
+
+}
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt
new file mode 100644
index 00000000..6fd58afe
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt
@@ -0,0 +1,68 @@
+package org.jetbrains.dokka.gradle
+
+import com.google.gson.GsonBuilder
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaBasePlugin.DOCUMENTATION_GROUP
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.jetbrains.dokka.plugability.Configurable
+
+open class DokkaMultimoduleTask : AbstractDokkaTask(), Configurable {
+
+ @Input
+ var documentationFileName: String = "README.md"
+
+
+ @Input
+ var dokkaTaskNames: Set<String> = setOf()
+ set(value) {
+ field = value.toSet()
+ setDependsOn(getSubprojectDokkaTasks(value))
+ }
+
+
+ override fun generate() {
+ val bootstrap = DokkaBootstrap("org.jetbrains.dokka.DokkaMultimoduleBootstrapImpl")
+ val gson = GsonBuilder().setPrettyPrinting().create()
+ val configuration = getConfiguration()
+ bootstrap.configure(gson.toJson(configuration)) { level, message ->
+ when (level) {
+ "debug" -> logger.debug(message)
+ "info" -> logger.info(message)
+ "progress" -> logger.lifecycle(message)
+ "warn" -> logger.warn(message)
+ "error" -> logger.error(message)
+ }
+ }
+ bootstrap.generate()
+ }
+
+ @Internal
+ internal fun getConfiguration(): GradleDokkaConfigurationImpl =
+ GradleDokkaConfigurationImpl().apply {
+ outputDir = project.file(outputDirectory).absolutePath
+ pluginsClasspath = plugins.resolve().toList()
+ pluginsConfiguration = this@DokkaMultimoduleTask.pluginsConfiguration
+ modules = getSubprojectDokkaTasks(dokkaTaskNames).map { dokkaTask ->
+ GradleDokkaModuleDescription().apply {
+ name = dokkaTask.project.name
+ path = dokkaTask.project.projectDir.resolve(dokkaTask.outputDirectory)
+ .toRelativeString(project.file(outputDirectory))
+ docFile = dokkaTask.project.projectDir.resolve(documentationFileName).absolutePath
+ }
+ }
+ }
+
+ private fun getSubprojectDokkaTasks(dokkaTaskName: String): List<DokkaTask> {
+ return project.subprojects
+ .mapNotNull { subproject -> subproject.tasks.findByName(dokkaTaskName) as? DokkaTask }
+ }
+
+ private fun getSubprojectDokkaTasks(dokkaTaskNames: Set<String>): List<DokkaTask> {
+ return dokkaTaskNames.flatMap { dokkaTaskName -> getSubprojectDokkaTasks(dokkaTaskName) }
+ }
+
+ init {
+ group = DOCUMENTATION_GROUP
+ }
+}
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetIDFactory.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetIDFactory.kt
new file mode 100644
index 00000000..3fadb4fd
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetIDFactory.kt
@@ -0,0 +1,10 @@
+@file:Suppress("FunctionName")
+
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.jetbrains.dokka.DokkaSourceSetID
+
+internal fun DokkaSourceSetID(project: Project, sourceSetName: String): DokkaSourceSetID {
+ return DokkaSourceSetID(moduleName = project.path, sourceSetName = sourceSetName)
+}
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt
index bafe657e..0d7e74a3 100644
--- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt
@@ -1,28 +1,26 @@
package org.jetbrains.dokka.gradle
import com.google.gson.GsonBuilder
-import org.gradle.api.*
-import org.gradle.api.artifacts.Configuration
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.Project
+import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.plugins.DslObject
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.tasks.*
-import org.jetbrains.dokka.DokkaBootstrap
import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink.Builder
import org.jetbrains.dokka.DokkaConfiguration.SourceRoot
+import org.jetbrains.dokka.DokkaException
import org.jetbrains.dokka.Platform
import org.jetbrains.dokka.ReflectDsl
import org.jetbrains.dokka.ReflectDsl.isNotInstance
import org.jetbrains.dokka.gradle.ConfigurationExtractor.PlatformData
-import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import java.io.File
-import java.net.URLClassLoader
import java.util.concurrent.Callable
-import java.util.function.BiConsumer
-open class DokkaTask : DefaultTask() {
+open class DokkaTask : AbstractDokkaTask() {
private val ANDROID_REFERENCE_URL = Builder("https://developer.android.com/reference/").build()
- private val GLOBAL_PLATFORM_NAME = "global" // Used for copying perPackageOptions to other platforms
+ private val configExtractor = ConfigurationExtractor(project)
@Suppress("MemberVisibilityCanBePrivate")
fun defaultKotlinTasks(): List<Task> = with(ReflectDsl) {
@@ -44,60 +42,50 @@ open class DokkaTask : DefaultTask() {
dependsOn(Callable { kotlinTasks.map { it.taskDependencies } })
}
- @Input
- var outputFormat: String = "html"
-
- @Input
- var outputDirectory: String = ""
-
- var dokkaRuntime: Configuration? = null
-
- @Input
- var subProjects: List<String> = emptyList()
-
- @Input
- var impliedPlatforms: MutableList<String> = arrayListOf()
-
@Optional
@Input
var cacheRoot: String? = null
- var multiplatform: NamedDomainObjectContainer<GradlePassConfigurationImpl>
- @Suppress("UNCHECKED_CAST")
- @Nested get() = (DslObject(this).extensions.getByName(MULTIPLATFORM_EXTENSION_NAME) as NamedDomainObjectContainer<GradlePassConfigurationImpl>)
- internal set(value) = DslObject(this).extensions.add(MULTIPLATFORM_EXTENSION_NAME, value)
- var configuration: GradlePassConfigurationImpl
- @Suppress("UNCHECKED_CAST")
- @Nested get() = DslObject(this).extensions.getByType(GradlePassConfigurationImpl::class.java)
- internal set(value) = DslObject(this).extensions.add(CONFIGURATION_EXTENSION_NAME, value)
+ /**
+ * Hack used by DokkaCollector to enforce a different configuration to be used.
+ */
+ @get:Internal
+ internal var enforcedConfiguration: GradleDokkaConfigurationImpl? = null
- // Configure Dokka with closure in Gradle Kotlin DSL
- fun configuration(action: Action<in GradlePassConfigurationImpl>) = action.execute(configuration)
+ @get:Nested
+ val dokkaSourceSets: NamedDomainObjectContainer<GradleDokkaSourceSet> =
+ project.container(GradleDokkaSourceSet::class.java) { name -> GradleDokkaSourceSet(name, project) }
+ .also { container -> DslObject(this).extensions.add("dokkaSourceSets", container) }
- private val kotlinTasks: List<Task> by lazy { extractKotlinCompileTasks(configuration.collectKotlinTasks ?: { defaultKotlinTasks() }) }
- private val configExtractor = ConfigurationExtractor(project)
+ private val kotlinTasks: List<Task> by lazy {
+ extractKotlinCompileTasks(
+ dokkaSourceSets.mapNotNull {
+ it.collectKotlinTasks?.invoke()
+ }.takeIf { it.isNotEmpty() }?.flatten() ?: defaultKotlinTasks()
+ )
+ }
@Input
var disableAutoconfiguration: Boolean = false
- private var outputDiagnosticInfo: Boolean = false // Workaround for Gradle, which fires some methods (like collectConfigurations()) multiple times in its lifecycle
+ @Input
+ var failOnWarning: Boolean = false
- private fun loadFatJar() {
- if (ClassloaderContainer.fatJarClassLoader == null) {
- val jars = dokkaRuntime!!.resolve().toList()
- ClassloaderContainer.fatJarClassLoader = URLClassLoader(jars.map { it.toURI().toURL() }.toTypedArray(), ClassLoader.getSystemClassLoader().parent)
- }
- }
+ @Input
+ var offlineMode: Boolean = false
- private fun extractKotlinCompileTasks(collectTasks: () -> List<Any?>?): List<Task> {
- val inputList = (collectTasks.invoke() ?: emptyList()).filterNotNull()
- val (paths, other) = inputList.partition { it is String }
+ private var outputDiagnosticInfo: Boolean =
+ false // Workaround for Gradle, which fires some methods (like collectConfigurations()) multiple times in its lifecycle
- val taskContainer = project.tasks
+ protected fun extractKotlinCompileTasks(collectTasks: List<Any?>?): List<Task> {
+ val inputList = (collectTasks ?: emptyList()).filterNotNull()
+ val (paths, other) = inputList.partition { it is String }
- val tasksByPath = paths.map { taskContainer.findByPath(it as String) ?: throw IllegalArgumentException("Task with path '$it' not found") }
+ val tasksByPath = paths.map {
+ project.tasks.findByPath(it as String) ?: throw IllegalArgumentException("Task with path '$it' not found")
+ }
other
.filter { it !is Task || it isNotInstance getAbstractKotlinCompileFor(it) }
@@ -111,11 +99,14 @@ open class DokkaTask : DefaultTask() {
return (tasksByPath + other) as List<Task>
}
- private fun Iterable<File>.toSourceRoots(): List<GradleSourceRootImpl> = this.filter { it.exists() }.map { GradleSourceRootImpl().apply { path = it.path } }
- private fun Iterable<String>.toProjects(): List<Project> = project.subprojects.toList().filter { this.contains(it.name) }
+ private fun Iterable<File>.toSourceRoots(): List<GradleSourceRootImpl> =
+ this.filter { it.exists() }.map { GradleSourceRootImpl().apply { path = it.path } }
+
+ private fun Iterable<String>.toProjects(): List<Project> =
+ project.subprojects.toList().filter { this.contains(it.name) }
protected open fun collectSuppressedFiles(sourceRoots: List<SourceRoot>) =
- if(project.isAndroidProject()) {
+ if (project.isAndroidProject()) {
val generatedRoot = project.buildDir.resolve("generated").absoluteFile
sourceRoots
.map { File(it.path) }
@@ -126,153 +117,152 @@ open class DokkaTask : DefaultTask() {
emptyList()
}
- @TaskAction
- fun generate() {
+ override fun generate() = enforcedConfiguration?.let { generate(it) } ?: generate(getConfigurationOrThrow())
+
+ protected open fun generate(configuration: GradleDokkaConfigurationImpl) {
outputDiagnosticInfo = true
- val kotlinColorsEnabledBefore = System.getProperty(COLORS_ENABLED_PROPERTY) ?: "false"
- System.setProperty(COLORS_ENABLED_PROPERTY, "false")
- try {
- loadFatJar()
-
- val bootstrapClass = ClassloaderContainer.fatJarClassLoader!!.loadClass("org.jetbrains.dokka.DokkaBootstrapImpl")
- val bootstrapInstance = bootstrapClass.constructors.first().newInstance()
- val bootstrapProxy: DokkaBootstrap =
- automagicTypedProxy(javaClass.classLoader, bootstrapInstance)
-
- val gson = GsonBuilder().setPrettyPrinting().create()
-
- val globalConfig = multiplatform.toList().find { it.name.toLowerCase() == GLOBAL_PLATFORM_NAME }
- val passConfigurationList = collectConfigurations()
- .map { defaultPassConfiguration(it, globalConfig) }
-
- val configuration = GradleDokkaConfigurationImpl()
- configuration.outputDir = outputDirectory
- configuration.format = outputFormat
- configuration.generateIndexPages = true
- configuration.cacheRoot = cacheRoot
- configuration.impliedPlatforms = impliedPlatforms
- configuration.passesConfigurations = passConfigurationList
-
- if(passConfigurationList.isEmpty() || passConfigurationList == listOf(globalConfig)) {
- println("No pass configurations for generation detected!")
- return
+ val bootstrap = DokkaBootstrap("org.jetbrains.dokka.DokkaBootstrapImpl")
+
+ bootstrap.configure(
+ GsonBuilder().setPrettyPrinting().create().toJson(configuration)
+ ) { level, message ->
+ when (level) {
+ "debug" -> logger.debug(message)
+ "info" -> logger.info(message)
+ "progress" -> logger.lifecycle(message)
+ "warn" -> logger.warn(message)
+ "error" -> logger.error(message)
}
+ }
+ bootstrap.generate()
+ }
- bootstrapProxy.configure(
- BiConsumer { level, message ->
- when (level) {
- "info" -> logger.info(message)
- "warn" -> logger.warn(message)
- "error" -> logger.error(message)
- }
- },
- gson.toJson(configuration)
- )
- bootstrapProxy.generate()
+ @Internal
+ internal fun getConfigurationOrNull(): GradleDokkaConfigurationImpl? {
+ val defaultModulesConfiguration = configuredDokkaSourceSets
+ .map { configureDefault(it) }.takeIf { it.isNotEmpty() }
+ ?: listOf(
+ configureDefault(configureDokkaSourceSet(dokkaSourceSets.create("main")))
+ ).takeIf { project.isNotMultiplatformProject() } ?: emptyList()
- } finally {
- System.setProperty(COLORS_ENABLED_PROPERTY, kotlinColorsEnabledBefore)
+ if (defaultModulesConfiguration.isEmpty()) {
+ return null
}
- }
- protected open fun collectConfigurations() =
- if (this.isMultiplatformProject()) collectMultiplatform() else listOf(collectSinglePlatform(configuration))
+ return GradleDokkaConfigurationImpl().apply {
+ outputDir = project.file(outputDirectory).absolutePath
+ cacheRoot = this@DokkaTask.cacheRoot
+ offlineMode = this@DokkaTask.offlineMode
+ sourceSets = defaultModulesConfiguration
+ pluginsClasspath = plugins.resolve().toList()
+ pluginsConfiguration = this@DokkaTask.pluginsConfiguration
+ failOnWarning = this@DokkaTask.failOnWarning
+ }
+ }
- protected open fun collectMultiplatform() = multiplatform
- .filterNot { it.name.toLowerCase() == GLOBAL_PLATFORM_NAME }
- .map { collectSinglePlatform(it) }
+ @Internal
+ internal fun getConfigurationOrThrow(): GradleDokkaConfigurationImpl {
+ return getConfigurationOrNull() ?: throw DokkaException(
+ """
+ No source sets to document found.
+ Make source to configure at least one source set e.g.
+
+ dokka {
+ dokkaSourceSets {
+ create("commonMain") {
+ displayName = "common"
+ platform = "common"
+ }
+ }
+ }
+ """
+ )
+ }
- protected open fun collectSinglePlatform(config: GradlePassConfigurationImpl): GradlePassConfigurationImpl {
- val userConfig = config.let {
- if (it.collectKotlinTasks != null) {
- configExtractor.extractFromKotlinTasks(extractKotlinCompileTasks(it.collectKotlinTasks!!))
- ?.let { platformData -> mergeUserConfigurationAndPlatformData(it, platformData) } ?: it
- } else {
- it
+ @get:Internal
+ protected val configuredDokkaSourceSets: List<GradleDokkaSourceSet>
+ get() = dokkaSourceSets.map { configureDokkaSourceSet(it) }
+
+ private fun configureDokkaSourceSet(config: GradleDokkaSourceSet): GradleDokkaSourceSet {
+ val userConfig = config
+ .apply {
+ collectKotlinTasks?.let {
+ configExtractor.extractFromKotlinTasks(extractKotlinCompileTasks(it()))
+ .fold(this) { config, platformData ->
+ mergeUserConfigurationAndPlatformData(config, platformData)
+ }
+ }
}
- }
if (disableAutoconfiguration) return userConfig
- val baseConfig = configExtractor.extractConfiguration(userConfig.name, userConfig.androidVariants)
+ return configExtractor.extractConfiguration(userConfig.name)
?.let { mergeUserConfigurationAndPlatformData(userConfig, it) }
- ?: if (this.isMultiplatformProject()) {
- if (outputDiagnosticInfo)
- logger.warn("Could not find target with name: ${userConfig.name} in Kotlin Gradle Plugin, " +
- "using only user provided configuration for this target")
- userConfig
- } else {
- logger.warn("Could not find target with name: ${userConfig.name} in Kotlin Gradle Plugin")
- collectFromSinglePlatformOldPlugin()
- }
-
- return if (subProjects.isNotEmpty()) {
- try {
- subProjects.toProjects().fold(baseConfig) { config, subProject ->
- mergeUserConfigurationAndPlatformData(
- config,
- ConfigurationExtractor(subProject).extractConfiguration(config.name, config.androidVariants)!!
+ ?: if (this.dokkaSourceSets.isNotEmpty()) {
+ if (outputDiagnosticInfo)
+ logger.warn(
+ "Could not find source set with name: ${userConfig.name} in Kotlin Gradle Plugin, " +
+ "using only user provided configuration for this source set"
)
- }
- } catch(e: NullPointerException) {
- logger.warn("Cannot extract sources from subProjects. Do you have the Kotlin plugin in version 1.3.30+ " +
- "and the Kotlin plugin applied in the root project?")
- baseConfig
+ userConfig
+ } else {
+ if (outputDiagnosticInfo)
+ logger.warn("Could not find source set with name: ${userConfig.name} in Kotlin Gradle Plugin")
+ collectFromSinglePlatformOldPlugin(userConfig.name, userConfig)
}
- } else {
- baseConfig
- }
}
- protected open fun collectFromSinglePlatformOldPlugin() =
- configExtractor.extractFromKotlinTasks(kotlinTasks)
- ?.let { mergeUserConfigurationAndPlatformData(configuration, it) }
- ?: configExtractor.extractFromJavaPlugin()
- ?.let { mergeUserConfigurationAndPlatformData(configuration, it) }
- ?: configuration
-
- protected open fun mergeUserConfigurationAndPlatformData(userConfig: GradlePassConfigurationImpl, autoConfig: PlatformData) =
- userConfig.copy().apply {
- sourceRoots.addAll(userConfig.sourceRoots.union(autoConfig.sourceRoots.toSourceRoots()).distinct())
- classpath = userConfig.classpath.union(autoConfig.classpath.map { it.absolutePath }).distinct()
- if (userConfig.platform == null && autoConfig.platform != "")
- platform = autoConfig.platform
+ private fun collectFromSinglePlatformOldPlugin(name: String, userConfig: GradleDokkaSourceSet) =
+ kotlinTasks.find { it.name == name }
+ ?.let { configExtractor.extractFromKotlinTasks(listOf(it)) }
+ ?.singleOrNull()
+ ?.let { mergeUserConfigurationAndPlatformData(userConfig, it) }
+ ?: configExtractor.extractFromJavaPlugin()
+ ?.let { mergeUserConfigurationAndPlatformData(userConfig, it) }
+ ?: userConfig
+
+ private fun mergeUserConfigurationAndPlatformData(
+ userConfig: GradleDokkaSourceSet,
+ autoConfig: PlatformData
+ ) = userConfig.copy().apply {
+ sourceRoots.addAll(userConfig.sourceRoots.union(autoConfig.sourceRoots.toSourceRoots()).distinct())
+ dependentSourceSets.addAll(userConfig.dependentSourceSets)
+ dependentSourceSets.addAll(autoConfig.dependentSourceSets.map { DokkaSourceSetID(project, it) })
+ classpath = userConfig.classpath.union(autoConfig.classpath.map { it.absolutePath }).distinct()
+ if (userConfig.platform == null && autoConfig.platform != "")
+ platform = autoConfig.platform
+ }
+
+ private fun configureDefault(config: GradleDokkaSourceSet): GradleDokkaSourceSet {
+ if (config.moduleDisplayName.isBlank()) {
+ config.moduleDisplayName = project.name
}
- protected open fun defaultPassConfiguration(
- config: GradlePassConfigurationImpl,
- globalConfig: GradlePassConfigurationImpl?
- ): GradlePassConfigurationImpl {
- if (config.moduleName == "") {
- config.moduleName = project.name
+ if (config.displayName.isBlank()) {
+ config.displayName = config.name.substringBeforeLast("Main", config.platform.toString())
}
- if (config.targets.isEmpty() && multiplatform.isNotEmpty()){
- config.targets = listOf(config.name)
+
+ if (project.isAndroidProject() && !config.noAndroidSdkLink) {
+ config.externalDocumentationLinks.add(ANDROID_REFERENCE_URL)
+ }
+
+ if (config.platform?.isNotBlank() == true) {
+ config.analysisPlatform = dokkaPlatformFromString(config.platform.toString())
}
- config.classpath = (config.classpath as List<Any>).map { it.toString() }.distinct() // Workaround for Groovy's GStringImpl
+
+ // Workaround for Groovy's GStringImpl
+ config.classpath = (config.classpath as List<Any>).map { it.toString() }.distinct()
config.sourceRoots = config.sourceRoots.distinct().toMutableList()
config.samples = config.samples.map { project.file(it).absolutePath }
config.includes = config.includes.map { project.file(it).absolutePath }
config.suppressedFiles += collectSuppressedFiles(config.sourceRoots)
- if (project.isAndroidProject() && !config.noAndroidSdkLink) { // TODO: introduce Android as a separate Dokka platform?
- config.externalDocumentationLinks.add(ANDROID_REFERENCE_URL)
- }
- if (config.platform != null && config.platform.toString().isNotEmpty()) {
- config.analysisPlatform = dokkaPlatformFromString(config.platform.toString())
- }
- if (globalConfig != null) {
- config.perPackageOptions.addAll(globalConfig.perPackageOptions)
- config.externalDocumentationLinks.addAll(globalConfig.externalDocumentationLinks)
- config.sourceLinks.addAll(globalConfig.sourceLinks)
- config.samples += globalConfig.samples.map { project.file(it).absolutePath }
- config.includes += globalConfig.includes.map { project.file(it).absolutePath }
- }
+
return config
}
private fun dokkaPlatformFromString(platform: String) = when (platform.toLowerCase()) {
- KotlinPlatformType.androidJvm.toString().toLowerCase(), "androidjvm", "android" -> Platform.jvm
+ "androidjvm", "android" -> Platform.jvm
"metadata" -> Platform.common
else -> Platform.fromString(platform)
}
@@ -283,16 +273,16 @@ open class DokkaTask : DefaultTask() {
// Needed for Gradle incremental build
@InputFiles
- fun getInputFiles(): FileCollection {
- val config = collectConfigurations()
- return project.files(config.flatMap { it.sourceRoots }.map { project.fileTree(File(it.path)) }) +
+ fun getInputFiles(): FileCollection = configuredDokkaSourceSets.let { config ->
+ project.files(config.flatMap { it.sourceRoots }.map { project.fileTree(File(it.path)) }) +
project.files(config.flatMap { it.includes }) +
project.files(config.flatMap { it.samples }.map { project.fileTree(File(it)) })
}
@Classpath
fun getInputClasspath(): FileCollection =
- project.files((collectConfigurations().flatMap { it.classpath } as List<Any>).map { project.fileTree(File(it.toString())) })
+ project.files((configuredDokkaSourceSets.flatMap { it.classpath } as List<Any>)
+ .map { project.fileTree(File(it.toString())) })
companion object {
const val COLORS_ENABLED_PROPERTY = "kotlin.colors.enabled"
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt
index f8965993..468f597f 100644
--- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt
@@ -1,9 +1,6 @@
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
+import java.lang.reflect.*
/**
@@ -13,8 +10,8 @@ import java.lang.reflect.Proxy
* to create access proxy for [delegate] into [targetClassLoader].
*/
@Suppress("UNCHECKED_CAST")
-inline fun <reified T : Any> automagicTypedProxy(targetClassLoader: ClassLoader, delegate: Any): T =
- automagicProxy(targetClassLoader, T::class.java, delegate) as T
+internal inline fun <reified T : Any> automagicTypedProxy(targetClassLoader: ClassLoader, delegate: Any): T =
+ automagicProxy(targetClassLoader, T::class.java, delegate) as T
/**
@@ -24,14 +21,14 @@ inline fun <reified T : Any> automagicTypedProxy(targetClassLoader: ClassLoader,
* to create access proxy for [delegate] into [targetClassLoader].
*
*/
-fun automagicProxy(targetClassLoader: ClassLoader, targetType: Class<*>, delegate: Any): Any =
- Proxy.newProxyInstance(
- targetClassLoader,
- arrayOf(targetType),
- DelegatedInvocationHandler(delegate)
- )
+internal fun automagicProxy(targetClassLoader: ClassLoader, targetType: Class<*>, delegate: Any): Any =
+ Proxy.newProxyInstance(
+ targetClassLoader,
+ arrayOf(targetType),
+ DelegatedInvocationHandler(delegate)
+ )
-class DelegatedInvocationHandler(private val delegate: Any) : InvocationHandler {
+internal class DelegatedInvocationHandler(private val delegate: Any) : InvocationHandler {
@Throws(Throwable::class)
override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ReflectDsl.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ReflectDsl.kt
new file mode 100644
index 00000000..4b511022
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ReflectDsl.kt
@@ -0,0 +1,72 @@
+package org.jetbrains.dokka
+
+import kotlin.reflect.*
+import kotlin.reflect.full.memberFunctions
+import kotlin.reflect.full.memberProperties
+import kotlin.reflect.jvm.isAccessible
+
+internal object ReflectDsl {
+
+ class CallOrPropAccess(private val receiver: Any?,
+ private val clz: KClass<*>,
+ private val selector: String) {
+
+ @Suppress("UNCHECKED_CAST")
+ operator fun <T : Any?> invoke(vararg a: Any?): T {
+ return func!!.call(receiver, *a) as T
+ }
+
+ operator fun get(s: String): CallOrPropAccess {
+ return v<Any?>()!![s]
+ }
+
+ val func: KFunction<*>? by lazy { clz.memberFunctions.find { it.name == selector } }
+ val prop: KProperty<*>? by lazy { clz.memberProperties.find { it.name == selector } }
+
+ fun takeIfIsFunc(): CallOrPropAccess? = if (func != null) this else null
+
+ fun takeIfIsProp(): CallOrPropAccess? = if (prop != null) this else null
+
+ @Suppress("UNCHECKED_CAST")
+ fun <T : Any?> v(): T {
+ val prop = prop!!
+ return try {
+ prop.getter.apply { isAccessible = true }.call(receiver) as T
+ } catch (e: KotlinNullPointerException) {
+ // Hack around kotlin-reflect bug KT-18480
+ val jclass = clz.java
+ val customGetterName = prop.getter.name
+ val getterName = if (customGetterName.startsWith("<")) "get" + prop.name.capitalize() else customGetterName
+ val getter = jclass.getDeclaredMethod(getterName)
+ getter.isAccessible = true
+
+ getter.invoke(receiver) as T
+
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ fun v(x: Any?) {
+ (prop as KMutableProperty).setter.apply { isAccessible = true }.call(receiver, x)
+ }
+
+
+ }
+
+ operator fun Any.get(s: String): CallOrPropAccess {
+ val clz = this.javaClass.kotlin
+ return CallOrPropAccess(this, clz, s)
+ }
+
+ operator fun Any.get(s: String, clz: Class<*>): CallOrPropAccess {
+ val kclz = clz.kotlin
+ return CallOrPropAccess(this, kclz, s)
+ }
+
+ operator fun Any.get(s: String, clz: KClass<*>): CallOrPropAccess {
+ return CallOrPropAccess(this, clz, s)
+ }
+
+ inline infix fun Any.isInstance(clz: Class<*>?): Boolean = clz != null && clz.isAssignableFrom(this.javaClass)
+ inline infix fun Any.isNotInstance(clz: Class<*>?): Boolean = !(this isInstance clz)
+} \ No newline at end of file
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt
index 65afad04..b6b8399c 100644
--- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt
@@ -1,12 +1,19 @@
+@file:Suppress("FunctionName")
+
package org.jetbrains.dokka.gradle
+import com.android.build.gradle.api.AndroidSourceSet
import groovy.lang.Closure
import org.gradle.api.Action
+import org.gradle.api.Project
import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.util.ConfigureUtil
import org.jetbrains.dokka.DokkaConfiguration
import org.jetbrains.dokka.DokkaConfiguration.*
+import org.jetbrains.dokka.DokkaDefaults
+import org.jetbrains.dokka.DokkaSourceSetID
import org.jetbrains.dokka.Platform
import java.io.File
import java.io.Serializable
@@ -14,8 +21,10 @@ import java.net.URL
import java.util.concurrent.Callable
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.memberProperties
+import org.gradle.api.tasks.SourceSet as GradleSourceSet
+import org.jetbrains.kotlin.gradle.model.SourceSet as KotlinSourceSet
-class GradleSourceRootImpl: SourceRoot, Serializable {
+class GradleSourceRootImpl : SourceRoot, Serializable {
override var path: String = ""
set(value) {
field = File(value).absolutePath
@@ -24,34 +33,113 @@ class GradleSourceRootImpl: SourceRoot, Serializable {
override fun toString(): String = path
}
-open class GradlePassConfigurationImpl(@Transient val name: String = ""): PassConfiguration {
- @Input @Optional override var classpath: List<String> = emptyList()
- @Input override var moduleName: String = ""
- @Input override var sourceRoots: MutableList<SourceRoot> = mutableListOf()
- @Input override var samples: List<String> = emptyList()
- @Input override var includes: List<String> = emptyList()
- @Input override var includeNonPublic: Boolean = false
- @Input override var includeRootPackage: Boolean = false
- @Input override var reportUndocumented: Boolean = false
- @Input override var skipEmptyPackages: Boolean = true
- @Input override var skipDeprecated: Boolean = false
- @Input override var jdkVersion: Int = 6
- @Input override var sourceLinks: MutableList<SourceLinkDefinition> = mutableListOf()
- @Input override var perPackageOptions: MutableList<PackageOptions> = mutableListOf()
- @Input override var externalDocumentationLinks: MutableList<ExternalDocumentationLink> = mutableListOf()
- @Input @Optional override var languageVersion: String? = null
- @Input @Optional override var apiVersion: String? = null
- @Input override var noStdlibLink: Boolean = false
- @Input override var noJdkLink: Boolean = false
- @Input var noAndroidSdkLink: Boolean = false
- @Input override var suppressedFiles: List<String> = emptyList()
- @Input override var collectInheritedExtensionsFromLibraries: Boolean = false
- @Input override var analysisPlatform: Platform = Platform.DEFAULT
- @Input @Optional var platform: String? = null
- @Input override var targets: List<String> = emptyList()
- @Input @Optional override var sinceKotlin: String? = null
- @Transient var collectKotlinTasks: (() -> List<Any?>?)? = null
- @Input @Transient var androidVariants: List<String> = emptyList()
+open class GradleDokkaSourceSet constructor(
+ @Transient @get:Input val name: String,
+ @Transient @get:Internal internal val project: Project
+) : DokkaSourceSet {
+
+ @Input
+ @Optional
+ override var classpath: List<String> = emptyList()
+
+ @Input
+ override var moduleDisplayName: String = ""
+
+ @Input
+ override var displayName: String = ""
+
+ @get:Internal
+ override val sourceSetID: DokkaSourceSetID = DokkaSourceSetID(project, name)
+
+ @Input
+ override var sourceRoots: MutableList<SourceRoot> = mutableListOf()
+
+ @Input
+ override var dependentSourceSets: MutableSet<DokkaSourceSetID> = mutableSetOf()
+
+ @Input
+ override var samples: List<String> = emptyList()
+
+ @Input
+ override var includes: List<String> = emptyList()
+
+ @Input
+ override var includeNonPublic: Boolean = DokkaDefaults.includeNonPublic
+
+ @Input
+ override var includeRootPackage: Boolean = DokkaDefaults.includeRootPackage
+
+ @Input
+ override var reportUndocumented: Boolean = DokkaDefaults.reportUndocumented
+
+ @Input
+ override var skipEmptyPackages: Boolean = DokkaDefaults.skipEmptyPackages
+
+ @Input
+ override var skipDeprecated: Boolean = DokkaDefaults.skipDeprecated
+
+ @Input
+ override var jdkVersion: Int = DokkaDefaults.jdkVersion
+
+ @Input
+ override var sourceLinks: MutableList<SourceLinkDefinition> = mutableListOf()
+
+ @Input
+ override var perPackageOptions: MutableList<PackageOptions> = mutableListOf()
+
+ @Input
+ override var externalDocumentationLinks: MutableList<ExternalDocumentationLink> = mutableListOf()
+
+ @Input
+ @Optional
+ override var languageVersion: String? = null
+
+ @Input
+ @Optional
+ override var apiVersion: String? = null
+
+ @Input
+ override var noStdlibLink: Boolean = DokkaDefaults.noStdlibLink
+
+ @Input
+ override var noJdkLink: Boolean = DokkaDefaults.noJdkLink
+
+ @Input
+ var noAndroidSdkLink: Boolean = false
+
+ @Input
+ override var suppressedFiles: List<String> = emptyList()
+
+ @Input
+ override var analysisPlatform: Platform = DokkaDefaults.analysisPlatform
+
+ @Input
+ @Optional
+ var platform: String? = null
+
+ @Internal
+ @Transient
+ var collectKotlinTasks: (() -> List<Any?>?)? = null
+
+ fun DokkaSourceSetID(sourceSetName: String): DokkaSourceSetID {
+ return DokkaSourceSetID(project, sourceSetName)
+ }
+
+ fun dependsOn(sourceSet: GradleSourceSet) {
+ dependsOn(DokkaSourceSetID(sourceSet.name))
+ }
+
+ fun dependsOn(sourceSet: DokkaSourceSet) {
+ dependsOn(sourceSet.sourceSetID)
+ }
+
+ fun dependsOn(sourceSetName: String) {
+ dependsOn(DokkaSourceSetID(sourceSetName))
+ }
+
+ fun dependsOn(sourceSetID: DokkaSourceSetID) {
+ dependentSourceSets.add(sourceSetID)
+ }
fun kotlinTasks(taskSupplier: Callable<List<Any>>) {
collectKotlinTasks = { taskSupplier.call() }
@@ -95,70 +183,76 @@ open class GradlePassConfigurationImpl(@Transient val name: String = ""): PassCo
}
fun externalDocumentationLink(c: Closure<Unit>) {
- val builder = ConfigureUtil.configure(c, GradleExternalDocumentationLinkImpl.Builder())
- externalDocumentationLinks.add(builder.build())
+ val link = ConfigureUtil.configure(c, GradleExternalDocumentationLinkImpl())
+ externalDocumentationLinks.add(ExternalDocumentationLink.Builder(link.url, link.packageListUrl).build())
}
- fun externalDocumentationLink(action: Action<in GradleExternalDocumentationLinkImpl.Builder>) {
- val builder = GradleExternalDocumentationLinkImpl.Builder()
- action.execute(builder)
- externalDocumentationLinks.add(builder.build())
+ fun externalDocumentationLink(action: Action<in GradleExternalDocumentationLinkImpl>) {
+ val link = GradleExternalDocumentationLinkImpl()
+ action.execute(link)
+ externalDocumentationLinks.add(ExternalDocumentationLink.Builder(link.url, link.packageListUrl).build())
}
}
+fun GradleDokkaSourceSet.dependsOn(sourceSet: KotlinSourceSet) {
+ dependsOn(DokkaSourceSetID(sourceSet.name))
+}
+
+fun GradleDokkaSourceSet.dependsOn(sourceSet: org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet) {
+ dependsOn(DokkaSourceSetID(sourceSet.name))
+}
+
+fun GradleDokkaSourceSet.dependsOn(sourceSet: AndroidSourceSet) {
+ dependsOn(DokkaSourceSetID(sourceSet.name))
+}
+
class GradleSourceLinkDefinitionImpl : SourceLinkDefinition, Serializable {
override var path: String = ""
override var url: String = ""
override var lineSuffix: String? = null
}
-class GradleExternalDocumentationLinkImpl(
- override val url: URL,
- override val packageListUrl: URL
-): ExternalDocumentationLink, Serializable {
- open class Builder(open var url: URL? = null,
- open var packageListUrl: URL? = null) {
-
- constructor(root: String, packageList: String? = null) : this(URL(root), packageList?.let { URL(it) })
+class GradleExternalDocumentationLinkImpl : ExternalDocumentationLink, Serializable {
+ override var url: URL = URL("http://")
+ override var packageListUrl: URL = URL("http://")
+}
- fun build(): ExternalDocumentationLink =
- if (packageListUrl != null && url != null)
- GradleExternalDocumentationLinkImpl(url!!, packageListUrl!!)
- else if (url != null)
- GradleExternalDocumentationLinkImpl(url!!, URL(url!!, "package-list"))
- else
- throw IllegalArgumentException("url or url && packageListUrl must not be null for external documentation link")
- }
+class GradleDokkaModuleDescription : DokkaModuleDescription {
+ override var name: String = ""
+ override var path: String = ""
+ override var docFile: String = ""
}
-class GradleDokkaConfigurationImpl: DokkaConfiguration {
+class GradleDokkaConfigurationImpl : DokkaConfiguration {
override var outputDir: String = ""
- override var format: String = "html"
- override var generateIndexPages: Boolean = false
- override var cacheRoot: String? = null
- override var impliedPlatforms: List<String> = emptyList()
- override var passesConfigurations: List<GradlePassConfigurationImpl> = emptyList()
+ override var cacheRoot: String? = DokkaDefaults.cacheRoot
+ override var offlineMode: Boolean = DokkaDefaults.offlineMode
+ override var failOnWarning: Boolean = DokkaDefaults.failOnWarning
+ override var sourceSets: List<GradleDokkaSourceSet> = emptyList()
+ override var pluginsClasspath: List<File> = emptyList()
+ override var pluginsConfiguration: Map<String, String> = mutableMapOf()
+ override var modules: List<GradleDokkaModuleDescription> = emptyList()
}
-class GradlePackageOptionsImpl: PackageOptions, Serializable {
+class GradlePackageOptionsImpl : PackageOptions, Serializable {
override var prefix: String = ""
- override var includeNonPublic: Boolean = false
- override var reportUndocumented: Boolean = false
- override var skipDeprecated: Boolean = false
- override var suppress: Boolean = false
+ override var includeNonPublic: Boolean = DokkaDefaults.includeNonPublic
+ override var reportUndocumented: Boolean = DokkaDefaults.reportUndocumented
+ override var skipDeprecated: Boolean = DokkaDefaults.skipDeprecated
+ override var suppress: Boolean = DokkaDefaults.suppress
}
-fun GradlePassConfigurationImpl.copy(): GradlePassConfigurationImpl {
- val newObj = GradlePassConfigurationImpl(this.name)
+internal fun GradleDokkaSourceSet.copy(): GradleDokkaSourceSet {
+ val newObj = GradleDokkaSourceSet(this.name, this.project)
this::class.memberProperties.forEach { field ->
if (field is KMutableProperty<*>) {
- val value = field.getter.call(this)
- if (value is Collection<*>) {
- field.setter.call(newObj, value.toMutableList())
- } else {
- field.setter.call(newObj, field.getter.call(this))
+ when (val value = field.getter.call(this)) {
+ is List<*> -> field.setter.call(newObj, value.toMutableList())
+ is Set<*> -> field.setter.call(newObj, value.toMutableSet())
+ else -> field.setter.call(newObj, field.getter.call(this))
}
+
}
}
return newObj
-} \ No newline at end of file
+}
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/defaultDokkaOutputDirectory.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/defaultDokkaOutputDirectory.kt
new file mode 100644
index 00000000..0a7ab534
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/defaultDokkaOutputDirectory.kt
@@ -0,0 +1,13 @@
+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/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaConfigurations.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaConfigurations.kt
new file mode 100644
index 00000000..20f54cc5
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaConfigurations.kt
@@ -0,0 +1,41 @@
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.attributes.Usage
+
+internal fun Project.maybeCreateDokkaDefaultPluginConfiguration(): Configuration {
+ return configurations.maybeCreate("dokkaPlugin") {
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, "java-runtime"))
+ isCanBeConsumed = false
+ }
+}
+
+internal fun Project.maybeCreateDokkaDefaultRuntimeConfiguration(): Configuration {
+ return configurations.maybeCreate("dokkaRuntime") {
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, "java-runtime"))
+ isCanBeConsumed = false
+ }
+}
+
+internal fun Project.maybeCreateDokkaPluginConfiguration(dokkaTaskName: String): Configuration {
+ return project.configurations.maybeCreate("${dokkaTaskName}Plugin") {
+ extendsFrom(maybeCreateDokkaDefaultPluginConfiguration())
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, "java-runtime"))
+ isCanBeConsumed = false
+ defaultDependencies { dependencies ->
+ dependencies.add(project.dokkaArtifacts.dokkaBase)
+ }
+ }
+}
+
+internal fun Project.maybeCreateDokkaRuntimeConfiguration(dokkaTaskName: String): Configuration {
+ return project.configurations.maybeCreate("${dokkaTaskName}Runtime") {
+ extendsFrom(maybeCreateDokkaDefaultRuntimeConfiguration())
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, "java-runtime"))
+ isCanBeConsumed = false
+ defaultDependencies { dependencies ->
+ dependencies.add(project.dokkaArtifacts.dokkaCore)
+ }
+ }
+}
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt
index 7ed29c58..d70448f1 100644
--- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt
@@ -2,57 +2,49 @@ package org.jetbrains.dokka.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
-import org.gradle.api.artifacts.Configuration
-import org.gradle.util.GradleVersion
-import java.io.File
-import java.io.InputStream
-import java.util.*
-
-internal const val CONFIGURATION_EXTENSION_NAME = "configuration"
-internal const val MULTIPLATFORM_EXTENSION_NAME = "multiplatform"
+import org.gradle.kotlin.dsl.register
open class DokkaPlugin : Plugin<Project> {
- private val taskName = "dokka"
-
override fun apply(project: Project) {
- loadDokkaVersion()
- val dokkaRuntimeConfiguration = addConfiguration(project)
- addTasks(project, dokkaRuntimeConfiguration, DokkaTask::class.java)
- }
- private fun loadDokkaVersion() = DokkaVersion.loadFrom(javaClass.getResourceAsStream("/META-INF/gradle-plugins/org.jetbrains.dokka.properties"))
+ project.setupDokkaTasks("dokkaHtml")
- private fun addConfiguration(project: Project) =
- project.configurations.create("dokkaRuntime").apply {
- defaultDependencies{ dependencies -> dependencies.add(project.dependencies.create("org.jetbrains.dokka:dokka-fatjar:${DokkaVersion.version}")) }
+ project.setupDokkaTasks("dokkaJavadoc") {
+ plugins.dependencies.add(project.dokkaArtifacts.javadocPlugin)
}
- protected open fun addTasks(project: Project, runtimeConfiguration: Configuration, taskClass: Class<out DokkaTask>) {
- if(GradleVersion.current() >= GradleVersion.version("4.10")) {
- project.tasks.register(taskName, taskClass)
- } else {
- project.tasks.create(taskName, taskClass)
+ project.setupDokkaTasks("dokkaGfm") {
+ plugins.dependencies.add(project.dokkaArtifacts.gfmPlugin)
}
- project.tasks.withType(taskClass) { task ->
- task.multiplatform = project.container(GradlePassConfigurationImpl::class.java)
- task.configuration = GradlePassConfigurationImpl()
- task.dokkaRuntime = runtimeConfiguration
- task.outputDirectory = File(project.buildDir, taskName).absolutePath
+
+ project.setupDokkaTasks("dokkaJekyll") {
+ plugins.dependencies.add(project.dokkaArtifacts.jekyllPlugin)
}
}
-}
-object DokkaVersion {
- var version: String? = null
+ /**
+ * Creates [DokkaTask], [DokkaMultimoduleTask] and [DokkaCollectorTask] for the given
+ * name and configuration.
+ */
+ private fun Project.setupDokkaTasks(name: String, configuration: AbstractDokkaTask.() -> Unit = {}) {
+ project.maybeCreateDokkaPluginConfiguration(name)
+ project.maybeCreateDokkaRuntimeConfiguration(name)
+ project.tasks.register<DokkaTask>(name) {
+ configuration()
+ }
- fun loadFrom(stream: InputStream) {
- version = Properties().apply {
- load(stream)
- }.getProperty("dokka-version")
+ if (project.subprojects.isNotEmpty()) {
+ val multimoduleName = "${name}Multimodule"
+ project.maybeCreateDokkaPluginConfiguration(multimoduleName)
+ project.maybeCreateDokkaRuntimeConfiguration(multimoduleName)
+ project.tasks.register<DokkaMultimoduleTask>(multimoduleName) {
+ dokkaTaskNames = dokkaTaskNames + name
+ configuration()
+ }
+
+ project.tasks.register<DokkaCollectorTask>("${name}Collector") {
+ dokkaTaskNames = dokkaTaskNames + name
+ }
+ }
}
}
-
-object ClassloaderContainer {
- @JvmField
- var fatJarClassLoader: ClassLoader? = null
-} \ No newline at end of file
diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt
index 31892e8e..b6c5cbd8 100644
--- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt
@@ -1,5 +1,6 @@
package org.jetbrains.dokka.gradle
+import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.UnknownDomainObjectException
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
@@ -7,25 +8,30 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
-fun Project.isAndroidProject() = try {
+internal fun Project.isAndroidProject() = try {
project.extensions.getByName("android")
true
-} catch(e: UnknownDomainObjectException) {
+} catch (e: UnknownDomainObjectException) {
false
-} catch(e: ClassNotFoundException) {
+} catch (e: ClassNotFoundException) {
false
}
-fun Project.isMultiplatformProject() = try {
+internal fun Project.isNotMultiplatformProject() = !isMultiplatformProject()
+
+internal fun Project.isMultiplatformProject() = try {
project.extensions.getByType(KotlinMultiplatformExtension::class.java)
true
-} catch(e: UnknownDomainObjectException) {
+} catch (e: UnknownDomainObjectException) {
false
-} catch (e: NoClassDefFoundError){
+} catch (e: NoClassDefFoundError) {
false
-} catch(e: ClassNotFoundException) {
+} catch (e: ClassNotFoundException) {
false
}
-fun KotlinTarget.isAndroidTarget() = this.platformType == KotlinPlatformType.androidJvm
-fun DokkaTask.isMultiplatformProject() = this.multiplatform.isNotEmpty() \ No newline at end of file
+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/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt
new file mode 100644
index 00000000..e981d6fe
--- /dev/null
+++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt
@@ -0,0 +1,48 @@
+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/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaTasksTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaTasksTest.kt
new file mode 100644
index 00000000..b948c540
--- /dev/null
+++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaTasksTest.kt
@@ -0,0 +1,65 @@
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.plugins.JavaBasePlugin
+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 DokkaTasksTest {
+
+ @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")
+ project.tasks.filter { "dokka" in it.name.toLowerCase() }.forEach { dokkaTask ->
+ assertEquals(
+ JavaBasePlugin.DOCUMENTATION_GROUP, dokkaTask.group,
+ "Expected task: ${dokkaTask.path} group to be ${JavaBasePlugin.DOCUMENTATION_GROUP}"
+ )
+ }
+ }
+}
diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt
new file mode 100644
index 00000000..7b78fb55
--- /dev/null
+++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt
@@ -0,0 +1,97 @@
+package org.jetbrains.dokka.gradle
+
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class KotlinDslDokkaTaskConfigurationTest {
+
+ @Test
+ fun `configure project using dokka extension function`() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka")
+ project.tasks.withType(DokkaTask::class.java).forEach { dokkaTask ->
+ dokkaTask.outputDirectory = "test"
+ }
+
+ project.tasks.withType(DokkaTask::class.java).forEach { dokkaTask ->
+ assertEquals("test", dokkaTask.outputDirectory)
+ }
+ }
+
+ @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") {
+ it.dependsOn("commonMain")
+ }
+
+ assertEquals(
+ 0, commonMain.dependentSourceSets.size,
+ "Expected no dependent source set in commonMain"
+ )
+
+ assertEquals(
+ 1, jvmMain.dependentSourceSets.size,
+ "Expected only one dependent source set in jvmMain"
+ )
+
+ assertEquals(
+ commonMain.sourceSetID, jvmMain.dependentSourceSets.single(),
+ "Expected jvmMain to depend on commonMain"
+ )
+
+ assertEquals(
+ DokkaSourceSetID(project.path, "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") {
+ it.dependsOn(commonMain)
+ }
+
+ assertEquals(
+ commonMain.sourceSetID, jvmMain.dependentSourceSets.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().run {
+ dokkaSourceSets.run {
+ val special = create("special") {
+ it.dependsOn(kotlin.sourceSets.getByName("main"))
+ }
+
+ assertEquals(
+ DokkaSourceSetID(project, "main"), special.dependentSourceSets.single()
+ )
+ }
+ }
+ }
+}