diff options
Diffstat (limited to 'runners/gradle-plugin')
-rw-r--r-- | runners/gradle-plugin/build.gradle | 17 | ||||
-rw-r--r-- | runners/gradle-plugin/src/main/kotlin/main.kt | 499 | ||||
-rw-r--r-- | runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt | 217 | ||||
-rw-r--r-- | runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt | 353 | ||||
-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.kt | 150 | ||||
-rw-r--r-- | runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt | 58 | ||||
-rw-r--r-- | runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt | 19 |
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 |