aboutsummaryrefslogtreecommitdiff
path: root/runners/gradle-plugin
diff options
context:
space:
mode:
Diffstat (limited to 'runners/gradle-plugin')
-rw-r--r--runners/gradle-plugin/build.gradle17
-rw-r--r--runners/gradle-plugin/src/main/kotlin/main.kt499
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt217
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt353
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt (renamed from runners/gradle-plugin/src/main/kotlin/ProxyUtils.kt)8
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt150
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt58
-rw-r--r--runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt19
8 files changed, 814 insertions, 507 deletions
diff --git a/runners/gradle-plugin/build.gradle b/runners/gradle-plugin/build.gradle
index 8e59a7be..ceb03bae 100644
--- a/runners/gradle-plugin/build.gradle
+++ b/runners/gradle-plugin/build.gradle
@@ -12,12 +12,17 @@ sourceCompatibility = 1.8
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
freeCompilerArgs += "-Xjsr305=strict"
- languageVersion = "1.2"
- apiVersion = "1.1"
+ languageVersion = language_version
+ apiVersion = language_version
jvmTarget = "1.8"
}
}
+repositories {
+ jcenter()
+ google()
+}
+
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
@@ -26,8 +31,13 @@ dependencies {
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) {
@@ -54,7 +64,6 @@ apply plugin: 'maven-publish'
publishing {
publications {
dokkaGradlePlugin(MavenPublication) { publication ->
-
artifactId = 'dokka-gradle-plugin'
artifact sourceJar {
@@ -77,7 +86,7 @@ pluginBundle {
website = 'https://www.kotlinlang.org/'
vcsUrl = 'https://github.com/kotlin/dokka.git'
description = 'Dokka, the Kotlin documentation tool'
- tags = ['dokka', 'kotlin', 'kdoc']
+ tags = ['dokka', 'kotlin', 'kdoc', 'android']
plugins {
dokkaGradlePlugin {
diff --git a/runners/gradle-plugin/src/main/kotlin/main.kt b/runners/gradle-plugin/src/main/kotlin/main.kt
deleted file mode 100644
index f4adc1c3..00000000
--- a/runners/gradle-plugin/src/main/kotlin/main.kt
+++ /dev/null
@@ -1,499 +0,0 @@
-package org.jetbrains.dokka.gradle
-
-import groovy.lang.Closure
-import org.gradle.api.Action
-import org.gradle.api.DefaultTask
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.Task
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.file.FileCollection
-import org.gradle.api.plugins.JavaBasePlugin
-import org.gradle.api.plugins.JavaPluginConvention
-import org.gradle.api.tasks.*
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.compile.AbstractCompile
-import org.jetbrains.dokka.*
-import org.jetbrains.dokka.ReflectDsl.isNotInstance
-import org.jetbrains.dokka.gradle.ClassloaderContainer.fatJarClassLoader
-import org.jetbrains.dokka.gradle.DokkaVersion.version
-import ru.yole.jkid.JsonExclude
-import ru.yole.jkid.serialization.serialize
-import java.io.File
-import java.io.InputStream
-import java.io.Serializable
-import java.net.URLClassLoader
-import java.util.*
-import java.util.concurrent.Callable
-import java.util.function.BiConsumer
-
-open class DokkaPlugin : Plugin<Project> {
-
- override fun apply(project: Project) {
- DokkaVersion.loadFrom(javaClass.getResourceAsStream("/META-INF/gradle-plugins/org.jetbrains.dokka.properties"))
- project.tasks.create("dokka", DokkaTask::class.java).apply {
- dokkaRuntime = project.configurations.create("dokkaRuntime")
- moduleName = project.name
- outputDirectory = File(project.buildDir, "dokka").absolutePath
- }
- }
-}
-
-object DokkaVersion {
- var version: String? = null
-
- fun loadFrom(stream: InputStream) {
- version = Properties().apply {
- load(stream)
- }.getProperty("dokka-version")
- }
-}
-
-
-object ClassloaderContainer {
- @JvmField
- var fatJarClassLoader: ClassLoader? = null
-}
-
-const val `deprecationMessage reportNotDocumented` = "Will be removed in 0.9.17, see dokka#243"
-
-open class DokkaTask : DefaultTask() {
-
- fun defaultKotlinTasks() = with(ReflectDsl) {
- val abstractKotlinCompileClz = try {
- project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE)
- } catch (cnfe: ClassNotFoundException) {
- logger.warn("$ABSTRACT_KOTLIN_COMPILE class not found, default kotlin tasks ignored")
- return@with emptyList<Task>()
- }
-
- return@with project.tasks.filter { it isInstance abstractKotlinCompileClz }.filter { "Test" !in it.name }
- }
-
- init {
- group = JavaBasePlugin.DOCUMENTATION_GROUP
- description = "Generates dokka documentation for Kotlin"
-
- @Suppress("LeakingThis")
- dependsOn(Callable { kotlinTasks.map { it.taskDependencies } })
- }
-
- @Input
- var moduleName: String = ""
- @Input
- var outputFormat: String = "html"
- var outputDirectory: String = ""
- var dokkaRuntime: Configuration? = null
-
- @Deprecated("Going to be removed in 0.9.16, use classpath + sourceDirs instead if kotlinTasks is not suitable for you")
- @Input var processConfigurations: List<Any?> = emptyList()
-
- @InputFiles var classpath: Iterable<File> = arrayListOf()
-
- @Input
- var includes: List<Any?> = arrayListOf()
- @Input
- var linkMappings: ArrayList<LinkMapping> = arrayListOf()
- @Input
- var samples: List<Any?> = arrayListOf()
- @Input
- var jdkVersion: Int = 6
- @Input
- var sourceDirs: Iterable<File> = emptyList()
-
- @Input
- var sourceRoots: MutableList<SourceRoot> = arrayListOf()
-
- @Input
- var dokkaFatJar: Any = "org.jetbrains.dokka:dokka-fatjar:$version"
-
- @Input var includeNonPublic = false
- @Input var skipDeprecated = false
- @Input var skipEmptyPackages = true
-
- @Deprecated(`deprecationMessage reportNotDocumented`, replaceWith = ReplaceWith("reportUndocumented"))
- var reportNotDocumented
- get() = reportUndocumented
- set(value) {
- logger.warn("Dokka: reportNotDocumented is deprecated and " + `deprecationMessage reportNotDocumented`.decapitalize())
- reportUndocumented = value
- }
-
- @Input var reportUndocumented = true
- @Input var perPackageOptions: MutableList<PackageOptions> = arrayListOf()
- @Input var impliedPlatforms: MutableList<String> = arrayListOf()
-
- @Input var externalDocumentationLinks = mutableListOf<DokkaConfiguration.ExternalDocumentationLink>()
-
- @Input var noStdlibLink: Boolean = false
-
- @Input
- var noJdkLink: Boolean = false
-
- @Optional @Input
- var cacheRoot: String? = null
-
-
- @Optional @Input
- var languageVersion: String? = null
-
- @Optional @Input
- var apiVersion: String? = null
-
- @Input
- var collectInheritedExtensionsFromLibraries: Boolean = false
-
- @get:Internal
- internal val kotlinCompileBasedClasspathAndSourceRoots: ClasspathAndSourceRoots by lazy { extractClasspathAndSourceRootsFromKotlinTasks() }
-
-
- private var kotlinTasksConfigurator: () -> List<Any?>? = { defaultKotlinTasks() }
- private val kotlinTasks: List<Task> by lazy { extractKotlinCompileTasks() }
-
- fun kotlinTasks(taskSupplier: Callable<List<Any>>) {
- kotlinTasksConfigurator = { taskSupplier.call() }
- }
-
- fun kotlinTasks(closure: Closure<Any?>) {
- kotlinTasksConfigurator = { closure.call() as? List<Any?> }
- }
-
- fun linkMapping(action: Action<LinkMapping>) {
- val mapping = LinkMapping()
- action.execute(mapping)
-
- if (mapping.path.isEmpty()) {
- throw IllegalArgumentException("Link mapping should have dir")
- }
- if (mapping.url.isEmpty()) {
- throw IllegalArgumentException("Link mapping should have url")
- }
-
- linkMappings.add(mapping)
- }
-
- fun linkMapping(closure: Closure<Unit>) {
- linkMapping(Action { mapping ->
- closure.delegate = mapping
- closure.call()
- })
- }
-
- fun sourceRoot(action: Action<SourceRoot>) {
- val sourceRoot = SourceRoot()
- action.execute(sourceRoot)
- sourceRoots.add(sourceRoot)
- }
-
- fun sourceRoot(closure: Closure<Unit>) {
- sourceRoot(Action { sourceRoot ->
- closure.delegate = sourceRoot
- closure.call()
- })
- }
-
- fun packageOptions(action: Action<PackageOptions>) {
- val packageOptions = PackageOptions()
- action.execute(packageOptions)
- perPackageOptions.add(packageOptions)
- }
-
- fun packageOptions(closure: Closure<Unit>) {
- packageOptions(Action { packageOptions ->
- closure.delegate = packageOptions
- closure.call()
- })
- }
-
- fun externalDocumentationLink(action: Action<DokkaConfiguration.ExternalDocumentationLink.Builder>) {
- val builder = DokkaConfiguration.ExternalDocumentationLink.Builder()
- action.execute(builder)
- externalDocumentationLinks.add(builder.build())
- }
-
- fun externalDocumentationLink(closure: Closure<Unit>) {
- externalDocumentationLink(Action { builder ->
- closure.delegate = builder
- closure.call()
- })
- }
-
- fun tryResolveFatJar(project: Project): Set<File> {
- return try {
- dokkaRuntime!!.resolve()
- } catch (e: Exception) {
- project.parent?.let { tryResolveFatJar(it) } ?: throw e
- }
- }
-
- fun loadFatJar() {
- if (fatJarClassLoader == null) {
- val jars = if (dokkaFatJar is File)
- setOf(dokkaFatJar as File)
- else
- tryResolveFatJar(project)
- fatJarClassLoader = URLClassLoader(jars.map { it.toURI().toURL() }.toTypedArray(), ClassLoader.getSystemClassLoader().parent)
- }
- }
-
- internal data class ClasspathAndSourceRoots(val classpathFileCollection: FileCollection, val sourceRoots: List<File>) : Serializable
-
- private fun extractKotlinCompileTasks(): List<Task> {
- val inputList = (kotlinTasksConfigurator.invoke() ?: emptyList()).filterNotNull()
- val (paths, other) = inputList.partition { it is String }
-
- val taskContainer = project.tasks
-
- val tasksByPath = paths.map { taskContainer.findByPath(it as String) ?: throw IllegalArgumentException("Task with path '$it' not found") }
-
- other
- .filter { it !is Task || it isNotInstance getAbstractKotlinCompileFor(it) }
- .forEach { throw IllegalArgumentException("Illegal entry in kotlinTasks, must be subtype of $ABSTRACT_KOTLIN_COMPILE or String, but was $it") }
-
- tasksByPath
- .filter { it == null || it isNotInstance getAbstractKotlinCompileFor(it) }
- .forEach { throw IllegalArgumentException("Illegal task path in kotlinTasks, must be subtype of $ABSTRACT_KOTLIN_COMPILE, but was $it") }
-
-
- return (tasksByPath + other) as List<Task>
- }
-
- private fun extractClasspathAndSourceRootsFromKotlinTasks(): ClasspathAndSourceRoots {
-
- val allTasks = kotlinTasks
-
- val allClasspath = mutableSetOf<File>()
- var allClasspathFileCollection: FileCollection = project.files()
- val allSourceRoots = mutableSetOf<File>()
-
- allTasks.forEach {
-
- logger.debug("Dokka found AbstractKotlinCompile task: $it")
- with(ReflectDsl) {
- val taskSourceRoots: List<File> = it["sourceRootsContainer"]["sourceRoots"].v()
-
- val abstractKotlinCompileClz = getAbstractKotlinCompileFor(it)!!
-
- val taskClasspath: Iterable<File> =
- (it["getClasspath", AbstractCompile::class].takeIfIsFunc()?.invoke()
- ?: it["compileClasspath", abstractKotlinCompileClz].takeIfIsProp()?.v()
- ?: it["getClasspath", abstractKotlinCompileClz]())
-
- if (taskClasspath is FileCollection) {
- allClasspathFileCollection += taskClasspath
- } else {
- allClasspath += taskClasspath
- }
- allSourceRoots += taskSourceRoots.filter { it.exists() }
- }
- }
-
- return ClasspathAndSourceRoots(allClasspathFileCollection + project.files(allClasspath), allSourceRoots.toList())
- }
-
- private fun Iterable<File>.toSourceRoots(): List<SourceRoot> = this.filter { it.exists() }.map { SourceRoot().apply { path = it.path } }
-
- protected open fun collectSuppressedFiles(sourceRoots: List<SourceRoot>): List<String> = emptyList()
-
- @TaskAction
- fun generate() {
- if (dokkaRuntime == null){
- dokkaRuntime = project.configurations.getByName("dokkaRuntime")
- }
-
- dokkaRuntime?.defaultDependencies{ dependencies -> dependencies.add(project.dependencies.create(dokkaFatJar)) }
- val kotlinColorsEnabledBefore = System.getProperty(COLORS_ENABLED_PROPERTY) ?: "false"
- System.setProperty(COLORS_ENABLED_PROPERTY, "false")
- try {
- loadFatJar()
-
- val (tasksClasspath, tasksSourceRoots) = kotlinCompileBasedClasspathAndSourceRoots
-
- val project = project
- val sourceRoots = collectSourceRoots() + tasksSourceRoots.toSourceRoots()
-
- if (sourceRoots.isEmpty()) {
- logger.warn("No source directories found: skipping dokka generation")
- return
- }
-
- val fullClasspath = collectClasspathFromOldSources() + tasksClasspath + classpath
-
- val bootstrapClass = fatJarClassLoader!!.loadClass("org.jetbrains.dokka.DokkaBootstrapImpl")
-
- val bootstrapInstance = bootstrapClass.constructors.first().newInstance()
-
- val bootstrapProxy: DokkaBootstrap = automagicTypedProxy(javaClass.classLoader, bootstrapInstance)
-
- val configuration = SerializeOnlyDokkaConfiguration(
- moduleName,
- fullClasspath.map { it.absolutePath },
- sourceRoots,
- samples.filterNotNull().map { project.file(it).absolutePath },
- includes.filterNotNull().map { project.file(it).absolutePath },
- outputDirectory,
- outputFormat,
- includeNonPublic,
- false,
- reportUndocumented,
- skipEmptyPackages,
- skipDeprecated,
- jdkVersion,
- true,
- linkMappings,
- impliedPlatforms,
- perPackageOptions,
- externalDocumentationLinks,
- noStdlibLink,
- noJdkLink,
- cacheRoot,
- collectSuppressedFiles(sourceRoots),
- languageVersion,
- apiVersion,
- collectInheritedExtensionsFromLibraries
- )
-
-
- bootstrapProxy.configure(
- BiConsumer { level, message ->
- when (level) {
- "info" -> logger.info(message)
- "warn" -> logger.warn(message)
- "error" -> logger.error(message)
- }
- },
- serialize(configuration)
- )
-
- bootstrapProxy.generate()
-
- } finally {
- System.setProperty(COLORS_ENABLED_PROPERTY, kotlinColorsEnabledBefore)
- }
- }
-
- private fun collectClasspathFromOldSources(): List<File> {
-
- val allConfigurations = project.configurations
-
- val fromConfigurations =
- processConfigurations.flatMap { allConfigurations.getByName(it.toString()) }
-
- return fromConfigurations
- }
-
- private fun collectSourceRoots(): List<SourceRoot> {
- val sourceDirs = if (sourceDirs.any()) {
- logger.info("Dokka: Taking source directories provided by the user")
- sourceDirs.toSet()
- } else if (kotlinTasks.isEmpty()) {
- project.convention.findPlugin(JavaPluginConvention::class.java)?.let { javaPluginConvention ->
- logger.info("Dokka: Taking source directories from default java plugin")
- val sourceSets = javaPluginConvention.sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME)
- sourceSets?.allSource?.srcDirs
- }
- } else {
- emptySet()
- }
-
- return sourceRoots + (sourceDirs?.toSourceRoots() ?: emptyList())
- }
-
-
- @Classpath
- fun getInputClasspath(): FileCollection {
- val (classpathFileCollection) = extractClasspathAndSourceRootsFromKotlinTasks()
- return project.files(collectClasspathFromOldSources() + classpath) + classpathFileCollection
- }
-
- @InputFiles
- fun getInputFiles(): FileCollection {
- val (_, tasksSourceRoots) = extractClasspathAndSourceRootsFromKotlinTasks()
- return project.files(tasksSourceRoots.map { project.fileTree(it) }) +
- project.files(collectSourceRoots().map { project.fileTree(File(it.path)) }) +
- project.files(includes) +
- project.files(samples.filterNotNull().map { project.fileTree(it) })
- }
-
- @OutputDirectory
- fun getOutputDirectoryAsFile(): File = project.file(outputDirectory)
-
- companion object {
- const val COLORS_ENABLED_PROPERTY = "kotlin.colors.enabled"
- const val ABSTRACT_KOTLIN_COMPILE = "org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile"
-
- private fun getAbstractKotlinCompileFor(task: Task) = try {
- task.project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE)
- } catch (e: ClassNotFoundException) {
- null
- }
- }
-}
-
-class SourceRoot : DokkaConfiguration.SourceRoot, Serializable {
- override var path: String = ""
- set(value) {
- field = File(value).absolutePath
- }
-
- override var platforms: List<String> = arrayListOf()
-
- override fun toString(): String {
- return "${platforms.joinToString()}::$path"
- }
-}
-
-open class LinkMapping : Serializable, DokkaConfiguration.SourceLinkDefinition {
- @JsonExclude
- var dir: String
- get() = path
- set(value) {
- if (value.contains("\\"))
- throw java.lang.IllegalArgumentException("Incorrect dir property, only Unix based path allowed.")
- else path = value
- }
-
- override var path: String = ""
- override var url: String = ""
-
- @JsonExclude
- var suffix: String?
- get() = lineSuffix
- set(value) {
- lineSuffix = value
- }
-
- override var lineSuffix: String? = null
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other?.javaClass != javaClass) return false
-
- other as LinkMapping
-
- if (path != other.path) return false
- if (url != other.url) return false
- if (lineSuffix != other.lineSuffix) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = path.hashCode()
- result = 31 * result + url.hashCode()
- result = 31 * result + (lineSuffix?.hashCode() ?: 0)
- return result
- }
-
- companion object {
- const val serialVersionUID: Long = -8133501684312445981L
- }
-}
-
-class PackageOptions : Serializable, DokkaConfiguration.PackageOptions {
- override var prefix: String = ""
- override var includeNonPublic: Boolean = false
- override var reportUndocumented: Boolean = true
- override var skipDeprecated: Boolean = false
- override var suppress: Boolean = false
-}
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
new file mode 100644
index 00000000..c66998d9
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt
@@ -0,0 +1,217 @@
+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
+import org.gradle.api.artifacts.ResolveException
+import org.gradle.api.file.FileCollection
+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.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.tasks.KotlinCompile
+import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
+import java.io.File
+import java.io.Serializable
+
+class ConfigurationExtractor(private val project: Project) {
+
+ fun extractFromSinglePlatform(variantName: String? = null): 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
+ }
+ }
+
+ return try {
+ PlatformData(null, getClasspath(target, variantName), getSourceSet(target, variantName), getPlatformName(target.platformType))
+ } catch(e: NoSuchMethodError){
+ null
+ }
+ }
+
+ fun extractFromMultiPlatform(): List<PlatformData>? {
+ val targets: NamedDomainObjectCollection<KotlinTarget>
+ try {
+ targets = project.extensions.getByType(KotlinMultiplatformExtension::class.java).targets
+ } catch (e: Throwable) {
+ when (e){
+ is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException ->
+ return null
+ else -> throw e
+ }
+ }
+
+ val commonTargetPlatformData = targets.find { it.platformType == KotlinPlatformType.common }?.let {
+ PlatformData("common", getClasspath(it), getSourceSet(it), "common")
+ }
+ val config = targets.filter { it.platformType != KotlinPlatformType.common }.map {
+ PlatformData(it.name, getClasspath(it), getSourceSet(it), it.platformType.toString())
+ }
+
+ return (config + commonTargetPlatformData).filterNotNull()
+ }
+
+ 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(), "") }
+
+ fun extractFromKotlinTasks(kotlinTasks: List<Task>): PlatformData? =
+ try {
+ kotlinTasks.map { extractFromKotlinTask(it) }.let { platformDataList ->
+ PlatformData(null, platformDataList.flatMap { it.classpath }, platformDataList.flatMap { it.sourceRoots }, "")
+ }
+ } catch (e: Throwable) {
+ when (e){
+ is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException ->
+ extractFromKotlinTasksTheHardWay(kotlinTasks)
+ else -> throw e
+ }
+ }
+
+ private fun extractFromKotlinTask(task: Task): PlatformData =
+ try {
+ project.extensions.getByType(KotlinSingleTargetExtension::class.java).target
+ .compilations
+ .find { it.compileKotlinTask == task }
+ } catch (e: Throwable) {
+ when (e){
+ is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException ->
+ project.extensions.getByType(KotlinMultiplatformExtension::class.java).targets
+ .firstNotNullResult { target -> target.compilations.find { it.compileKotlinTask == task } }
+ else -> throw e
+ }
+ }.let { PlatformData(task.name, getClasspath(it), getSourceSet(it), it?.platformType?.toString() ?: "") }
+
+ private fun extractFromKotlinTasksTheHardWay(kotlinTasks: List<Task>): PlatformData? {
+ val allClasspath = mutableSetOf<File>()
+ var allClasspathFileCollection: FileCollection = project.files()
+ val allSourceRoots = mutableSetOf<File>()
+
+ kotlinTasks.forEach {
+ with(ReflectDsl) {
+ val taskSourceRoots: List<File>
+ val abstractKotlinCompileClz: Class<out Any>
+ try {
+ taskSourceRoots = it["sourceRootsContainer"]["sourceRoots"].v()
+ abstractKotlinCompileClz = DokkaTask.getAbstractKotlinCompileFor(it)!!
+ } catch (e: NullPointerException) {
+ println("Error during extraction of sources from kotlinTasks. This may be a result of outdated Kotlin Gradle Plugin")
+ return null
+ }
+
+ val taskClasspath: Iterable<File> =
+ (it["getClasspath", AbstractCompile::class].takeIfIsFunc()?.invoke()
+ ?: it["compileClasspath", abstractKotlinCompileClz].takeIfIsProp()?.v()
+ ?: it["getClasspath", abstractKotlinCompileClz]())
+
+ if (taskClasspath is FileCollection) {
+ allClasspathFileCollection += taskClasspath
+ } else {
+ allClasspath += taskClasspath
+ }
+ allSourceRoots += taskSourceRoots.filter { it.exists() }
+ }
+ }
+ val classpath: MutableList<File> = try {
+ allClasspathFileCollection.toMutableList()
+ } catch (e: ResolveException) {
+ mutableListOf()
+ }
+ classpath.addAll (project.files(allClasspath).toList())
+
+ return PlatformData(null, classpath, allSourceRoots.toList(), "")
+ }
+
+ private fun getSourceSet(target: KotlinTarget, variantName: String? = null): List<File> =
+ if(variantName != null)
+ getSourceSet(getCompilation(target, variantName))
+ else
+ getSourceSet(getMainCompilation(target))
+
+ private fun getClasspath(target: KotlinTarget, variantName: String? = null): List<File> = if (target.isAndroidTarget()) {
+ if(variantName != null)
+ getClasspathFromAndroidTask(getCompilation(target, variantName))
+ else
+ getClasspathFromAndroidTask(getMainCompilation(target))
+ } 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>()
+ }
+
+ return baseVariants + testVariants
+ }
+
+ private fun getPlatformName(platform: KotlinPlatformType): String =
+ if (platform == KotlinPlatformType.androidJvm) KotlinPlatformType.jvm.toString() else platform.toString()
+
+ data class PlatformData(val name: String?,
+ val classpath: List<File>,
+ val sourceRoots: List<File>,
+ val platform: String) : Serializable
+} \ No newline at end of file
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
new file mode 100644
index 00000000..5153ae1c
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt
@@ -0,0 +1,353 @@
+package org.jetbrains.dokka.gradle
+
+import com.google.gson.GsonBuilder
+import org.gradle.api.*
+import org.gradle.api.artifacts.Configuration
+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
+import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink.Builder
+import org.jetbrains.dokka.DokkaConfiguration.SourceRoot
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.ReflectDsl
+import org.jetbrains.dokka.ReflectDsl.isNotInstance
+import org.jetbrains.dokka.gradle.ConfigurationExtractor.PlatformData
+import java.io.File
+import java.net.URLClassLoader
+import java.util.concurrent.Callable
+import java.util.function.BiConsumer
+
+open class DokkaTask : DefaultTask() {
+ 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
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ fun defaultKotlinTasks(): List<Task> = with(ReflectDsl) {
+ val abstractKotlinCompileClz = try {
+ project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE)
+ } catch (cnfe: ClassNotFoundException) {
+ logger.warn("$ABSTRACT_KOTLIN_COMPILE class not found, default kotlin tasks ignored")
+ return@with emptyList<Task>()
+ }
+
+ return@with project.tasks.filter { it isInstance abstractKotlinCompileClz }.filter { "Test" !in it.name }
+ }
+
+ init {
+ group = JavaBasePlugin.DOCUMENTATION_GROUP
+ description = "Generates dokka documentation for Kotlin"
+
+ @Suppress("LeakingThis")
+ dependsOn(Callable { kotlinTasks.map { it.taskDependencies } })
+ }
+
+ @Input
+ var outputFormat: String = "html"
+
+ @Input
+ var outputDirectory: String = ""
+
+ var dokkaRuntime: Configuration? = null
+
+ @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)
+
+ // Configure Dokka with closure in Gradle Kotlin DSL
+ fun configuration(action: Action<in GradlePassConfigurationImpl>) = action.execute(configuration)
+
+ private var externalDocumentationLinks: MutableList<DokkaConfiguration.ExternalDocumentationLink> = mutableListOf()
+
+ private val kotlinTasks: List<Task> by lazy { extractKotlinCompileTasks(configuration.collectKotlinTasks ?: { defaultKotlinTasks() }) }
+
+ private val configExtractor = ConfigurationExtractor(project)
+
+ @Input
+ var subProjects: List<String> = emptyList()
+
+ @Input
+ var disableAutoconfiguration: Boolean = false
+
+ private var outputDiagnosticInfo: Boolean = false // Workaround for Gradle, which fires some methods (like collectConfigurations()) multiple times in its lifecycle
+
+ private fun tryResolveFatJar(configuration: Configuration?): Set<File> {
+ return try {
+ configuration!!.resolve()
+ } catch (e: Exception) {
+ project.parent?.let { tryResolveFatJar(configuration) } ?: throw e
+ }
+ }
+
+ private fun loadFatJar() {
+ if (ClassloaderContainer.fatJarClassLoader == null) {
+ val jars = tryResolveFatJar(dokkaRuntime).toList()
+ ClassloaderContainer.fatJarClassLoader = URLClassLoader(jars.map { it.toURI().toURL() }.toTypedArray(), ClassLoader.getSystemClassLoader().parent)
+ }
+ }
+
+ private fun extractKotlinCompileTasks(collectTasks: () -> List<Any?>?): List<Task> {
+ val inputList = (collectTasks.invoke() ?: emptyList()).filterNotNull()
+ val (paths, other) = inputList.partition { it is String }
+
+ val taskContainer = project.tasks
+
+ val tasksByPath = paths.map { taskContainer.findByPath(it as String) ?: throw IllegalArgumentException("Task with path '$it' not found") }
+
+ other
+ .filter { it !is Task || it isNotInstance getAbstractKotlinCompileFor(it) }
+ .forEach { throw IllegalArgumentException("Illegal entry in kotlinTasks, must be subtype of $ABSTRACT_KOTLIN_COMPILE or String, but was $it") }
+
+ tasksByPath
+ .filter { it isNotInstance getAbstractKotlinCompileFor(it) }
+ .forEach { throw IllegalArgumentException("Illegal task path in kotlinTasks, must be subtype of $ABSTRACT_KOTLIN_COMPILE, but was $it") }
+
+ @Suppress("UNCHECKED_CAST")
+ 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 collectSuppressedFiles(sourceRoots: List<SourceRoot>) =
+ if(project.isAndroidProject()) {
+ val generatedRoot = project.buildDir.resolve("generated").absoluteFile
+ sourceRoots
+ .map { File(it.path) }
+ .filter { it.startsWith(generatedRoot) }
+ .flatMap { it.walk().toList() }
+ .map { it.absolutePath }
+ } else {
+ emptyList()
+ }
+
+ @TaskAction
+ fun generate() {
+ 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(globalConfig, it) }
+
+ val configuration = GradleDokkaConfigurationImpl()
+ configuration.outputDir = outputDirectory
+ configuration.format = outputFormat
+ configuration.generateIndexPages = true
+ configuration.cacheRoot = cacheRoot
+ configuration.impliedPlatforms = impliedPlatforms
+ configuration.passesConfigurations = passConfigurationList
+
+ bootstrapProxy.configure(
+ BiConsumer { level, message ->
+ when (level) {
+ "info" -> logger.info(message)
+ "warn" -> logger.warn(message)
+ "error" -> logger.error(message)
+ }
+ },
+ gson.toJson(configuration)
+ )
+
+ bootstrapProxy.generate()
+
+ } finally {
+ System.setProperty(COLORS_ENABLED_PROPERTY, kotlinColorsEnabledBefore)
+ }
+ }
+
+ private fun collectConfigurations(): List<GradlePassConfigurationImpl> =
+ if (this.isMultiplatformProject()) collectFromMultiPlatform() else collectFromSinglePlatform()
+
+ private fun collectFromMultiPlatform(): List<GradlePassConfigurationImpl> {
+ val userConfig = multiplatform
+ .filterNot { it.name.toLowerCase() == GLOBAL_PLATFORM_NAME }
+ .map {
+ if (it.collectKotlinTasks != null) {
+ configExtractor.extractFromKotlinTasks(extractKotlinCompileTasks(it.collectKotlinTasks!!))
+ ?.let { platformData -> mergeUserConfigurationAndPlatformData(it, platformData) } ?: it
+ } else {
+ it
+ }
+ }
+
+ if (disableAutoconfiguration) return userConfig
+
+ val baseConfig = mergeUserAndAutoConfigurations(
+ userConfig,
+ configExtractor.extractFromMultiPlatform().orEmpty()
+ )
+
+ return if (subProjects.isNotEmpty())
+ subProjects.toProjects().fold(baseConfig) { list, subProject ->
+ mergeUserAndAutoConfigurations(list, ConfigurationExtractor(subProject).extractFromMultiPlatform().orEmpty())
+ }
+ else
+ baseConfig
+ }
+
+ private fun collectFromSinglePlatform(): List<GradlePassConfigurationImpl> {
+ val userConfig = configuration.let {
+ if (it.collectKotlinTasks != null) {
+ configExtractor.extractFromKotlinTasks(extractKotlinCompileTasks(it.collectKotlinTasks!!))
+ ?.let { platformData -> mergeUserConfigurationAndPlatformData(it, platformData) } ?: it
+ } else {
+ it
+ }
+ }
+
+ if (disableAutoconfiguration) return listOf(userConfig)
+
+ val extractedConfig = configExtractor.extractFromSinglePlatform(userConfig.androidVariant)
+ val baseConfig = if (extractedConfig != null)
+ listOf(mergeUserConfigurationAndPlatformData(userConfig, extractedConfig))
+ else
+ collectFromSinglePlatformOldPlugin()
+
+ return if (subProjects.isNotEmpty()) {
+ try {
+ subProjects.toProjects().fold(baseConfig) { list, subProject ->
+ listOf(mergeUserConfigurationAndPlatformData(
+ list.first(),
+ ConfigurationExtractor(subProject).extractFromSinglePlatform()!!
+ ))
+ }
+ } 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
+ }
+ } else {
+ baseConfig
+ }
+ }
+
+ private fun collectFromSinglePlatformOldPlugin(): List<GradlePassConfigurationImpl> {
+ val kotlinTasks = configExtractor.extractFromKotlinTasks(kotlinTasks)
+ return if (kotlinTasks != null) {
+ listOf(mergeUserConfigurationAndPlatformData(configuration, kotlinTasks))
+ } else {
+ val javaPlugin = configExtractor.extractFromJavaPlugin()
+ if (javaPlugin != null)
+ listOf(mergeUserConfigurationAndPlatformData(configuration, javaPlugin)) else listOf(configuration)
+ }
+ }
+
+ private fun mergeUserAndAutoConfigurations(userConfigurations: List<GradlePassConfigurationImpl>,
+ autoConfigurations: List<PlatformData>): List<GradlePassConfigurationImpl> {
+ val merged: MutableList<GradlePassConfigurationImpl> = mutableListOf()
+ merged.addAll(
+ userConfigurations.map { userConfig ->
+ val autoConfig = autoConfigurations.find { autoConfig -> autoConfig.name == userConfig.name }
+ if (autoConfig != null) {
+ mergeUserConfigurationAndPlatformData(userConfig, autoConfig)
+ } else {
+ if(outputDiagnosticInfo) {
+ logger.warn(
+ "Could not find platform with name: ${userConfig.name} in Kotlin Gradle Plugin, " +
+ "using only user provided configuration for this platform"
+ )
+ }
+ userConfig
+ }
+ }
+ )
+ return merged.toList()
+ }
+
+ private fun mergeUserConfigurationAndPlatformData(userConfig: GradlePassConfigurationImpl,
+ autoConfig: PlatformData): GradlePassConfigurationImpl =
+ 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 defaultPassConfiguration(globalConfig: GradlePassConfigurationImpl?, config: GradlePassConfigurationImpl): GradlePassConfigurationImpl {
+ if (config.moduleName == "") {
+ config.moduleName = project.name
+ }
+ if (config.targets.isEmpty() && multiplatform.isNotEmpty()){
+ config.targets = listOf(config.name)
+ }
+ config.classpath = (config.classpath as List<Any>).map { it.toString() }.distinct() // Workaround for Groovy's GStringImpl
+ 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)
+ }
+ config.externalDocumentationLinks.addAll(externalDocumentationLinks)
+ 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()) {
+ "androidjvm", "android" -> Platform.jvm
+ else -> Platform.fromString(platform)
+ }
+
+ // Needed for Gradle incremental build
+ @OutputDirectory
+ fun getOutputDirectoryAsFile(): File = project.file(outputDirectory)
+
+ // 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)) }) +
+ 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())) })
+
+ companion object {
+ const val COLORS_ENABLED_PROPERTY = "kotlin.colors.enabled"
+ const val ABSTRACT_KOTLIN_COMPILE = "org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile"
+
+ internal fun getAbstractKotlinCompileFor(task: Task) = try {
+ task.project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE)
+ } catch (e: ClassNotFoundException) {
+ null
+ }
+ }
+}
diff --git a/runners/gradle-plugin/src/main/kotlin/ProxyUtils.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt
index 7bdf2f9d..f8965993 100644
--- a/runners/gradle-plugin/src/main/kotlin/ProxyUtils.kt
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt
@@ -1,4 +1,4 @@
-package org.jetbrains.dokka
+package org.jetbrains.dokka.gradle
import java.lang.reflect.InvocationHandler
import java.lang.reflect.InvocationTargetException
@@ -26,9 +26,9 @@ inline fun <reified T : Any> automagicTypedProxy(targetClassLoader: ClassLoader,
*/
fun automagicProxy(targetClassLoader: ClassLoader, targetType: Class<*>, delegate: Any): Any =
Proxy.newProxyInstance(
- targetClassLoader,
- arrayOf(targetType),
- DelegatedInvocationHandler(delegate)
+ targetClassLoader,
+ arrayOf(targetType),
+ DelegatedInvocationHandler(delegate)
)
class DelegatedInvocationHandler(private val delegate: Any) : InvocationHandler {
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
new file mode 100644
index 00000000..767bf4f4
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt
@@ -0,0 +1,150 @@
+package org.jetbrains.dokka.gradle
+
+import groovy.lang.Closure
+import org.gradle.api.Action
+import org.gradle.api.tasks.Input
+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.Platform
+import java.io.File
+import java.io.Serializable
+import java.net.URL
+import java.util.concurrent.Callable
+import kotlin.reflect.KMutableProperty
+import kotlin.reflect.full.memberProperties
+
+class GradleSourceRootImpl: SourceRoot, Serializable {
+ override var path: String = ""
+ set(value) {
+ field = File(value).absolutePath
+ }
+
+ 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 = false
+ @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 @Optional @Transient var androidVariant: String? = null
+
+ fun kotlinTasks(taskSupplier: Callable<List<Any>>) {
+ collectKotlinTasks = { taskSupplier.call() }
+ }
+
+ fun kotlinTasks(closure: Closure<Any?>) {
+ collectKotlinTasks = { closure.call() as? List<Any?> }
+ }
+
+ fun sourceRoot(c: Closure<Unit>) {
+ val configured = ConfigureUtil.configure(c, GradleSourceRootImpl())
+ sourceRoots.add(configured)
+ }
+
+ fun sourceRoot(action: Action<in GradleSourceRootImpl>) {
+ val sourceRoot = GradleSourceRootImpl()
+ action.execute(sourceRoot)
+ sourceRoots.add(sourceRoot)
+ }
+
+ fun sourceLink(c: Closure<Unit>) {
+ val configured = ConfigureUtil.configure(c, GradleSourceLinkDefinitionImpl())
+ sourceLinks.add(configured)
+ }
+
+ fun sourceLink(action: Action<in GradleSourceLinkDefinitionImpl>) {
+ val sourceLink = GradleSourceLinkDefinitionImpl()
+ action.execute(sourceLink)
+ sourceLinks.add(sourceLink)
+ }
+
+ fun perPackageOption(c: Closure<Unit>) {
+ val configured = ConfigureUtil.configure(c, GradlePackageOptionsImpl())
+ perPackageOptions.add(configured)
+ }
+
+ fun perPackageOption(action: Action<in GradlePackageOptionsImpl>) {
+ val option = GradlePackageOptionsImpl()
+ action.execute(option)
+ perPackageOptions.add(option)
+ }
+
+ fun externalDocumentationLink(c: Closure<Unit>) {
+ val link = ConfigureUtil.configure(c, GradleExternalDocumentationLinkImpl())
+ externalDocumentationLinks.add(link)
+ }
+
+ fun externalDocumentationLink(action: Action<in GradleExternalDocumentationLinkImpl>) {
+ val link = GradleExternalDocumentationLinkImpl()
+ action.execute(link)
+ externalDocumentationLinks.add(link)
+ }
+}
+
+class GradleSourceLinkDefinitionImpl : SourceLinkDefinition, Serializable {
+ override var path: String = ""
+ override var url: String = ""
+ override var lineSuffix: String? = null
+}
+
+class GradleExternalDocumentationLinkImpl : ExternalDocumentationLink, Serializable {
+ override var url: URL = URL("http://")
+ override var packageListUrl: URL = URL("http://")
+}
+
+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()
+}
+
+class GradlePackageOptionsImpl: PackageOptions, Serializable {
+ override var prefix: String = ""
+ override var includeNonPublic: Boolean = false
+ override var reportUndocumented: Boolean = true
+ override var skipDeprecated: Boolean = true
+ override var suppress: Boolean = false
+}
+
+fun GradlePassConfigurationImpl.copy(): GradlePassConfigurationImpl {
+ val newObj = GradlePassConfigurationImpl(this.name)
+ 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))
+ }
+ }
+ }
+ return newObj
+} \ No newline at end of file
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
new file mode 100644
index 00000000..6f8d55e4
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt
@@ -0,0 +1,58 @@
+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"
+
+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"))
+
+ private fun addConfiguration(project: Project) =
+ project.configurations.create("dokkaRuntime").apply {
+ defaultDependencies{ dependencies -> dependencies.add(project.dependencies.create("org.jetbrains.dokka:dokka-fatjar:${DokkaVersion.version}")) }
+ }
+
+ private 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.tasks.withType(taskClass) { task ->
+ task.multiplatform = project.container(GradlePassConfigurationImpl::class.java)
+ task.configuration = GradlePassConfigurationImpl()
+ task.dokkaRuntime = runtimeConfiguration
+ task.outputDirectory = File(project.buildDir, taskName).absolutePath
+ }
+ }
+}
+
+object DokkaVersion {
+ var version: String? = null
+
+ fun loadFrom(stream: InputStream) {
+ version = Properties().apply {
+ load(stream)
+ }.getProperty("dokka-version")
+ }
+}
+
+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
new file mode 100644
index 00000000..d70b0499
--- /dev/null
+++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt
@@ -0,0 +1,19 @@
+package org.jetbrains.dokka.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.UnknownDomainObjectException
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
+
+
+fun Project.isAndroidProject() = try {
+ project.extensions.getByName("android")
+ true
+} catch(e: UnknownDomainObjectException) {
+ false
+} catch(e: ClassNotFoundException) {
+ false
+}
+
+fun KotlinTarget.isAndroidTarget() = this.platformType == KotlinPlatformType.androidJvm
+fun DokkaTask.isMultiplatformProject() = this.multiplatform.isNotEmpty() \ No newline at end of file