diff options
Diffstat (limited to 'runners')
5 files changed, 218 insertions, 108 deletions
diff --git a/runners/gradle-plugin/build.gradle b/runners/gradle-plugin/build.gradle index adcfd009..d993597d 100644 --- a/runners/gradle-plugin/build.gradle +++ b/runners/gradle-plugin/build.gradle @@ -26,6 +26,7 @@ dependencies { compile project(":integration") + compileOnly "org.jetbrains.kotlin:kotlin-gradle-plugin" compileOnly gradleApi() compileOnly localGroovy() implementation "com.google.code.gson:gson:$gson_version" 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..185a32fd --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt @@ -0,0 +1,118 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.NamedDomainObjectCollection +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.UnknownDomainObjectException +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 java.io.File +import java.io.Serializable + +object ConfigurationExtractor { + + fun extractFromSinglePlatform(project: Project): PlatformData? { + val target: KotlinTarget + try { + target = project.extensions.getByType(KotlinSingleTargetExtension::class.java).target + } catch(e: UnknownDomainObjectException) { + return null + } catch(e: NoClassDefFoundError) { + return null + } catch(e: ClassNotFoundException) { + return null + } + + return try { + return PlatformData(null, getClasspath(target), getSourceSet(target), getPlatformName(target.platformType)) + } catch(e: NoSuchMethodError){ + null + } + } + + fun extractFromMultiPlatform(project: Project): List<PlatformData>? { + val targets: NamedDomainObjectCollection<KotlinTarget> + try { + targets = project.extensions.getByType(KotlinMultiplatformExtension::class.java).targets + } catch(e: UnknownDomainObjectException) { + return null + } catch(e: ClassNotFoundException) { + return null + } catch(e: NoClassDefFoundError) { + return null + } + + val commonTarget = targets.find { it.platformType == KotlinPlatformType.common } + val commonTargetCompilation = commonTarget?.compilations?.getByName("main") + val commonTargetSourceList = commonTargetCompilation?.allKotlinSourceSets?.flatMap { it.kotlin.sourceDirectories } + val commonTargetClasspath = commonTargetCompilation?.compileDependencyFiles?.files?.toList() + + val platformTargets = targets.filter { it.platformType != KotlinPlatformType.common } + + val config = platformTargets.map { + PlatformData(it.name, getClasspath(it) + commonTargetClasspath.orEmpty(), + getSourceSet(it) + commonTargetSourceList.orEmpty(), it.platformType.toString()) + } + return config + PlatformData("common", commonTargetClasspath.orEmpty(), commonTargetSourceList.orEmpty(), "common") + } + + fun extractFromKotlinTasks(kotlinTasks: List<Task>, project: Project): PlatformData? { + val allClasspath = mutableSetOf<File>() + var allClasspathFileCollection: FileCollection = project.files() + val allSourceRoots = mutableSetOf<File>() + + kotlinTasks.forEach { + with(ReflectDsl) { + val taskSourceRoots: List<File> = it["sourceRootsContainer"]["sourceRoots"].v() + val abstractKotlinCompileClz = DokkaTask.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 PlatformData(null, + (allClasspathFileCollection + project.files(allClasspath)).toList(), allSourceRoots.toList(), "" + ) + } + + fun extractFromJavaPlugin(project: Project): PlatformData? = + project.convention.findPlugin(JavaPluginConvention::class.java) + ?.run { sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME)?.allSource?.srcDirs } + ?.let { PlatformData(null, emptyList(), it.toList(), "") } + + private fun getSourceSet(target: KotlinTarget): List<File> = + getMainCompilation(target).allKotlinSourceSets.flatMap { it.kotlin.sourceDirectories } + + private fun getClasspath(target: KotlinTarget): List<File> = + getMainCompilation(target).compileDependencyFiles.files.toList() + + private fun getMainCompilation(target: KotlinTarget): KotlinCompilation<KotlinCommonOptions> = + target.compilations.getByName("main") + + private fun getPlatformName(platform: KotlinPlatformType): String = + if (platform == KotlinPlatformType.androidJvm) "jvm" 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 index d4eee4b2..4a4518b9 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt @@ -3,14 +3,14 @@ package org.jetbrains.dokka.gradle import com.google.gson.GsonBuilder import groovy.lang.Closure import org.gradle.api.DefaultTask +import org.gradle.api.NamedDomainObjectContainer 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.internal.plugins.DslObject import org.gradle.api.plugins.JavaBasePlugin -import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.tasks.* -import org.gradle.api.tasks.compile.AbstractCompile import org.jetbrains.dokka.DokkaBootstrap import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.DokkaConfiguration.SourceRoot @@ -18,7 +18,6 @@ import org.jetbrains.dokka.Platform import org.jetbrains.dokka.ReflectDsl import org.jetbrains.dokka.ReflectDsl.isNotInstance import java.io.File -import java.io.Serializable import java.net.URLClassLoader import java.util.concurrent.Callable import java.util.function.BiConsumer @@ -54,15 +53,6 @@ open class DokkaTask : DefaultTask() { var dokkaRuntime: Configuration? = null - @InputFiles - var classpath: Iterable<File> = arrayListOf() - - @Input - var sourceDirs: Iterable<File> = emptyList() - - @Input - var sourceRoots: MutableList<SourceRoot> = arrayListOf() - @Input var dokkaFatJar: Any = "org.jetbrains.dokka:dokka-fatjar:${DokkaVersion.version}" @@ -76,22 +66,33 @@ open class DokkaTask : DefaultTask() { @Input var collectInheritedExtensionsFromLibraries: Boolean = false - @get:Internal - internal val kotlinCompileBasedClasspathAndSourceRoots: ClasspathAndSourceRoots by lazy { extractClasspathAndSourceRootsFromKotlinTasks() } + var multiplatform: NamedDomainObjectContainer<GradlePassConfigurationImpl> + @Suppress("UNCHECKED_CAST") + 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") + get() = DslObject(this).extensions.getByName(CONFIGURATION_EXTENSION_NAME) as GradlePassConfigurationImpl + internal set(value) = DslObject(this).extensions.add(CONFIGURATION_EXTENSION_NAME, value) protected var externalDocumentationLinks: MutableList<DokkaConfiguration.ExternalDocumentationLink> = mutableListOf() private var kotlinTasksConfigurator: () -> List<Any?>? = { defaultKotlinTasks() } private val kotlinTasks: List<Task> by lazy { extractKotlinCompileTasks() } + @Deprecated("Use manual configuration of source roots or subProjects{} closure") fun kotlinTasks(taskSupplier: Callable<List<Any>>) { kotlinTasksConfigurator = { taskSupplier.call() } } + @Deprecated("Use manual configuration of source roots or subProjects{} closure") fun kotlinTasks(closure: Closure<Any?>) { kotlinTasksConfigurator = { closure.call() as? List<Any?> } } + @Input + var subProjects: List<String> = emptyList() fun tryResolveFatJar(project: Project): Set<File> { return try { @@ -111,9 +112,6 @@ open class DokkaTask : DefaultTask() { } } - 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 } @@ -134,45 +132,14 @@ open class DokkaTask : DefaultTask() { 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<GradleSourceRootImpl> = this.filter { it.exists() }.map { GradleSourceRootImpl().apply { path = it.path } } + private fun Iterable<String>.toProjects(): List<Project> = project.subprojects.toList().filter { this.contains(it.name) } protected open fun collectSuppressedFiles(sourceRoots: List<SourceRoot>): List<String> = emptyList() @TaskAction fun generate() { - if (dokkaRuntime == null){ + if (dokkaRuntime == null) { dokkaRuntime = project.configurations.getByName("dokkaRuntime") } @@ -181,10 +148,6 @@ open class DokkaTask : DefaultTask() { System.setProperty(COLORS_ENABLED_PROPERTY, "false") try { loadFatJar() - // TODO: implement extracting source roots from kotlin tasks - val (_, tasksSourceRoots) = kotlinCompileBasedClasspathAndSourceRoots - - val sourceRoots = collectSourceRoots() + tasksSourceRoots.toSourceRoots() val bootstrapClass = ClassloaderContainer.fatJarClassLoader!!.loadClass("org.jetbrains.dokka.DokkaBootstrapImpl") val bootstrapInstance = bootstrapClass.constructors.first().newInstance() @@ -193,16 +156,8 @@ open class DokkaTask : DefaultTask() { val gson = GsonBuilder().setPrettyPrinting().create() - val passConfigurationExtension: GradlePassConfigurationImpl = extensions.findByName( - CONFIGURATION_EXTENSION_NAME) as GradlePassConfigurationImpl - val passConfigurationsContainer = - (extensions.getByName(MULTIPLATFORM_EXTENSION_NAME) as Iterable<GradlePassConfigurationImpl>).toList() - - passConfigurationExtension.sourceRoots.addAll(sourceRoots) - - val passConfigurationList = - (if (passConfigurationsContainer.isEmpty()) listOf(passConfigurationExtension) else passConfigurationsContainer) - .map { defaultPassConfiguration(it) } + val passConfigurationList = collectConfigurations() + .map { defaultPassConfiguration(it) } val configuration = GradleDokkaConfigurationImpl() configuration.outputDir = outputDirectory @@ -230,39 +185,83 @@ open class DokkaTask : DefaultTask() { } } - private fun defaultPassConfiguration(passConfig: GradlePassConfigurationImpl): GradlePassConfigurationImpl { - val (tasksClasspath, _) = kotlinCompileBasedClasspathAndSourceRoots - - val fullClasspath = tasksClasspath + classpath - passConfig.moduleName = moduleName - passConfig.classpath = fullClasspath.map { it.absolutePath } - passConfig.samples = passConfig.samples.map { project.file(it).absolutePath } - passConfig.includes = passConfig.includes.map { project.file(it).absolutePath } - passConfig.collectInheritedExtensionsFromLibraries = collectInheritedExtensionsFromLibraries - passConfig.suppressedFiles = collectSuppressedFiles(passConfig.sourceRoots) - passConfig.externalDocumentationLinks.addAll(externalDocumentationLinks) - if(passConfig.platform.isNotEmpty()){ - passConfig.analysisPlatform = Platform.fromString(passConfig.platform) + private fun collectConfigurations(): List<GradlePassConfigurationImpl> = + if (multiplatform.toList().isNotEmpty()) collectFromMultiPlatform() else collectFromSinglePlatform() + + private fun collectFromMultiPlatform(): List<GradlePassConfigurationImpl> { + val baseConfig = mergeUserAndAutoConfigurations( + multiplatform.toList(), + ConfigurationExtractor.extractFromMultiPlatform(project).orEmpty() + ) + return if (subProjects.isEmpty()) + baseConfig + else + subProjects.toProjects().fold(baseConfig, { list, project -> + mergeUserAndAutoConfigurations(list, ConfigurationExtractor.extractFromMultiPlatform(project).orEmpty())}) + } + + private fun collectFromSinglePlatform(): List<GradlePassConfigurationImpl> { + val autoConfig = ConfigurationExtractor.extractFromSinglePlatform(project) + val baseConfig = if (autoConfig != null) + listOf(mergeUserConfigurationAndPlatformData(configuration, autoConfig)) + else + collectFromSinglePlatformOldPlugin() + + return if (subProjects.isNotEmpty()) { + try { + subProjects.toProjects().fold(baseConfig, { list, project -> + listOf(mergeUserConfigurationAndPlatformData(list.first(), ConfigurationExtractor.extractFromSinglePlatform(project)!!)) + }) + } catch(e: NoClassDefFoundError) { + logger.warn("Cannot extract sources from subProjects. Please update Kotlin plugin to version 1.3.30+") + baseConfig + } + } else { + baseConfig } + } - return passConfig + private fun collectFromSinglePlatformOldPlugin(): List<GradlePassConfigurationImpl> { + val kotlinTasks = ConfigurationExtractor.extractFromKotlinTasks(kotlinTasks, project) + return if (kotlinTasks != null) { + listOf(mergeUserConfigurationAndPlatformData(configuration, kotlinTasks)) + } else { + val javaPlugin = ConfigurationExtractor.extractFromJavaPlugin(project) + if (javaPlugin != null) + listOf(mergeUserConfigurationAndPlatformData(configuration, javaPlugin)) else listOf(configuration) + } } - private fun collectSourceRoots(): List<SourceRoot> { - val sourceDirs = when { - sourceDirs.any() -> { - logger.info("Dokka: Taking source directories provided by the user") - sourceDirs.toSet() - } - 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() + private fun mergeUserAndAutoConfigurations(userConfigurations: List<GradlePassConfigurationImpl>, + autoConfigurations: List<ConfigurationExtractor.PlatformData>) = + userConfigurations.map { userConfig -> + val autoConfig = autoConfigurations.find { autoConfig -> autoConfig.name == userConfig.name } + if (autoConfig != null) mergeUserConfigurationAndPlatformData(userConfig, autoConfig) else userConfig } - return sourceRoots + (sourceDirs?.toSourceRoots() ?: emptyList()) + + private fun mergeUserConfigurationAndPlatformData(user: GradlePassConfigurationImpl, + auto: ConfigurationExtractor.PlatformData): GradlePassConfigurationImpl { + user.sourceRoots.addAll(auto.sourceRoots.toSourceRoots()) + user.classpath += auto.classpath.map { it.absolutePath } + if (user.platform == null) + user.platform = auto.platform + return user + } + + private fun defaultPassConfiguration(config: GradlePassConfigurationImpl): GradlePassConfigurationImpl { + if (config.moduleName == "") { + config.moduleName = moduleName + } + config.samples = config.samples.map { project.file(it).absolutePath } + config.includes = config.includes.map { project.file(it).absolutePath } + config.collectInheritedExtensionsFromLibraries = collectInheritedExtensionsFromLibraries + config.suppressedFiles += collectSuppressedFiles(config.sourceRoots) + config.externalDocumentationLinks.addAll(externalDocumentationLinks) + if (config.platform != null && config.platform.toString().isNotEmpty()){ + config.analysisPlatform = Platform.fromString(config.platform.toString()) + } + return config } /** @@ -275,17 +274,14 @@ open class DokkaTask : DefaultTask() { * Needed for Gradle incremental build */ @InputFiles - fun getInputFiles(): FileCollection { - val (_, tasksSourceRoots) = extractClasspathAndSourceRootsFromKotlinTasks() - return project.files(tasksSourceRoots.map { project.fileTree(it) }) + - project.files(collectSourceRoots().map { project.fileTree(File(it.path)) }) - } + fun getInputFiles(): FileCollection = + project.files(collectConfigurations().flatMap { it.sourceRoots }.map { project.fileTree(File(it.path)) }) 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 { + 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/org/jetbrains/dokka/gradle/configurationImplementations.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt index 85671c5b..c9c0d15d 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt @@ -41,7 +41,7 @@ open class GradlePassConfigurationImpl(@Transient val name: String = ""): PassCo override var suppressedFiles: List<String> = emptyList() override var collectInheritedExtensionsFromLibraries: Boolean = false override var analysisPlatform: Platform = Platform.DEFAULT - var platform: String = "" + var platform: String? = null override var targets: List<String> = emptyList() override var sinceKotlin: String? = null diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt index d04ed5f0..7cc19900 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt @@ -2,18 +2,11 @@ package org.jetbrains.dokka.gradle import org.gradle.api.Plugin import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin import java.io.File import java.io.InputStream import java.util.* -/* -* Extension names, which are used in a build.gradle file as closure names: -* dokka { -* configuration { // extension name -* -* } -* } -* */ internal const val CONFIGURATION_EXTENSION_NAME = "configuration" internal const val MULTIPLATFORM_EXTENSION_NAME = "multiplatform" @@ -22,15 +15,17 @@ open class DokkaPlugin : Plugin<Project> { override fun apply(project: Project) { DokkaVersion.loadFrom(javaClass.getResourceAsStream("/META-INF/gradle-plugins/org.jetbrains.dokka.properties")) + // TODO: Register instead of create for Gradle >= 4.10 project.tasks.create("dokka", DokkaTask::class.java).apply { dokkaRuntime = project.configurations.create("dokkaRuntime") moduleName = project.name outputDirectory = File(project.buildDir, "dokka").absolutePath } + project.tasks.withType(DokkaTask::class.java) { task -> val passConfiguration = project.container(GradlePassConfigurationImpl::class.java) - task.extensions.add(MULTIPLATFORM_EXTENSION_NAME, passConfiguration) - task.extensions.create(CONFIGURATION_EXTENSION_NAME, GradlePassConfigurationImpl::class.java, "") + task.multiplatform = passConfiguration + task.configuration = GradlePassConfigurationImpl() } } } |