From eae1ce49d18c2978b49166ea502bf2c109a85504 Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Sat, 18 Jul 2020 12:18:59 +0200 Subject: Simplify Dokka Gradle Plugin --- runners/cli/src/main/kotlin/cli/main.kt | 20 +- .../dokka/gradle/AbstractDokkaParentTask.kt | 73 +++++ .../jetbrains/dokka/gradle/AbstractDokkaTask.kt | 55 ++-- .../dokka/gradle/ConfigurationExtractor.kt | 185 ------------ .../dokka/gradle/DokkaBootstrapFactory.kt | 18 -- .../jetbrains/dokka/gradle/DokkaCollectorTask.kt | 73 ++--- .../jetbrains/dokka/gradle/DokkaMultimoduleTask.kt | 78 ++--- .../dokka/gradle/DokkaSourceSetIDFactory.kt | 10 - .../kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt | 315 ++------------------- .../dokka/gradle/GradleDokkaSourceSetBuilder.kt | 298 +++++++++++++++++++ .../GradleExternalDocumentationLinkBuilder.kt | 22 ++ .../dokka/gradle/GradlePackageOptionsBuilder.kt | 36 +++ .../dokka/gradle/GradleSourceLinkBuilder.kt | 25 ++ .../dokka/gradle/GradleSourceRootBuilder.kt | 15 + .../jetbrains/dokka/gradle/KotlinSourceSetGist.kt | 105 +++++++ .../org/jetbrains/dokka/gradle/ProxyUtils.kt | 43 --- .../org/jetbrains/dokka/gradle/ReflectDsl.kt | 72 ----- .../gradle/TaskDependencyInternalWithAdditions.kt | 20 ++ .../jetbrains/dokka/gradle/automagicTypedProxy.kt | 46 +++ .../dokka/gradle/configurationImplementations.kt | 259 ----------------- .../dokka/gradle/defaultDokkaOutputDirectory.kt | 13 - .../kotlin/org/jetbrains/dokka/gradle/dokka.kt | 7 + .../dokka/gradle/dokkaBootstrapFactory.kt | 21 ++ .../jetbrains/dokka/gradle/dokkaConfigurations.kt | 41 --- .../dokka/gradle/dokkaDefaultOutputDirectory.kt | 13 + .../dokka/gradle/dokkaSourceSetIDFactory.kt | 10 + .../jetbrains/dokka/gradle/gradleConfigurations.kt | 41 +++ .../main/kotlin/org/jetbrains/dokka/gradle/main.kt | 1 - .../gradle/sourceSetKotlinGistConfiguration.kt | 21 ++ .../kotlin/org/jetbrains/dokka/gradle/utils.kt | 12 + .../dokka/gradle/AbstractDokkaParentTaskTest.kt | 107 +++++++ .../dokka/gradle/DokkaCollectorTaskTest.kt | 60 ++++ .../dokka/gradle/DokkaConfigurationJsonTest.kt | 51 +--- .../gradle/DokkaConfigurationSerializableTest.kt | 13 +- .../dokka/gradle/DokkaMultiModuleTaskTest.kt | 112 ++++++++ .../gradle/KotlinDslDokkaTaskConfigurationTest.kt | 5 +- runners/maven-plugin/src/main/kotlin/DokkaMojo.kt | 21 +- 37 files changed, 1183 insertions(+), 1134 deletions(-) create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTask.kt delete mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt delete mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaBootstrapFactory.kt delete mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetIDFactory.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleExternalDocumentationLinkBuilder.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradlePackageOptionsBuilder.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceLinkBuilder.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceRootBuilder.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/KotlinSourceSetGist.kt delete mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt delete mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ReflectDsl.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/TaskDependencyInternalWithAdditions.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/automagicTypedProxy.kt delete mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt delete mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/defaultDokkaOutputDirectory.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokka.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaBootstrapFactory.kt delete mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaConfigurations.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaDefaultOutputDirectory.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaSourceSetIDFactory.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/gradleConfigurations.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/sourceSetKotlinGistConfiguration.kt create mode 100644 runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTaskTest.kt create mode 100644 runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTaskTest.kt create mode 100644 runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTaskTest.kt (limited to 'runners') diff --git a/runners/cli/src/main/kotlin/cli/main.kt b/runners/cli/src/main/kotlin/cli/main.kt index f8d5c55c..df763596 100644 --- a/runners/cli/src/main/kotlin/cli/main.kt +++ b/runners/cli/src/main/kotlin/cli/main.kt @@ -17,11 +17,11 @@ class GlobalArguments(args: Array) : DokkaConfiguration { val json: String? by parser.argument(ArgType.String, description = "Json file name").optional() - override val outputDir by parser.option(ArgType.String, description = "Output directory path") + override val outputDir by parser.option(ArgTypeFile, description = "Output directory path") .default(DokkaDefaults.outputDir) override val cacheRoot by parser.option( - ArgType.String, + ArgTypeFile, description = "Path to cache folder, or 'default' to use ~/.cache/dokka, if not provided caching is disabled" ) @@ -132,12 +132,12 @@ private fun parseSourceSet(args: Array): DokkaConfiguration.DokkaSourceS ).default(DokkaDefaults.sourceSetDisplayName) val classpath by parser.option( - ArgType.String, + ArgTypeFile, description = "Classpath for symbol resolution (allows many paths separated by the semicolon `;`)" ).delimiter(";") val sourceRoots by parser.option( - ArgType.String, + ArgTypeFile, description = "Source file or directory (allows many paths separated by the semicolon `;`)", fullName = "src" ).delimiter(";") @@ -148,12 +148,12 @@ private fun parseSourceSet(args: Array): DokkaConfiguration.DokkaSourceS ).delimiter(";") val samples by parser.option( - ArgType.String, + ArgTypeFile, description = "Source root for samples (allows many paths separated by the semicolon `;`)" ).delimiter(";") val includes by parser.option( - ArgType.String, + ArgTypeFile, description = "Markdown files to load (allows many paths separated by the semicolon `;`)" ).delimiter(";") @@ -196,7 +196,7 @@ private fun parseSourceSet(args: Array): DokkaConfiguration.DokkaSourceS .default(DokkaDefaults.noJdkLink) val suppressedFiles by parser.option( - ArgType.String, + ArgTypeFile, description = "Paths to files to be suppressed (allows many paths separated by the semicolon `;`)" ).delimiter(";") @@ -228,12 +228,12 @@ private fun parseSourceSet(args: Array): DokkaConfiguration.DokkaSourceS override val displayName = displayName override val sourceSetID = DokkaSourceSetID(moduleName, sourceSetName) override val classpath = classpath - override val sourceRoots = sourceRoots.map { SourceRootImpl(it.toAbsolutePath()) } + override val sourceRoots = sourceRoots.map { SourceRootImpl(it) } override val dependentSourceSets: Set = dependentSourceSets .map { dependentSourceSetName -> dependentSourceSetName.split('/').let { DokkaSourceSetID(it[0], it[1]) } } .toSet() - override val samples = samples.map { it.toAbsolutePath() } - override val includes = includes.map { it.toAbsolutePath() } + override val samples = samples + override val includes = includes override val includeNonPublic = includeNonPublic override val includeRootPackage = includeRootPackage override val reportUndocumented = reportUndocumented diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTask.kt new file mode 100644 index 00000000..bf8308bf --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTask.kt @@ -0,0 +1,73 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.Project +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Nested +import org.jetbrains.dokka.DokkaBootstrap +import org.jetbrains.dokka.DokkaBootstrapImpl +import kotlin.reflect.KClass + +// TODO NOW: Test UP-TO-DATE behaviour +abstract class AbstractDokkaParentTask( + bootstrapClass: KClass = DokkaBootstrapImpl::class +) : AbstractDokkaTask(bootstrapClass) { + + @Input + open var dokkaTaskNames: Set = setOf() + + @Input + var subprojectPaths: Set = project.subprojects.map { project -> project.path }.toSet() + + @get:Internal + val subprojects: List + get() = subprojectPaths.map { path -> project.project(path) }.distinct() + + @get:Nested + internal val dokkaTasks: List + get() = dokkaTaskNames.flatMap { dokkaTaskName -> findSubprojectDokkaTasks(dokkaTaskName) } + + + /** + * Will remove a single project from participating in this parent task. + * Note: This will not remove the [project]s subprojects. + * + * @see removeAllProjects + */ + fun removeSubproject(project: Project) { + subprojectPaths = subprojectPaths - project.path + } + + /** + * Will remove the [project] and all its subprojects from participating in this parent task. + * @see removeSubproject + */ + fun removeAllProjects(project: Project) { + project.allprojects.forEach(::removeSubproject) + } + + /** + * Includes the [project] to participate in this parent task. + * Note: This will not include any of the [project]s subprojects. + * @see addAllProjects + */ + fun addSubproject(project: Project) { + subprojectPaths = (subprojectPaths + project.path) + } + + /** + * Includes the [project] and all its subprojects to participate in this parent task. + * @see addSubproject + */ + fun addAllProjects(project: Project) { + project.allprojects.forEach(::addSubproject) + } + + protected fun findSubprojectDokkaTasks(dokkaTaskNames: Set): List { + return dokkaTaskNames.flatMap { dokkaTaskName -> findSubprojectDokkaTasks(dokkaTaskName) } + } + + private fun findSubprojectDokkaTasks(dokkaTaskName: String): List { + return subprojects.mapNotNull { subproject -> subproject.tasks.findByName(dokkaTaskName) as? DokkaTask } + } +} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt index 1269b305..6413d788 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt @@ -1,21 +1,33 @@ package org.jetbrains.dokka.gradle import org.gradle.api.DefaultTask -import org.gradle.api.Project import org.gradle.api.artifacts.Configuration -import org.gradle.api.attributes.Usage -import org.gradle.api.tasks.Classpath -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.TaskAction -import org.gradle.kotlin.dsl.dependencies +import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.api.tasks.* import org.jetbrains.dokka.DokkaBootstrap +import org.jetbrains.dokka.DokkaConfigurationImpl import org.jetbrains.dokka.plugability.Configurable +import org.jetbrains.dokka.toJsonString +import java.io.File +import java.util.function.BiConsumer +import kotlin.reflect.KClass +abstract class AbstractDokkaTask( + private val bootstrapClass: KClass = DokkaBootstrap::class +) : DefaultTask(), Configurable { + + @OutputDirectory + var outputDirectory: File = defaultDokkaOutputDirectory() + + @Optional + @InputDirectory + var cacheRoot: File? = null + + @Input + var failOnWarning: Boolean = false -abstract class AbstractDokkaTask : DefaultTask(), Configurable { @Input - var outputDirectory: String = defaultDokkaOutputDirectory().absolutePath + var offlineMode: Boolean = false @Input override val pluginsConfiguration: MutableMap = mutableMapOf() @@ -27,19 +39,26 @@ abstract class AbstractDokkaTask : DefaultTask(), Configurable { val runtime: Configuration = project.maybeCreateDokkaRuntimeConfiguration(name) @TaskAction - protected fun run() { - val kotlinColorsEnabledBefore = System.getProperty(DokkaTask.COLORS_ENABLED_PROPERTY) ?: "false" - System.setProperty(DokkaTask.COLORS_ENABLED_PROPERTY, "false") - try { + protected open fun generateDocumentation() { + DokkaBootstrap(runtime, bootstrapClass).apply { + configure(buildDokkaConfiguration().toJsonString(), createProxyLogger()) generate() - } finally { - System.setProperty(DokkaTask.COLORS_ENABLED_PROPERTY, kotlinColorsEnabledBefore) } } - protected abstract fun generate() + internal abstract fun buildDokkaConfiguration(): DokkaConfigurationImpl + + private fun createProxyLogger(): BiConsumer = BiConsumer { level, message -> + when (level) { + "debug" -> logger.debug(message) + "info" -> logger.info(message) + "progress" -> logger.lifecycle(message) + "warn" -> logger.warn(message) + "error" -> logger.error(message) + } + } - protected fun DokkaBootstrap(bootstrapClassFQName: String): DokkaBootstrap { - return DokkaBootstrap(runtime, bootstrapClassFQName) + init { + group = JavaBasePlugin.DOCUMENTATION_GROUP } } 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 deleted file mode 100644 index 6217703f..00000000 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt +++ /dev/null @@ -1,185 +0,0 @@ -package org.jetbrains.dokka.gradle - -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.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension -import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension -import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation -import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.File -import java.io.Serializable - -class ConfigurationExtractor(private val project: Project) { - fun extractConfiguration(sourceSetName: String): PlatformData? { - val projectExtension = project.extensions.findByType(KotlinProjectExtension::class.java) ?: run { - project.logger.error("Missing kotlin project extension") - return null - } - - val sourceSet = projectExtension.sourceSets.findByName(sourceSetName) ?: run { - project.logger.error("No source set with name '$sourceSetName' found") - return null - } - - val compilation = try { - when (projectExtension) { - is KotlinMultiplatformExtension -> { - val targets = projectExtension.targets.flatMap { it.compilations } - targets.find { it.name == sourceSetName } - ?: targets.find { it.kotlinSourceSets.contains(sourceSet) } - } - is KotlinSingleTargetExtension -> projectExtension.target.compilations.find { - it.kotlinSourceSets.contains(sourceSet) - } - else -> null - } - } catch (e: NoClassDefFoundError) { // Old Kotlin plugin versions - null - } - - val sourceRoots = sourceSet.sourceFiles - val classpath = compilation?.classpath - ?: sourceRoots + sourceSet.allParentSourceFiles() - - return PlatformData( - sourceSetName, - classpath.filter { it.exists() }, - sourceRoots, - sourceSet.dependsOn.map { it.name }, - compilation?.target?.platformType?.name ?: "common" - ) - } - - private fun KotlinSourceSet.allParentSourceFiles(): List = - sourceFiles + dependsOn.flatMap { it.allParentSourceFiles() } - - fun extractFromJavaPlugin(): PlatformData? = - project.convention.findPlugin(JavaPluginConvention::class.java) - ?.run { sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME)?.allSource?.srcDirs } - ?.let { PlatformData(null, emptyList(), it.toList(), emptyList(), "") } - - fun extractFromKotlinTasks(kotlinTasks: List): List = - try { - kotlinTasks.map { extractFromKotlinTask(it) } - } catch (e: Throwable) { - when (e) { - is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException -> - listOfNotNull(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 - .flatMap { it.compilations }.firstOrNull { it.compileKotlinTask == task } - else -> throw e - } - }.let { compilation -> - PlatformData( - task.name, - compilation?.classpath.orEmpty(), - compilation?.sourceFiles.orEmpty(), - compilation?.dependentSourceSets?.map { it.name }.orEmpty(), - compilation?.platformType?.toString() ?: "" - ) - } - - private fun extractFromKotlinTasksTheHardWay(kotlinTasks: List): PlatformData? { - val allClasspath = mutableSetOf() - var allClasspathFileCollection: FileCollection = project.files() - val allSourceRoots = mutableSetOf() - - kotlinTasks.forEach { - with(ReflectDsl) { - val taskSourceRoots: List - val abstractKotlinCompileClz: Class - 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 = - (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 = try { - allClasspathFileCollection.toMutableList() - } catch (e: ResolveException) { - mutableListOf() - } - classpath.addAll(project.files(allClasspath).toList()) - - return PlatformData(null, classpath, allSourceRoots.toList(), emptyList(), "") - } - - private val KotlinCompilation<*>.sourceFiles: List - get() = kotlinSourceSets.flatMap { it.sourceFiles } - - private val KotlinSourceSet.sourceFiles: List - get() = kotlin.sourceDirectories.filter { it.exists() }.toList() - - private val KotlinCompilation<*>.dependentSourceSets: Set - get() = (allKotlinSourceSets - kotlinSourceSets) - - private val KotlinCompilation<*>.classpath: List - get() = if (target.isAndroidTarget()) { - getClasspathFromAndroidTask(this) - } else { - getClasspathFromRegularTask(this) - } - - // This is a workaround for KT-33893 - private fun getClasspathFromAndroidTask(compilation: KotlinCompilation<*>): List = (compilation - .compileKotlinTask as? KotlinCompile) - ?.classpath?.files?.toList() ?: getClasspathFromRegularTask(compilation) - - private fun getClasspathFromRegularTask(compilation: KotlinCompilation<*>): List { - // explicit dependencies of the compilation - val ownDependencyFiles: Set = compilation.compileDependencyFiles.files - - // the dependencies provided by the platform (e.g. Kotlin/Native platform libs) - val platformDependencyFiles: Set = (compilation as? KotlinNativeCompilation) - ?.target?.project?.configurations - ?.findByName(compilation.defaultSourceSet.implementationMetadataConfigurationName)?.files - ?: emptySet() - - return (ownDependencyFiles + platformDependencyFiles).toList().filter { it.exists() } - } - - data class PlatformData( - val name: String?, - val classpath: List, - val sourceRoots: List, - val dependentSourceSets: List, - val platform: String - ) : Serializable -} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaBootstrapFactory.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaBootstrapFactory.kt deleted file mode 100644 index df29c19b..00000000 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaBootstrapFactory.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.jetbrains.dokka.gradle - -import org.gradle.api.artifacts.Configuration -import org.jetbrains.dokka.DokkaBootstrap -import java.net.URLClassLoader - - -fun DokkaBootstrap(configuration: Configuration, bootstrapClassFQName: String): DokkaBootstrap { - val runtimeJars = configuration.resolve() - val runtimeClassLoader = URLClassLoader( - runtimeJars.map { it.toURI().toURL() }.toTypedArray(), - ClassLoader.getSystemClassLoader().parent - ) - - val bootstrapClass = runtimeClassLoader.loadClass(bootstrapClassFQName) - val bootstrapInstance = bootstrapClass.constructors.first().newInstance() - return automagicTypedProxy(DokkaPlugin::class.java.classLoader, bootstrapInstance) -} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTask.kt index 8d337795..7a73d633 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTask.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTask.kt @@ -1,59 +1,24 @@ package org.jetbrains.dokka.gradle -import com.google.gson.GsonBuilder -import org.gradle.api.plugins.JavaBasePlugin.DOCUMENTATION_GROUP -import org.gradle.api.tasks.Input -import org.jetbrains.dokka.toJsonString - -open class DokkaCollectorTask : AbstractDokkaTask() { - - @Input - var modules: List = emptyList() - - @Input - var dokkaTaskNames: Set = setOf() - - override fun generate() { - val configurations = getSubprojectDokkaTasks(dokkaTaskNames) - .mapNotNull { dokkaTask -> dokkaTask.getConfigurationOrNull() } - - val initial = GradleDokkaConfigurationImpl().apply { - outputDir = outputDirectory - cacheRoot = configurations.first().cacheRoot - } - - val configuration = configurations.fold(initial) { acc, it: GradleDokkaConfigurationImpl -> - if (acc.cacheRoot != it.cacheRoot) - throw IllegalStateException("Dokka task configurations differ on core argument cacheRoot") - acc.sourceSets = acc.sourceSets + it.sourceSets - acc.pluginsClasspath = (acc.pluginsClasspath + it.pluginsClasspath).distinct() - acc +import org.jetbrains.dokka.DokkaConfigurationImpl + +open class DokkaCollectorTask : AbstractDokkaParentTask() { + + override fun buildDokkaConfiguration(): DokkaConfigurationImpl { + val initialDokkaConfiguration = DokkaConfigurationImpl( + outputDir = outputDirectory, + cacheRoot = cacheRoot, + failOnWarning = failOnWarning, + offlineMode = offlineMode, + pluginsClasspath = plugins.resolve().toList(), + ) + + val subprojectDokkaConfigurations = dokkaTasks.map { dokkaTask -> dokkaTask.buildDokkaConfiguration() } + return subprojectDokkaConfigurations.fold(initialDokkaConfiguration) { acc, it: DokkaConfigurationImpl -> + acc.copy( + sourceSets = acc.sourceSets + it.sourceSets, + pluginsClasspath = acc.pluginsClasspath + it.pluginsClasspath + ) } - - val bootstrap = DokkaBootstrap("org.jetbrains.dokka.DokkaBootstrapImpl") - bootstrap.configure(configuration.toJsonString()) { level, message -> - when (level) { - "debug" -> logger.debug(message) - "info" -> logger.info(message) - "progress" -> logger.lifecycle(message) - "warn" -> logger.warn(message) - "error" -> logger.error(message) - } - } - bootstrap.generate() - } - - private fun getSubprojectDokkaTasks(dokkaTaskName: String): List { - return project.subprojects - .filter { subproject -> subproject.name in modules } - .mapNotNull { subproject -> subproject.tasks.findByName(dokkaTaskName) as? DokkaTask } - } - - private fun getSubprojectDokkaTasks(dokkaTaskNames: Set): List { - return dokkaTaskNames.flatMap { dokkaTaskName -> getSubprojectDokkaTasks(dokkaTaskName) } - } - - init { - group = DOCUMENTATION_GROUP } } diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt index 986b883a..8369954b 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt @@ -1,66 +1,40 @@ package org.jetbrains.dokka.gradle -import org.gradle.api.plugins.JavaBasePlugin.DOCUMENTATION_GROUP +import org.gradle.api.internal.tasks.TaskDependencyInternal import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal +import org.jetbrains.dokka.DokkaConfigurationImpl +import org.jetbrains.dokka.DokkaModuleDescriptionImpl +import org.jetbrains.dokka.DokkaMultimoduleBootstrapImpl import org.jetbrains.dokka.plugability.Configurable -import org.jetbrains.dokka.toJsonString -open class DokkaMultimoduleTask : AbstractDokkaTask(), Configurable { +open class DokkaMultimoduleTask : AbstractDokkaParentTask(DokkaMultimoduleBootstrapImpl::class), Configurable { + /** + * Name of the file containing all necessary module information. + * This file has to be placed inside the subrpojects root directory. + */ @Input var documentationFileName: String = "README.md" - - @Input - var dokkaTaskNames: Set = setOf() - set(value) { - field = value.toSet() - setDependsOn(getSubprojectDokkaTasks(value)) - } - - - override fun generate() { - val bootstrap = DokkaBootstrap("org.jetbrains.dokka.DokkaMultimoduleBootstrapImpl") - val configuration = getConfiguration() - bootstrap.configure(configuration.toJsonString()) { level, message -> - when (level) { - "debug" -> logger.debug(message) - "info" -> logger.info(message) - "progress" -> logger.lifecycle(message) - "warn" -> logger.warn(message) - "error" -> logger.error(message) - } - } - bootstrap.generate() + override fun getTaskDependencies(): TaskDependencyInternal { + return super.getTaskDependencies() + dokkaTasks } - @Internal - internal fun getConfiguration(): GradleDokkaConfigurationImpl = - GradleDokkaConfigurationImpl().apply { - outputDir = project.file(outputDirectory).absolutePath - pluginsClasspath = plugins.resolve().toList() - pluginsConfiguration = this@DokkaMultimoduleTask.pluginsConfiguration - modules = getSubprojectDokkaTasks(dokkaTaskNames).map { dokkaTask -> - GradleDokkaModuleDescription().apply { - name = dokkaTask.project.name - path = dokkaTask.project.projectDir.resolve(dokkaTask.outputDirectory) - .toRelativeString(project.file(outputDirectory)) - docFile = dokkaTask.project.projectDir.resolve(documentationFileName).absolutePath - } + override fun buildDokkaConfiguration(): DokkaConfigurationImpl { + return DokkaConfigurationImpl( + outputDir = outputDirectory, + cacheRoot = cacheRoot, + pluginsConfiguration = pluginsConfiguration, + failOnWarning = failOnWarning, + offlineMode = offlineMode, + pluginsClasspath = plugins.resolve().toList(), + modules = dokkaTasks.map { dokkaTask -> + DokkaModuleDescriptionImpl( + name = dokkaTask.project.name, + path = dokkaTask.outputDirectory.relativeTo(outputDirectory), + docFile = dokkaTask.project.projectDir.resolve(documentationFileName).absoluteFile + ) } - } - - private fun getSubprojectDokkaTasks(dokkaTaskName: String): List { - return project.subprojects - .mapNotNull { subproject -> subproject.tasks.findByName(dokkaTaskName) as? DokkaTask } - } - - private fun getSubprojectDokkaTasks(dokkaTaskNames: Set): List { - return dokkaTaskNames.flatMap { dokkaTaskName -> getSubprojectDokkaTasks(dokkaTaskName) } - } - - init { - group = DOCUMENTATION_GROUP + ) } } diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetIDFactory.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetIDFactory.kt deleted file mode 100644 index 3fadb4fd..00000000 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetIDFactory.kt +++ /dev/null @@ -1,10 +0,0 @@ -@file:Suppress("FunctionName") - -package org.jetbrains.dokka.gradle - -import org.gradle.api.Project -import org.jetbrains.dokka.DokkaSourceSetID - -internal fun DokkaSourceSetID(project: Project, sourceSetName: String): DokkaSourceSetID { - return DokkaSourceSetID(moduleName = project.path, sourceSetName = sourceSetName) -} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt index b4601acf..a74068ae 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt @@ -1,306 +1,35 @@ package org.jetbrains.dokka.gradle import org.gradle.api.NamedDomainObjectContainer -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.file.FileCollection import org.gradle.api.internal.plugins.DslObject -import org.gradle.api.plugins.JavaBasePlugin -import org.gradle.api.tasks.* -import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink.Builder -import org.jetbrains.dokka.DokkaConfiguration.SourceRoot -import org.jetbrains.dokka.DokkaException -import org.jetbrains.dokka.Platform -import org.jetbrains.dokka.ReflectDsl -import org.jetbrains.dokka.ReflectDsl.isNotInstance -import org.jetbrains.dokka.gradle.ConfigurationExtractor.PlatformData -import org.jetbrains.dokka.toJsonString -import java.io.File -import java.net.URL -import java.util.concurrent.Callable +import org.gradle.api.tasks.Nested +import org.jetbrains.dokka.DokkaBootstrapImpl +import org.jetbrains.dokka.DokkaConfigurationImpl +import org.jetbrains.dokka.build -open class DokkaTask : AbstractDokkaTask() { - private val ANDROID_REFERENCE_URL = Builder("https://developer.android.com/reference/").build() - - private val ANDROIDX_REFERENCE_URL = Builder( - url = URL("https://developer.android.com/reference/kotlin/"), - packageListUrl = URL("https://developer.android.com/reference/androidx/package-list") - ).build() - - private val configExtractor = ConfigurationExtractor(project) - - @Suppress("MemberVisibilityCanBePrivate") - fun defaultKotlinTasks(): List = 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() - } - - 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 } }) - } - - @Optional - @Input - var cacheRoot: String? = null - - - /** - * Hack used by DokkaCollector to enforce a different configuration to be used. - */ - @get:Internal - internal var enforcedConfiguration: GradleDokkaConfigurationImpl? = null +open class DokkaTask : AbstractDokkaTask(DokkaBootstrapImpl::class) { @get:Nested - val dokkaSourceSets: NamedDomainObjectContainer = - project.container(GradleDokkaSourceSet::class.java) { name -> GradleDokkaSourceSet(name, project) } - .also { container -> DslObject(this).extensions.add("dokkaSourceSets", container) } - - - private val kotlinTasks: List by lazy { - extractKotlinCompileTasks( - dokkaSourceSets.mapNotNull { - it.collectKotlinTasks?.invoke() - }.takeIf { it.isNotEmpty() }?.flatten() ?: defaultKotlinTasks() - ) - } - - @Input - var disableAutoconfiguration: Boolean = false - - @Input - var failOnWarning: Boolean = false - - @Input - var offlineMode: Boolean = false - - private var outputDiagnosticInfo: Boolean = - false // Workaround for Gradle, which fires some methods (like collectConfigurations()) multiple times in its lifecycle - - protected fun extractKotlinCompileTasks(collectTasks: List?): List { - val inputList = (collectTasks ?: emptyList()).filterNotNull() - val (paths, other) = inputList.partition { it is String } - - val tasksByPath = paths.map { - project.tasks.findByPath(it as String) ?: throw IllegalArgumentException("Task with path '$it' not found") - } - - other - .filter { it !is Task || it isNotInstance getAbstractKotlinCompileFor(it) } - .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 - } - - private fun Iterable.toSourceRoots(): List = - this.filter { it.exists() }.map { GradleSourceRootImpl().apply { path = it.path } } - - private fun Iterable.toProjects(): List = - project.subprojects.toList().filter { this.contains(it.name) } - - protected open fun collectSuppressedFiles(sourceRoots: List) = - 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() - } - - override fun generate() = enforcedConfiguration?.let { generate(it) } ?: generate(getConfigurationOrThrow()) - - protected open fun generate(configuration: GradleDokkaConfigurationImpl) { - outputDiagnosticInfo = true - val bootstrap = DokkaBootstrap("org.jetbrains.dokka.DokkaBootstrapImpl") - - bootstrap.configure(configuration.toJsonString()) { level, message -> - when (level) { - "debug" -> logger.debug(message) - "info" -> logger.info(message) - "progress" -> logger.lifecycle(message) - "warn" -> logger.warn(message) - "error" -> logger.error(message) - } - } - bootstrap.generate() - } - - - @Internal - internal fun getConfigurationOrNull(): GradleDokkaConfigurationImpl? { - val defaultModulesConfiguration = configuredDokkaSourceSets - .map { configureDefault(it) }.takeIf { it.isNotEmpty() } - ?: listOf( - configureDefault(configureDokkaSourceSet(dokkaSourceSets.create("main"))) - ).takeIf { project.isNotMultiplatformProject() } ?: emptyList() - - if (defaultModulesConfiguration.isEmpty()) { - return null - } - - return GradleDokkaConfigurationImpl().apply { - outputDir = project.file(outputDirectory).absolutePath - cacheRoot = this@DokkaTask.cacheRoot - offlineMode = this@DokkaTask.offlineMode - sourceSets = defaultModulesConfiguration - pluginsClasspath = plugins.resolve().toList() - pluginsConfiguration = this@DokkaTask.pluginsConfiguration - failOnWarning = this@DokkaTask.failOnWarning - } - } - - @Internal - internal fun getConfigurationOrThrow(): GradleDokkaConfigurationImpl { - return getConfigurationOrNull() ?: throw DokkaException( - """ - No source sets to document found. - Make source to configure at least one source set e.g. - - tasks { - dokkaHtml { - dokkaSourceSets { - register("commonMain") { - displayName = "common" - platform = "common" - } - } + val dokkaSourceSets: NamedDomainObjectContainer = + project.container(GradleDokkaSourceSetBuilder::class.java, GradleDokkaSourceSetBuilderFactory()) + .also { container -> + DslObject(this).extensions.add("dokkaSourceSets", container) + project.findKotlinSourceSets().orEmpty().forEach { kotlinSourceSet -> + container.register(kotlinSourceSet.name) { dokkaSourceSet -> + dokkaSourceSet.configureWithKotlinSourceSetGist(kotlinSourceSet) } } - """ - ) - } - - @get:Internal - protected val configuredDokkaSourceSets: List - get() = dokkaSourceSets.map { configureDokkaSourceSet(it) } - - private fun configureDokkaSourceSet(config: GradleDokkaSourceSet): GradleDokkaSourceSet { - val userConfig = config - .apply { - collectKotlinTasks?.let { - configExtractor.extractFromKotlinTasks(extractKotlinCompileTasks(it())) - .fold(this) { config, platformData -> - mergeUserConfigurationAndPlatformData(config, platformData) - } - } - } - - if (disableAutoconfiguration) return userConfig - - return configExtractor.extractConfiguration(userConfig.name) - ?.let { mergeUserConfigurationAndPlatformData(userConfig, it) } - ?: if (this.dokkaSourceSets.isNotEmpty()) { - if (outputDiagnosticInfo) - logger.warn( - "Could not find source set with name: ${userConfig.name} in Kotlin Gradle Plugin, " + - "using only user provided configuration for this source set" - ) - userConfig - } else { - if (outputDiagnosticInfo) - logger.warn("Could not find source set with name: ${userConfig.name} in Kotlin Gradle Plugin") - collectFromSinglePlatformOldPlugin(userConfig.name, userConfig) } - } - - private fun collectFromSinglePlatformOldPlugin(name: String, userConfig: GradleDokkaSourceSet) = - kotlinTasks.find { it.name == name } - ?.let { configExtractor.extractFromKotlinTasks(listOf(it)) } - ?.singleOrNull() - ?.let { mergeUserConfigurationAndPlatformData(userConfig, it) } - ?: configExtractor.extractFromJavaPlugin() - ?.let { mergeUserConfigurationAndPlatformData(userConfig, it) } - ?: userConfig - - private fun mergeUserConfigurationAndPlatformData( - userConfig: GradleDokkaSourceSet, - autoConfig: PlatformData - ) = userConfig.copy().apply { - sourceRoots.addAll(userConfig.sourceRoots.union(autoConfig.sourceRoots.toSourceRoots()).distinct()) - dependentSourceSets.addAll(userConfig.dependentSourceSets) - dependentSourceSets.addAll(autoConfig.dependentSourceSets.map { DokkaSourceSetID(project, it) }) - classpath = userConfig.classpath.union(autoConfig.classpath.map { it.absolutePath }).distinct() - if (userConfig.platform == null && autoConfig.platform != "") - platform = autoConfig.platform - } - - private fun configureDefault(config: GradleDokkaSourceSet): GradleDokkaSourceSet { - if (config.moduleDisplayName.isBlank()) { - config.moduleDisplayName = project.name - } - - if (config.displayName.isBlank()) { - config.displayName = config.name.substringBeforeLast("Main", config.platform.toString()) - } - - if (project.isAndroidProject() && !config.noAndroidSdkLink) { - config.externalDocumentationLinks.add(ANDROID_REFERENCE_URL) - config.externalDocumentationLinks.add(ANDROIDX_REFERENCE_URL) - } - - if (config.platform?.isNotBlank() == true) { - config.analysisPlatform = dokkaPlatformFromString(config.platform.toString()) - } - // Workaround for Groovy's GStringImpl - config.classpath = (config.classpath as List).map { it.toString() }.distinct() - config.sourceRoots = config.sourceRoots.distinct().toMutableList() - config.samples = config.samples.map { project.file(it).absolutePath } - config.includes = config.includes.map { project.file(it).absolutePath } - config.suppressedFiles += collectSuppressedFiles(config.sourceRoots) - config.suppressedFiles = config.suppressedFiles.map { project.file(it).absolutePath } - - return config - } - - private fun dokkaPlatformFromString(platform: String) = when (platform.toLowerCase()) { - "androidjvm", "android" -> Platform.jvm - "metadata" -> Platform.common - 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 = configuredDokkaSourceSets.let { config -> - project.files(config.flatMap { it.sourceRoots }.map { project.fileTree(File(it.path)) }) + - project.files(config.flatMap { it.includes }) + - project.files(config.flatMap { it.samples }.map { project.fileTree(File(it)) }) - } - - @Classpath - fun getInputClasspath(): FileCollection = - project.files((configuredDokkaSourceSets.flatMap { it.classpath } as List) - .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 - } + override fun buildDokkaConfiguration(): DokkaConfigurationImpl { + return DokkaConfigurationImpl( + outputDir = outputDirectory, + cacheRoot = cacheRoot, + offlineMode = offlineMode, + failOnWarning = failOnWarning, + sourceSets = dokkaSourceSets.build(), + pluginsConfiguration = pluginsConfiguration, + pluginsClasspath = plugins.resolve().toList() + ) } } diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt new file mode 100644 index 00000000..e420e1a5 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleDokkaSourceSetBuilder.kt @@ -0,0 +1,298 @@ +@file:Suppress("FunctionName") + +package org.jetbrains.dokka.gradle + +import com.android.build.gradle.api.AndroidSourceSet +import com.fasterxml.jackson.annotation.JsonIgnore +import groovy.lang.Closure +import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.* +import org.gradle.util.ConfigureUtil +import org.jetbrains.dokka.* +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import java.io.File +import java.net.URL +import org.jetbrains.kotlin.gradle.model.SourceSet as KotlinModelSourceSet + + +internal fun Task.GradleDokkaSourceSetBuilderFactory(): (name: String) -> GradleDokkaSourceSetBuilder = + { name -> GradleDokkaSourceSetBuilder(name, project) } + +open class GradleDokkaSourceSetBuilder constructor( + @get:JsonIgnore @Transient @get:Input val name: String, + @get:JsonIgnore @Transient @get:Internal internal val project: Project +) : DokkaConfigurationBuilder { + + @Classpath + @Optional + var classpath: List = emptyList() + + @Input + @Optional + var moduleDisplayName: String? = null + + @Input + @Optional + var displayName: String? = null + + @get:Internal + val sourceSetID: DokkaSourceSetID = DokkaSourceSetID(project, name) + + @Nested + var sourceRoots: MutableList = mutableListOf() + + @Input + var dependentSourceSets: MutableSet = mutableSetOf() + + @InputFiles + @Optional + var samples: List = emptyList() + + @InputFiles + @Optional + var includes: List = emptyList() + + @Input + var includeNonPublic: Boolean = DokkaDefaults.includeNonPublic + + @Input + var includeRootPackage: Boolean = DokkaDefaults.includeRootPackage + + @Input + var reportUndocumented: Boolean = DokkaDefaults.reportUndocumented + + @Input + var skipEmptyPackages: Boolean = DokkaDefaults.skipEmptyPackages + + @Input + var skipDeprecated: Boolean = DokkaDefaults.skipDeprecated + + @Input + var jdkVersion: Int = DokkaDefaults.jdkVersion + + @Nested + var sourceLinks: MutableList = mutableListOf() + + @Nested + var perPackageOptions: MutableList = mutableListOf() + + @Nested + var externalDocumentationLinks: MutableList = mutableListOf() + + @Input + @Optional + var languageVersion: String? = null + + @Input + @Optional + var apiVersion: String? = null + + @Input + var noStdlibLink: Boolean = DokkaDefaults.noStdlibLink + + @Input + var noJdkLink: Boolean = DokkaDefaults.noJdkLink + + @Input + var noAndroidSdkLink: Boolean = false + + @Input + var suppressedFiles: List = emptyList() + + @Input + @Optional + var analysisPlatform: Platform? = null + + @Input + @Optional + var platform: String? = null + + fun DokkaSourceSetID(sourceSetName: String): DokkaSourceSetID { + return DokkaSourceSetID(project, sourceSetName) + } + + fun dependsOn(sourceSet: SourceSet) { + dependsOn(DokkaSourceSetID(sourceSet.name)) + } + + fun dependsOn(sourceSet: GradleDokkaSourceSetBuilder) { + dependsOn(sourceSet.sourceSetID) + } + + fun dependsOn(sourceSet: DokkaConfiguration.DokkaSourceSet) { + dependsOn(sourceSet.sourceSetID) + } + + fun dependsOn(sourceSetName: String) { + dependsOn(DokkaSourceSetID(sourceSetName)) + } + + fun dependsOn(sourceSetID: DokkaSourceSetID) { + dependentSourceSets.add(sourceSetID) + } + + // TODO NOW: Cover with tests + fun sourceRoot(c: Closure) { + val configured = ConfigureUtil.configure(c, GradleSourceRootBuilder()) + sourceRoots.add(configured) + } + + fun sourceRoot(action: Action) { + val sourceRoot = GradleSourceRootBuilder() + action.execute(sourceRoot) + sourceRoots.add(sourceRoot) + } + + fun sourceLink(c: Closure) { + val configured = ConfigureUtil.configure(c, GradleSourceLinkBuilder()) + sourceLinks.add(configured) + } + + fun sourceLink(action: Action) { + val sourceLink = GradleSourceLinkBuilder() + action.execute(sourceLink) + sourceLinks.add(sourceLink) + } + + fun perPackageOption(c: Closure) { + val configured = ConfigureUtil.configure(c, GradlePackageOptionsBuilder()) + perPackageOptions.add(configured) + } + + fun perPackageOption(action: Action) { + val option = GradlePackageOptionsBuilder() + action.execute(option) + perPackageOptions.add(option) + } + + fun externalDocumentationLink(c: Closure) { + val link = ConfigureUtil.configure(c, GradleExternalDocumentationLinkBuilder()) + externalDocumentationLinks.add(link) + } + + fun externalDocumentationLink(action: Action) { + val link = GradleExternalDocumentationLinkBuilder() + action.execute(link) + externalDocumentationLinks.add(link) + } + + fun externalDocumentationLink(url: String, packageListUrl: String? = null) { + externalDocumentationLinks.add( + GradleExternalDocumentationLinkBuilder().apply { + this.url = URL(url) + this.packageListUrl = URL(packageListUrl) + } + ) + } + + fun externalDocumentationLink(url: URL, packageListUrl: URL? = null) { + externalDocumentationLinks.add( + GradleExternalDocumentationLinkBuilder().apply { + this.url = url + if (packageListUrl != null) { + this.packageListUrl = packageListUrl + } + } + ) + } + + override fun build(): DokkaSourceSetImpl { + val moduleDisplayName = moduleDisplayName ?: project.name + + val displayName = displayName ?: name.substringBeforeLast("Main", platform.toString()) + + val externalDocumentationLinks = externalDocumentationLinks.map { it.build() } + .run { + if (noJdkLink) this + else this + ExternalDocumentationLink( + url = + if (jdkVersion < 11) "https://docs.oracle.com/javase/${jdkVersion}/docs/api/" + else "https://docs.oracle.com/en/java/javase/${jdkVersion}/docs/api/java.base/", + packageListUrl = + if (jdkVersion < 11) "https://docs.oracle.com/javase/${jdkVersion}/docs/api/package-list" + else "https://docs.oracle.com/en/java/javase/${jdkVersion}/docs/api/element-list" + ) + } + .run { + if (noStdlibLink) this + else this + ExternalDocumentationLink("https://kotlinlang.org/api/latest/jvm/stdlib/") + } + .run { + if (noAndroidSdkLink || !project.isAndroidProject()) this + else this + + ExternalDocumentationLink("https://developer.android.com/reference/") + + ExternalDocumentationLink( + url = URL("https://developer.android.com/reference/kotlin/"), + packageListUrl = URL("https://developer.android.com/reference/androidx/package-list") + ) + } + + val analysisPlatform = when { + analysisPlatform != null -> checkNotNull(analysisPlatform) + + platform?.isNotBlank() == true -> when (val platform = platform.toString().toLowerCase()) { + "androidjvm", "android" -> Platform.jvm + "metadata" -> Platform.common + else -> Platform.fromString(platform) + } + + else -> Platform.DEFAULT + } + + val sourceRoots = sourceRoots.build().distinct() + + val suppressedFiles = suppressedFiles + project.collectSuppressedFiles(sourceRoots) + + return DokkaSourceSetImpl( + classpath = classpath.distinct().toList(), + moduleDisplayName = moduleDisplayName, + displayName = displayName, + sourceSetID = sourceSetID, + sourceRoots = sourceRoots, + dependentSourceSets = dependentSourceSets.toSet(), + samples = samples.toList(), + includes = includes.toList(), + includeNonPublic = includeNonPublic, + includeRootPackage = includeRootPackage, + reportUndocumented = reportUndocumented, + skipEmptyPackages = skipEmptyPackages, + skipDeprecated = skipDeprecated, + jdkVersion = jdkVersion, + sourceLinks = sourceLinks.build(), + perPackageOptions = perPackageOptions.build(), + externalDocumentationLinks = externalDocumentationLinks, + languageVersion = languageVersion, + apiVersion = apiVersion, + noStdlibLink = noStdlibLink, + noJdkLink = noJdkLink, + suppressedFiles = suppressedFiles, + analysisPlatform = analysisPlatform + ) + } +} + +fun GradleDokkaSourceSetBuilder.dependsOn(sourceSet: KotlinModelSourceSet) { + dependsOn(DokkaSourceSetID(sourceSet.name)) +} + +fun GradleDokkaSourceSetBuilder.dependsOn(sourceSet: KotlinSourceSet) { + dependsOn(DokkaSourceSetID(sourceSet.name)) +} + +fun GradleDokkaSourceSetBuilder.dependsOn(sourceSet: AndroidSourceSet) { + dependsOn(DokkaSourceSetID(sourceSet.name)) +} + +// TODO NOW: Test +private fun Project.collectSuppressedFiles(sourceRoots: List): List = + if (project.isAndroidProject()) { + val generatedRoot = project.buildDir.resolve("generated").absoluteFile + sourceRoots + .map { it.directory } + .filter { it.startsWith(generatedRoot) } + .flatMap { it.walk().toList() } + } else { + emptyList() + } diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleExternalDocumentationLinkBuilder.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleExternalDocumentationLinkBuilder.kt new file mode 100644 index 00000000..84ad6c1e --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleExternalDocumentationLinkBuilder.kt @@ -0,0 +1,22 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.tasks.Input +import org.jetbrains.dokka.DokkaConfigurationBuilder +import org.jetbrains.dokka.ExternalDocumentationLink +import org.jetbrains.dokka.ExternalDocumentationLinkImpl +import java.net.URL + +class GradleExternalDocumentationLinkBuilder : DokkaConfigurationBuilder { + @Input + var url: URL? = null + + @Input + var packageListUrl: URL? = null + + override fun build(): ExternalDocumentationLinkImpl { + return ExternalDocumentationLink( + url = checkNotNull(url) { "url not specified " }, + packageListUrl = packageListUrl + ) + } +} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradlePackageOptionsBuilder.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradlePackageOptionsBuilder.kt new file mode 100644 index 00000000..fdc0275e --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradlePackageOptionsBuilder.kt @@ -0,0 +1,36 @@ +@file:Suppress("FunctionName") + +package org.jetbrains.dokka.gradle + +import org.gradle.api.tasks.Input +import org.jetbrains.dokka.DokkaConfigurationBuilder +import org.jetbrains.dokka.DokkaDefaults +import org.jetbrains.dokka.PackageOptionsImpl + + +class GradlePackageOptionsBuilder : DokkaConfigurationBuilder { + @Input + var prefix: String = "" + + @Input + var includeNonPublic: Boolean = DokkaDefaults.includeNonPublic + + @Input + var reportUndocumented: Boolean = DokkaDefaults.reportUndocumented + + @Input + var skipDeprecated: Boolean = DokkaDefaults.skipDeprecated + + @Input + var suppress: Boolean = DokkaDefaults.suppress + + override fun build(): PackageOptionsImpl { + return PackageOptionsImpl( + prefix = prefix, + includeNonPublic = includeNonPublic, + reportUndocumented = reportUndocumented, + skipDeprecated = skipDeprecated, + suppress = suppress + ) + } +} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceLinkBuilder.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceLinkBuilder.kt new file mode 100644 index 00000000..007575ec --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceLinkBuilder.kt @@ -0,0 +1,25 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.tasks.Input +import org.jetbrains.dokka.DokkaConfigurationBuilder +import org.jetbrains.dokka.SourceLinkDefinitionImpl + +class GradleSourceLinkBuilder : DokkaConfigurationBuilder { + // TODO NOW: CHECK UP TO DATE + @Input + var path: String = "" + + @Input + var url: String = "" + + @Input + var lineSuffix: String? = null + + override fun build(): SourceLinkDefinitionImpl { + return SourceLinkDefinitionImpl( + path = path, + url = url, + lineSuffix = lineSuffix + ) + } +} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceRootBuilder.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceRootBuilder.kt new file mode 100644 index 00000000..687dec9c --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/GradleSourceRootBuilder.kt @@ -0,0 +1,15 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.tasks.InputDirectory +import org.jetbrains.dokka.DokkaConfigurationBuilder +import org.jetbrains.dokka.SourceRootImpl +import java.io.File + +class GradleSourceRootBuilder : DokkaConfigurationBuilder { + @InputDirectory + var directory: File? = null + + override fun build(): SourceRootImpl { + return SourceRootImpl(checkNotNull(directory) { "directory not set" }) + } +} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/KotlinSourceSetGist.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/KotlinSourceSetGist.kt new file mode 100644 index 00000000..334aae15 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/KotlinSourceSetGist.kt @@ -0,0 +1,105 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.Project +import org.jetbrains.dokka.utilities.cast +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import java.io.File + +private typealias KotlinCompilation = + org.jetbrains.kotlin.gradle.plugin.KotlinCompilation + +internal data class KotlinSourceSetGist( + val name: String, + val platform: String, + val classpath: List, + val sourceRoots: List, + val dependentSourceSets: List, +) + +/** + * @return null if the kotlin extension cannot be found, + * A list of [KotlinSourceSetGist] for every currently registered kotlin source set + */ +internal fun Project.findKotlinSourceSets(): List? { + val kotlin = kotlinExtensionOrNull ?: return null + return kotlin.sourceSets.map { sourceSet -> kotlin.gistOf(sourceSet) } +} + +internal fun KotlinProjectExtension.gistOf(sourceSet: KotlinSourceSet): KotlinSourceSetGist { + return KotlinSourceSetGist( + name = sourceSet.name, + platform = platformOf(sourceSet), + classpath = classpathOf(sourceSet).filter(File::exists), + sourceRoots = sourceSet.kotlin.sourceDirectories.toList().filter(File::exists), + dependentSourceSe