aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--integration/src/main/kotlin/org/jetbrains/dokka/ReflectDsl.kt70
-rw-r--r--runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt53
-rw-r--r--runners/gradle-plugin/src/main/kotlin/main.kt182
3 files changed, 194 insertions, 111 deletions
diff --git a/integration/src/main/kotlin/org/jetbrains/dokka/ReflectDsl.kt b/integration/src/main/kotlin/org/jetbrains/dokka/ReflectDsl.kt
new file mode 100644
index 00000000..3c9bf156
--- /dev/null
+++ b/integration/src/main/kotlin/org/jetbrains/dokka/ReflectDsl.kt
@@ -0,0 +1,70 @@
+package org.jetbrains.dokka
+
+import kotlin.reflect.*
+import kotlin.reflect.jvm.isAccessible
+
+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/android-gradle-plugin/src/main/kotlin/mainAndroid.kt b/runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt
index 5db2ad2f..8dd0a4c6 100644
--- a/runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt
+++ b/runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt
@@ -1,13 +1,8 @@
package org.jetbrains.dokka.gradle
-import com.android.build.gradle.*
-import com.android.build.gradle.internal.VariantManager
import org.gradle.api.Plugin
import org.gradle.api.Project
-import org.gradle.api.tasks.SourceSet
import java.io.File
-import kotlin.reflect.jvm.isAccessible
-import kotlin.reflect.memberProperties
open class DokkaAndroidPlugin : Plugin<Project> {
override fun apply(project: Project) {
@@ -19,50 +14,4 @@ open class DokkaAndroidPlugin : Plugin<Project> {
}
}
-open class DokkaAndroidTask : DokkaTask() {
- override val sdkProvider: SdkProvider? = AndroidSdkProvider(project)
-}
-
-private class AndroidSdkProvider(private val project: Project) : SdkProvider {
- private val ext: BaseExtension? by lazy {
- project.extensions.findByType(LibraryExtension::class.java)
- ?: project.extensions.findByType(AppExtension::class.java)
- ?: project.extensions.findByType(TestExtension::class.java)
- }
-
- private val isAndroidProject: Boolean get() = ext != null
-
- private val variantManager: VariantManager? by lazy {
- val plugin = (project.plugins.findPlugin("android")
- ?: project.plugins.findPlugin("android-library")
- ?: project.plugins.findPlugin("com.android.test")
- ?: throw Exception("Android plugin not found, please use dokka-android with android or android-library plugin.")) as BasePlugin
- plugin.javaClass.kotlin.memberProperties
- .find { it.name == "variantManager" }
- ?.apply { isAccessible = true }
- ?.let { it.get(plugin) as VariantManager }
- ?: plugin.variantManager
- }
-
- private val allVariantsClassPath by lazy {
- try {
- variantManager?.variantDataList?.flatMap { it.variantConfiguration.compileClasspath }!!
- } catch(e: Exception) {
- throw Exception("Unsupported version of android build tools, could not access variant manager.", e)
- }
- }
-
- override val name: String = "android"
-
- override val isValid: Boolean
- get() = isAndroidProject
-
- override val classpath: List<File>
- get() = ext?.bootClasspath.orEmpty() + allVariantsClassPath
-
- override val sourceDirs: Set<File>?
- get() {
- val sourceSet = ext?.sourceSets?.findByName(SourceSet.MAIN_SOURCE_SET_NAME)
- return sourceSet?.java?.srcDirs
- }
-}
+open class DokkaAndroidTask : DokkaTask()
diff --git a/runners/gradle-plugin/src/main/kotlin/main.kt b/runners/gradle-plugin/src/main/kotlin/main.kt
index 61c83de4..bd4b3ab3 100644
--- a/runners/gradle-plugin/src/main/kotlin/main.kt
+++ b/runners/gradle-plugin/src/main/kotlin/main.kt
@@ -4,15 +4,14 @@ import groovy.lang.Closure
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
+import org.gradle.api.Task
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.jetbrains.dokka.DokkaBootstrap
-import org.jetbrains.dokka.DokkaConfiguration
-import org.jetbrains.dokka.SerializeOnlyDokkaConfiguration
-import org.jetbrains.dokka.automagicTypedProxy
+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
@@ -52,9 +51,24 @@ object ClassloaderContainer {
}
open class DokkaTask : DefaultTask() {
+
+ fun defaultKotlinTasks() = with(ReflectDsl) {
+
+ val abstractKotlinCompileClz = try {
+ project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE)
+ } catch (cnfe: ClassNotFoundException) {
+ 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"
+ project.afterEvaluate {
+ this.dependsOn(kotlinTasks.flatMap { it.dependsOn })
+ }
}
@Input
@@ -62,8 +76,13 @@ open class DokkaTask : DefaultTask() {
@Input
var outputFormat: String = "html"
var outputDirectory: String = ""
- @Input
- var processConfigurations: List<Any?> = arrayListOf("compile")
+
+
+ @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()
+
+ @Input var classpath: List<File> = arrayListOf()
+
@Input
var includes: List<Any?> = arrayListOf()
@Input
@@ -92,7 +111,16 @@ open class DokkaTask : DefaultTask() {
@Optional @Input var cacheRoot: String? = null
- protected open val sdkProvider: SdkProvider? = null
+ @get:Input
+ internal val kotlinCompileBasedClasspathAndSourceRoots: ClasspathAndSourceRoots by lazy { extractClasspathAndSourceRootsFromKotlinTasks() }
+
+
+ private var kotlinTasksConfigurator: () -> List<Any?>? = { defaultKotlinTasks() }
+ private val kotlinTasks: List<Task> by lazy { extractKotlinCompileTasks() }
+
+ fun kotlinTasks(closure: Closure<Any?>) {
+ kotlinTasksConfigurator = { closure.call() as? List<Any?> }
+ }
fun linkMapping(closure: Closure<Any?>) {
val mapping = LinkMapping()
@@ -151,6 +179,56 @@ open class DokkaTask : DefaultTask() {
}
}
+ internal data class ClasspathAndSourceRoots(val classpath: List<File>, 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>()
+ 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["compileClasspath", abstractKotlinCompileClz].takeIfIsProp()?.v() ?:
+ it["getClasspath", abstractKotlinCompileClz]())
+
+ allClasspath += taskClasspath.filter { it.exists() }
+ allSourceRoots += taskSourceRoots.filter { it.exists() }
+ }
+ }
+
+ return ClasspathAndSourceRoots(allClasspath.toList(), allSourceRoots.toList())
+ }
+
+ private fun Iterable<File>.toSourceRoots(): List<SourceRoot> = this.filter { it.exists() }.map { SourceRoot().apply { path = it.path } }
@TaskAction
fun generate() {
@@ -159,22 +237,18 @@ open class DokkaTask : DefaultTask() {
try {
loadFatJar()
- val project = project
- val sdkProvider = sdkProvider
- val sourceRoots = collectSourceRoots()
- val allConfigurations = project.configurations
+ val (tasksClasspath, tasksSourceRoots) = kotlinCompileBasedClasspathAndSourceRoots
- val classpath =
- if (sdkProvider != null && sdkProvider.isValid) sdkProvider.classpath else emptyList<File>() +
- processConfigurations
- .map { allConfigurations?.getByName(it.toString()) ?: throw IllegalArgumentException("No configuration $it found") }
- .flatMap { it }
+ 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()
@@ -183,7 +257,7 @@ open class DokkaTask : DefaultTask() {
val configuration = SerializeOnlyDokkaConfiguration(
moduleName,
- classpath.map { it.absolutePath },
+ fullClasspath.map { it.absolutePath },
sourceRoots,
samples.filterNotNull().map { project.file(it).absolutePath },
includes.filterNotNull().map { project.file(it).absolutePath },
@@ -222,28 +296,40 @@ open class DokkaTask : DefaultTask() {
}
}
- fun collectSourceRoots(): List<SourceRoot> {
- val provider = sdkProvider
+ private fun collectClasspathFromOldSources(): List<File> {
+
+ val allConfigurations = project.configurations
+
+ val fromConfigurations =
+ processConfigurations.map {
+ allConfigurations?.getByName(it.toString()) ?: throw IllegalArgumentException("No configuration $it found")
+ }.flatten()
+
+ 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 (provider != null && provider.isValid) {
- logger.info("Dokka: Taking source directories from ${provider.name} sdk provider")
- provider.sourceDirs
- } else {
+ } else if (kotlinTasks.isEmpty()) {
logger.info("Dokka: Taking source directories from default java plugin")
val javaPluginConvention = project.convention.getPlugin(JavaPluginConvention::class.java)
val sourceSets = javaPluginConvention.sourceSets?.findByName(SourceSet.MAIN_SOURCE_SET_NAME)
sourceSets?.allSource?.srcDirs
+ } else {
+ emptySet()
}
- return sourceRoots + (sourceDirs?.filter { it.exists() }?.map { SourceRoot().apply { path = it.path } } ?: emptyList())
+ return sourceRoots + (sourceDirs?.toSourceRoots() ?: emptyList())
}
- @InputFiles
+
@SkipWhenEmpty
+ @InputFiles
fun getInputFiles(): FileCollection =
- project.files(collectSourceRoots().map { project.fileTree(File(it.path)) }) +
+ project.files(kotlinCompileBasedClasspathAndSourceRoots.sourceRoots.map { project.fileTree(File(it.path)) }) +
+ project.files(collectSourceRoots().map { project.fileTree(File(it.path)) }) +
project.files(includes) +
project.files(samples.map { project.fileTree(it) })
@@ -252,6 +338,13 @@ open class DokkaTask : DefaultTask() {
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
+ }
}
}
@@ -262,6 +355,10 @@ class SourceRoot : DokkaConfiguration.SourceRoot {
}
override var platforms: List<String> = arrayListOf()
+
+ override fun toString(): String {
+ return "${platforms.joinToString()}::$path"
+ }
}
open class LinkMapping : Serializable, DokkaConfiguration.SourceLinkDefinition {
@@ -315,36 +412,3 @@ class PackageOptions : DokkaConfiguration.PackageOptions {
override var reportUndocumented: Boolean = true
override var skipDeprecated: Boolean = false
}
-/**
- * A provider for SDKs that can be used if a project uses classes that live outside the JDK or uses a
- * different method to determine the source directories.
- *
- * For example an Android library project configures its sources through the Android extension instead
- * of the basic java convention. Also it has its custom classes located in the SDK installation directory.
- */
-interface SdkProvider {
- /**
- * The name of this provider. Only used for logging purposes.
- */
- val name: String
-
- /**
- * Checks whether this provider has everything it needs to provide the source directories.
- */
- val isValid: Boolean
-
- /**
- * Provides additional classpath files where Dokka should search for external classes.
- * The file list is injected **after** JDK Jars and **before** project dependencies.
- *
- * This is only called if [isValid] returns `true`.
- */
- val classpath: List<File>
-
- /**
- * Provides a list of directories where Dokka should search for source files.
- *
- * This is only called if [isValid] returns `true`.
- */
- val sourceDirs: Set<File>?
-}