diff options
Diffstat (limited to 'runners/gradle-plugin')
20 files changed, 1072 insertions, 556 deletions
diff --git a/runners/gradle-plugin/build.gradle b/runners/gradle-plugin/build.gradle deleted file mode 100644 index ceb03bae..00000000 --- a/runners/gradle-plugin/build.gradle +++ /dev/null @@ -1,108 +0,0 @@ -import com.gradle.publish.DependenciesBuilder - -apply plugin: 'java' -apply plugin: 'kotlin' - - -apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: "com.gradle.plugin-publish" - -sourceCompatibility = 1.8 - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions { - freeCompilerArgs += "-Xjsr305=strict" - languageVersion = language_version - apiVersion = language_version - jvmTarget = "1.8" - } -} - -repositories { - jcenter() - google() -} - -dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' - - shadow group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlin_for_gradle_runtime_version - shadow group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_for_gradle_runtime_version - - 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) { - from sourceSets.main.allSource -} - -processResources { - eachFile { - if (it.name == "org.jetbrains.dokka.properties") { - it.filter { line -> - line.replace("<version>", dokka_version) - } - } - } -} - -shadowJar { - baseName = 'dokka-gradle-plugin' - classifier = '' -} - -apply plugin: 'maven-publish' - -publishing { - publications { - dokkaGradlePlugin(MavenPublication) { publication -> - artifactId = 'dokka-gradle-plugin' - - artifact sourceJar { - classifier "sources" - } - - project.shadow.component(publication) - } - } -} - -bintrayPublication(project, ['dokkaGradlePlugin']) - -configurations.archives.artifacts.clear() -artifacts { - archives shadowJar -} - -pluginBundle { - website = 'https://www.kotlinlang.org/' - vcsUrl = 'https://github.com/kotlin/dokka.git' - description = 'Dokka, the Kotlin documentation tool' - tags = ['dokka', 'kotlin', 'kdoc', 'android'] - - plugins { - dokkaGradlePlugin { - id = 'org.jetbrains.dokka' - displayName = 'Dokka plugin' - } - } - - withDependencies { List<Dependency> list -> - list.clear() - def builder = new DependenciesBuilder() - builder.addUniqueScopedDependencies(list, configurations.shadow, "compile") - } - - mavenCoordinates { - groupId = "org.jetbrains.dokka" - artifactId = "dokka-gradle-plugin" - } -}
\ No newline at end of file diff --git a/runners/gradle-plugin/build.gradle.kts b/runners/gradle-plugin/build.gradle.kts new file mode 100644 index 00000000..0222f5e0 --- /dev/null +++ b/runners/gradle-plugin/build.gradle.kts @@ -0,0 +1,63 @@ +import org.jetbrains.configureBintrayPublication +import org.jetbrains.dokkaVersion + +plugins { + `java-gradle-plugin` +} + +repositories { + google() +} + +dependencies { + implementation(project(":core")) + 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(gradleKotlinDsl()) + testImplementation(gradleApi()) + testImplementation(gradleKotlinDsl()) + testImplementation(kotlin("test-junit")) + testImplementation("org.jetbrains.kotlin:kotlin-gradle-plugin") + + constraints { + val kotlin_version: String by project + compileOnly("org.jetbrains.kotlin:kotlin-reflect:${kotlin_version}") { + because("kotlin-gradle-plugin and :core both depend on this") + } + } +} + +val sourceJar by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + from(sourceSets["main"].allSource) +} + +gradlePlugin { + plugins { + create("dokkaGradlePlugin") { + id = "org.jetbrains.dokka" + implementationClass = "org.jetbrains.dokka.gradle.DokkaPlugin" + version = dokkaVersion + } + } +} + +publishing { + publications { + register<MavenPublication>("pluginMaven") { + artifactId = "dokka-gradle-plugin" + } + + register<MavenPublication>("dokkaGradlePluginForIntegrationTests") { + artifactId = "dokka-gradle-plugin" + from(components["java"]) + version = "for-integration-tests-SNAPSHOT" + } + } +} + + +configureBintrayPublication("dokkaGradlePluginPluginMarkerMaven", "pluginMaven") 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 new file mode 100644 index 00000000..846f021c --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt @@ -0,0 +1,45 @@ +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.jetbrains.dokka.DokkaBootstrap +import org.jetbrains.dokka.plugability.Configurable + + +abstract class AbstractDokkaTask : DefaultTask(), Configurable { + @Input + var outputDirectory: String = defaultDokkaOutputDirectory().absolutePath + + @Input + override val pluginsConfiguration: Map<String, String> = mutableMapOf() + + @Classpath + val plugins: Configuration = project.maybeCreateDokkaPluginConfiguration(name) + + @Classpath + 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 { + generate() + } finally { + System.setProperty(DokkaTask.COLORS_ENABLED_PROPERTY, kotlinColorsEnabledBefore) + } + } + + protected abstract fun generate() + + protected fun DokkaBootstrap(bootstrapClassFQName: String): DokkaBootstrap { + return DokkaBootstrap(runtime, bootstrapClassFQName) + } +} 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 index 39672b9a..1bfd2c78 100644 --- 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 @@ -1,9 +1,5 @@ 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 @@ -13,90 +9,71 @@ 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.KotlinProjectExtension 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.plugin.KotlinSourceSet 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 extractConfiguration(targetName: String, variantNames: List<String>) = - if (project.isMultiplatformProject()) { - extractFromMultiPlatform(targetName, variantNames) - } else { - extractFromSinglePlatform(variantNames) + fun extractConfiguration(sourceSetName: String): PlatformData? { + val projectExtension = project.extensions.findByType(KotlinProjectExtension::class.java) ?: run { + project.logger.error("Missing kotlin project extension") + return null } - private fun extractFromSinglePlatform(variantNames: List<String>): 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 - } + val sourceSet = projectExtension.sourceSets.findByName(sourceSetName) ?: run { + project.logger.error("No source set with name '$sourceSetName' found") + return null } - return try { - PlatformData( - null, - accumulateClassPaths(variantNames, target), - accumulateSourceSets(variantNames, target), - getPlatformName(target.platformType) - ) - } catch (e: NoSuchMethodError) { + 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 extractFromMultiPlatform(targetName: String, variantNames: List<String>): PlatformData? = - try { - project.extensions.getByType(KotlinMultiplatformExtension::class.java).targets - } catch (e: Throwable) { - when (e) { - is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException -> - null - else -> throw e - } - }?.let { - val fixedName = if (targetName.toLowerCase() == "common") "metadata" else targetName.toLowerCase() - it.find { target -> target.name.toLowerCase() == fixedName }?.let { target -> - PlatformData( - fixedName, - accumulateClassPaths(variantNames, target), - accumulateSourceSets(variantNames, target), - target.platformType.toString() - ) - } - } + private fun KotlinSourceSet.allParentSourceFiles(): List<File> = + 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(), "") } + ?.let { PlatformData(null, emptyList(), it.toList(), emptyList(), "") } - fun extractFromKotlinTasks(kotlinTasks: List<Task>): PlatformData? = + fun extractFromKotlinTasks(kotlinTasks: List<Task>): List<PlatformData> = try { - kotlinTasks.map { extractFromKotlinTask(it) }.let { platformDataList -> - PlatformData( - null, - platformDataList.flatMap { it.classpath }, - platformDataList.flatMap { it.sourceRoots }, - "" - ) - } + kotlinTasks.map { extractFromKotlinTask(it) } } catch (e: Throwable) { when (e) { is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException -> - extractFromKotlinTasksTheHardWay(kotlinTasks) + listOfNotNull(extractFromKotlinTasksTheHardWay(kotlinTasks)) else -> throw e } } @@ -110,10 +87,18 @@ class ConfigurationExtractor(private val project: Project) { when (e) { is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException -> project.extensions.getByType(KotlinMultiplatformExtension::class.java).targets - .firstNotNullResult { target -> target.compilations.find { it.compileKotlinTask == task } } + .flatMap { it.compilations }.firstOrNull { it.compileKotlinTask == task } else -> throw e } - }.let { PlatformData(task.name, getClasspath(it), getSourceSet(it), it?.platformType?.toString() ?: "") } + }.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<Task>): PlatformData? { val allClasspath = mutableSetOf<File>() @@ -134,8 +119,8 @@ class ConfigurationExtractor(private val project: Project) { val taskClasspath: Iterable<File> = (it["getClasspath", AbstractCompile::class].takeIfIsFunc()?.invoke() - ?: it["compileClasspath", abstractKotlinCompileClz].takeIfIsProp()?.v() - ?: it["getClasspath", abstractKotlinCompileClz]()) + ?: it["compileClasspath", abstractKotlinCompileClz].takeIfIsProp()?.v() + ?: it["getClasspath", abstractKotlinCompileClz]()) if (taskClasspath is FileCollection) { allClasspathFileCollection += taskClasspath @@ -152,97 +137,42 @@ class ConfigurationExtractor(private val project: Project) { } classpath.addAll(project.files(allClasspath).toList()) - return PlatformData(null, classpath, allSourceRoots.toList(), "") + return PlatformData(null, classpath, allSourceRoots.toList(), emptyList(), "") } - private fun getSourceSet(target: KotlinTarget, variantName: String): List<File> = - if (target.isAndroidTarget()) - getSourceSet(getCompilation(target, variantName)) - else - getSourceSet(getMainCompilation(target)) - - private fun getClasspath(target: KotlinTarget, variantName: String): List<File> = - if (target.isAndroidTarget()) - getClasspathFromAndroidTask(getCompilation(target, variantName)) - 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>() - } + private val KotlinCompilation<*>.sourceFiles: List<File> + get() = kotlinSourceSets.flatMap { it.sourceFiles } - return baseVariants + testVariants - } + private val KotlinSourceSet.sourceFiles: List<File> + get() = kotlin.sourceDirectories.filter { it.exists() }.toList() - private fun getPlatformName(platform: KotlinPlatformType): String = - if (platform == KotlinPlatformType.androidJvm) KotlinPlatformType.jvm.toString() else platform.toString() + private val KotlinCompilation<*>.dependentSourceSets: Set<KotlinSourceSet> + get() = (allKotlinSourceSets - kotlinSourceSets) - private fun accumulateClassPaths(variantNames: List<String>, target: KotlinTarget) = - if (variantNames.isNotEmpty()) { - variantNames.flatMap { getClasspath(target, it) }.distinct() + private val KotlinCompilation<*>.classpath: List<File> + get() = if (target.isAndroidTarget()) { + getClasspathFromAndroidTask(this) } else { - if (target.isAndroidTarget()) - getClasspathFromAndroidTask(getMainCompilation(target)) - else - getClasspath(getMainCompilation(target)) + getClasspathFromRegularTask(this) } - private fun accumulateSourceSets(variantNames: List<String>, target: KotlinTarget) = - if (variantNames.isNotEmpty()) - variantNames.flatMap { getSourceSet(target, it) }.distinct() - else - getSourceSet(getMainCompilation(target)) + // This is a workaround for KT-33893 + private fun getClasspathFromAndroidTask(compilation: KotlinCompilation<*>): List<File> = (compilation + .compileKotlinTask as? KotlinCompile) + ?.classpath?.files?.toList() ?: getClasspathFromRegularTask(compilation) + + private fun getClasspathFromRegularTask(compilation: KotlinCompilation<*>): List<File> = + compilation + .compileDependencyFiles + .files + .toList() + .filter { it.exists() } data class PlatformData( val name: String?, val classpath: List<File>, val sourceRoots: List<File>, + val dependentSourceSets: List<String>, val platform: String ) : Serializable -}
\ No newline at end of file +} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt new file mode 100644 index 00000000..90d51015 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaArtifacts.kt @@ -0,0 +1,17 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.Project +import org.jetbrains.dokka.DokkaVersion + +internal val Project.dokkaArtifacts get() = DokkaArtifacts(this) + +internal class DokkaArtifacts(private val project: Project) { + private fun fromModuleName(name: String) = + project.dependencies.create("org.jetbrains.dokka:$name:${DokkaVersion.version}") + + val dokkaCore get() = fromModuleName("dokka-core") + val dokkaBase get() = fromModuleName("dokka-base") + val javadocPlugin get() = fromModuleName("javadoc-plugin") + val gfmPlugin get() = fromModuleName("gfm-plugin") + val jekyllPlugin get() = fromModuleName("jekyll-plugin") +} 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 new file mode 100644 index 00000000..df29c19b --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaBootstrapFactory.kt @@ -0,0 +1,18 @@ +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 new file mode 100644 index 00000000..37952ea8 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTask.kt @@ -0,0 +1,58 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.api.plugins.JavaBasePlugin.DOCUMENTATION_GROUP +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction +import java.lang.IllegalStateException + +open class DokkaCollectorTask : DefaultTask() { + + @Input + var modules: List<String> = emptyList() + + @Input + var outputDirectory: String = defaultDokkaOutputDirectory().absolutePath + + private lateinit var configuration: GradleDokkaConfigurationImpl + + @Input + var dokkaTaskNames: Set<String> = setOf() + + @TaskAction + fun collect() { + val configurations = project.subprojects + .filter { subProject -> subProject.name in modules } + .flatMap { subProject -> dokkaTaskNames.mapNotNull(subProject.tasks::findByName) } + .filterIsInstance<DokkaTask>() + .mapNotNull { dokkaTask -> dokkaTask.getConfigurationOrNull() } + + + val initial = GradleDokkaConfigurationImpl().apply { + outputDir = outputDirectory + cacheRoot = configurations.first().cacheRoot + } + + // TODO this certainly not the ideal solution + 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 + } + project.tasks.withType(DokkaTask::class.java).configureEach { it.enforcedConfiguration = configuration } + } + + init { + // TODO: This this certainly not the ideal solution + dokkaTaskNames.forEach { dokkaTaskName -> + finalizedBy(dokkaTaskName) + } + + 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 new file mode 100644 index 00000000..6fd58afe --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt @@ -0,0 +1,68 @@ +package org.jetbrains.dokka.gradle + +import com.google.gson.GsonBuilder +import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.api.plugins.JavaBasePlugin.DOCUMENTATION_GROUP +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.jetbrains.dokka.plugability.Configurable + +open class DokkaMultimoduleTask : AbstractDokkaTask(), Configurable { + + @Input + var documentationFileName: String = "README.md" + + + @Input + var dokkaTaskNames: Set<String> = setOf() + set(value) { + field = value.toSet() + setDependsOn(getSubprojectDokkaTasks(value)) + } + + + override fun generate() { + val bootstrap = DokkaBootstrap("org.jetbrains.dokka.DokkaMultimoduleBootstrapImpl") + val gson = GsonBuilder().setPrettyPrinting().create() + val configuration = getConfiguration() + bootstrap.configure(gson.toJson(configuration)) { 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 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 + } + } + } + + private fun getSubprojectDokkaTasks(dokkaTaskName: String): List<DokkaTask> { + return project.subprojects + .mapNotNull { subproject -> subproject.tasks.findByName(dokkaTaskName) as? DokkaTask } + } + + private fun getSubprojectDokkaTasks(dokkaTaskNames: Set<String>): List<DokkaTask> { + 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 new file mode 100644 index 00000000..3fadb4fd --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaSourceSetIDFactory.kt @@ -0,0 +1,10 @@ +@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 bafe657e..0d7e74a3 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,28 +1,26 @@ package org.jetbrains.dokka.gradle import com.google.gson.GsonBuilder -import org.gradle.api.* -import org.gradle.api.artifacts.Configuration +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.DokkaBootstrap 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.kotlin.gradle.plugin.KotlinPlatformType import java.io.File -import java.net.URLClassLoader import java.util.concurrent.Callable -import java.util.function.BiConsumer -open class DokkaTask : DefaultTask() { +open class DokkaTask : AbstractDokkaTask() { 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 + private val configExtractor = ConfigurationExtractor(project) @Suppress("MemberVisibilityCanBePrivate") fun defaultKotlinTasks(): List<Task> = with(ReflectDsl) { @@ -44,60 +42,50 @@ open class DokkaTask : DefaultTask() { dependsOn(Callable { kotlinTasks.map { it.taskDependencies } }) } - @Input - var outputFormat: String = "html" - - @Input - var outputDirectory: String = "" - - var dokkaRuntime: Configuration? = null - - @Input - var subProjects: List<String> = emptyList() - - @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) + /** + * Hack used by DokkaCollector to enforce a different configuration to be used. + */ + @get:Internal + internal var enforcedConfiguration: GradleDokkaConfigurationImpl? = null - // Configure Dokka with closure in Gradle Kotlin DSL - fun configuration(action: Action<in GradlePassConfigurationImpl>) = action.execute(configuration) + @get:Nested + val dokkaSourceSets: NamedDomainObjectContainer<GradleDokkaSourceSet> = + project.container(GradleDokkaSourceSet::class.java) { name -> GradleDokkaSourceSet(name, project) } + .also { container -> DslObject(this).extensions.add("dokkaSourceSets", container) } - private val kotlinTasks: List<Task> by lazy { extractKotlinCompileTasks(configuration.collectKotlinTasks ?: { defaultKotlinTasks() }) } - private val configExtractor = ConfigurationExtractor(project) + private val kotlinTasks: List<Task> by lazy { + extractKotlinCompileTasks( + dokkaSourceSets.mapNotNull { + it.collectKotlinTasks?.invoke() + }.takeIf { it.isNotEmpty() }?.flatten() ?: defaultKotlinTasks() + ) + } @Input var disableAutoconfiguration: Boolean = false - private var outputDiagnosticInfo: Boolean = false // Workaround for Gradle, which fires some methods (like collectConfigurations()) multiple times in its lifecycle + @Input + var failOnWarning: Boolean = false - private fun loadFatJar() { - if (ClassloaderContainer.fatJarClassLoader == null) { - val jars = dokkaRuntime!!.resolve().toList() - ClassloaderContainer.fatJarClassLoader = URLClassLoader(jars.map { it.toURI().toURL() }.toTypedArray(), ClassLoader.getSystemClassLoader().parent) - } - } + @Input + var offlineMode: Boolean = false - private fun extractKotlinCompileTasks(collectTasks: () -> List<Any?>?): List<Task> { - val inputList = (collectTasks.invoke() ?: emptyList()).filterNotNull() - val (paths, other) = inputList.partition { it is String } + private var outputDiagnosticInfo: Boolean = + false // Workaround for Gradle, which fires some methods (like collectConfigurations()) multiple times in its lifecycle - val taskContainer = project.tasks + protected fun extractKotlinCompileTasks(collectTasks: List<Any?>?): List<Task> { + val inputList = (collectTasks ?: emptyList()).filterNotNull() + val (paths, other) = inputList.partition { it is String } - val tasksByPath = paths.map { taskContainer.findByPath(it as String) ?: throw IllegalArgumentException("Task with path '$it' not found") } + 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) } @@ -111,11 +99,14 @@ open class DokkaTask : DefaultTask() { 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 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>) = - if(project.isAndroidProject()) { + if (project.isAndroidProject()) { val generatedRoot = project.buildDir.resolve("generated").absoluteFile sourceRoots .map { File(it.path) } @@ -126,153 +117,152 @@ open class DokkaTask : DefaultTask() { emptyList() } - @TaskAction - fun generate() { + override fun generate() = enforcedConfiguration?.let { generate(it) } ?: generate(getConfigurationOrThrow()) + + protected open fun generate(configuration: GradleDokkaConfigurationImpl) { 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(it, globalConfig) } - - val configuration = GradleDokkaConfigurationImpl() - configuration.outputDir = outputDirectory - configuration.format = outputFormat - configuration.generateIndexPages = true - configuration.cacheRoot = cacheRoot - configuration.impliedPlatforms = impliedPlatforms - configuration.passesConfigurations = passConfigurationList - - if(passConfigurationList.isEmpty() || passConfigurationList == listOf(globalConfig)) { - println("No pass configurations for generation detected!") - return + val bootstrap = DokkaBootstrap("org.jetbrains.dokka.DokkaBootstrapImpl") + + bootstrap.configure( + GsonBuilder().setPrettyPrinting().create().toJson(configuration) + ) { 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() + } - bootstrapProxy.configure( - BiConsumer { level, message -> - when (level) { - "info" -> logger.info(message) - "warn" -> logger.warn(message) - "error" -> logger.error(message) - } - }, - gson.toJson(configuration) - ) - bootstrapProxy.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() - } finally { - System.setProperty(COLORS_ENABLED_PROPERTY, kotlinColorsEnabledBefore) + if (defaultModulesConfiguration.isEmpty()) { + return null } - } - protected open fun collectConfigurations() = - if (this.isMultiplatformProject()) collectMultiplatform() else listOf(collectSinglePlatform(configuration)) + 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 + } + } - protected open fun collectMultiplatform() = multiplatform - .filterNot { it.name.toLowerCase() == GLOBAL_PLATFORM_NAME } - .map { collectSinglePlatform(it) } + @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. + + dokka { + dokkaSourceSets { + create("commonMain") { + displayName = "common" + platform = "common" + } + } + } + """ + ) + } - protected open fun collectSinglePlatform(config: GradlePassConfigurationImpl): GradlePassConfigurationImpl { - val userConfig = config.let { - if (it.collectKotlinTasks != null) { - configExtractor.extractFromKotlinTasks(extractKotlinCompileTasks(it.collectKotlinTasks!!)) - ?.let { platformData -> mergeUserConfigurationAndPlatformData(it, platformData) } ?: it - } else { - it + @get:Internal + protected val configuredDokkaSourceSets: List<GradleDokkaSourceSet> + 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 - val baseConfig = configExtractor.extractConfiguration(userConfig.name, userConfig.androidVariants) + return configExtractor.extractConfiguration(userConfig.name) ?.let { mergeUserConfigurationAndPlatformData(userConfig, it) } - ?: if (this.isMultiplatformProject()) { - if (outputDiagnosticInfo) - logger.warn("Could not find target with name: ${userConfig.name} in Kotlin Gradle Plugin, " + - "using only user provided configuration for this target") - userConfig - } else { - logger.warn("Could not find target with name: ${userConfig.name} in Kotlin Gradle Plugin") - collectFromSinglePlatformOldPlugin() - } - - return if (subProjects.isNotEmpty()) { - try { - subProjects.toProjects().fold(baseConfig) { config, subProject -> - mergeUserConfigurationAndPlatformData( - config, - ConfigurationExtractor(subProject).extractConfiguration(config.name, config.androidVariants)!! + ?: 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" ) - } - } 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 + userConfig + } else { + if (outputDiagnosticInfo) + logger.warn("Could not find source set with name: ${userConfig.name} in Kotlin Gradle Plugin") + collectFromSinglePlatformOldPlugin(userConfig.name, userConfig) } - } else { - baseConfig - } } - protected open fun collectFromSinglePlatformOldPlugin() = - configExtractor.extractFromKotlinTasks(kotlinTasks) - ?.let { mergeUserConfigurationAndPlatformData(configuration, it) } - ?: configExtractor.extractFromJavaPlugin() - ?.let { mergeUserConfigurationAndPlatformData(configuration, it) } - ?: configuration - - protected open fun mergeUserConfigurationAndPlatformData(userConfig: GradlePassConfigurationImpl, autoConfig: PlatformData) = - 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 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 } - protected open fun defaultPassConfiguration( - config: GradlePassConfigurationImpl, - globalConfig: GradlePassConfigurationImpl? - ): GradlePassConfigurationImpl { - if (config.moduleName == "") { - config.moduleName = project.name + if (config.displayName.isBlank()) { + config.displayName = config.name.substringBeforeLast("Main", config.platform.toString()) } - if (config.targets.isEmpty() && multiplatform.isNotEmpty()){ - config.targets = listOf(config.name) + + if (project.isAndroidProject() && !config.noAndroidSdkLink) { + config.externalDocumentationLinks.add(ANDROID_REFERENCE_URL) + } + + if (config.platform?.isNotBlank() == true) { + config.analysisPlatform = dokkaPlatformFromString(config.platform.toString()) } - config.classpath = (config.classpath as List<Any>).map { it.toString() }.distinct() // Workaround for Groovy's GStringImpl + + // Workaround for Groovy's GStringImpl + config.classpath = (config.classpath as List<Any>).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) - if (project.isAndroidProject() && !config.noAndroidSdkLink) { // TODO: introduce Android as a separate Dokka platform? - config.externalDocumentationLinks.add(ANDROID_REFERENCE_URL) - } - 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()) { - KotlinPlatformType.androidJvm.toString().toLowerCase(), "androidjvm", "android" -> Platform.jvm + "androidjvm", "android" -> Platform.jvm "metadata" -> Platform.common else -> Platform.fromString(platform) } @@ -283,16 +273,16 @@ open class DokkaTask : DefaultTask() { // 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)) }) + + 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((collectConfigurations().flatMap { it.classpath } as List<Any>).map { project.fileTree(File(it.toString())) }) + project.files((configuredDokkaSourceSets.flatMap { it.classpath } as List<Any>) + .map { project.fileTree(File(it.toString())) }) companion object { const val COLORS_ENABLED_PROPERTY = "kotlin.colors.enabled" diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt index f8965993..468f597f 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt @@ -1,9 +1,6 @@ package org.jetbrains.dokka.gradle -import java.lang.reflect.InvocationHandler -import java.lang.reflect.InvocationTargetException -import java.lang.reflect.Method -import java.lang.reflect.Proxy +import java.lang.reflect.* /** @@ -13,8 +10,8 @@ import java.lang.reflect.Proxy * to create access proxy for [delegate] into [targetClassLoader]. */ @Suppress("UNCHECKED_CAST") -inline fun <reified T : Any> automagicTypedProxy(targetClassLoader: ClassLoader, delegate: Any): T = - automagicProxy(targetClassLoader, T::class.java, delegate) as T +internal inline fun <reified T : Any> automagicTypedProxy(targetClassLoader: ClassLoader, delegate: Any): T = + automagicProxy(targetClassLoader, T::class.java, delegate) as T /** @@ -24,14 +21,14 @@ inline fun <reified T : Any> automagicTypedProxy(targetClassLoader: ClassLoader, * to create access proxy for [delegate] into [targetClassLoader]. * */ -fun automagicProxy(targetClassLoader: ClassLoader, targetType: Class<*>, delegate: Any): Any = - Proxy.newProxyInstance( - targetClassLoader, - arrayOf(targetType), - DelegatedInvocationHandler(delegate) - ) +internal fun automagicProxy(targetClassLoader: ClassLoader, targetType: Class<*>, delegate: Any): Any = + Proxy.newProxyInstance( + targetClassLoader, + arrayOf(targetType), + DelegatedInvocationHandler(delegate) + ) -class DelegatedInvocationHandler(private val delegate: Any) : InvocationHandler { +internal class DelegatedInvocationHandler(private val delegate: Any) : InvocationHandler { @Throws(Throwable::class) override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? { diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ReflectDsl.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ReflectDsl.kt new file mode 100644 index 00000000..4b511022 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ReflectDsl.kt @@ -0,0 +1,72 @@ +package org.jetbrains.dokka + +import kotlin.reflect.* +import kotlin.reflect.full.memberFunctions +import kotlin.reflect.full.memberProperties +import kotlin.reflect.jvm.isAccessible + +internal object ReflectDsl { + + class CallOrPropAccess(private val receiver: Any?, + private val clz: KClass<*>, + private val selector: String) { + + @Suppress("UNCHECKED_CAST") + operator fun <T : Any?> invoke(vararg a: Any?): T { + return func!!.call(receiver, *a) as T + } + + operator fun get(s: String): CallOrPropAccess { + return v<Any?>()!![s] + } + + val func: KFunction<*>? by lazy { clz.memberFunctions.find { it.name == selector } } + val prop: KProperty<*>? by lazy { clz.memberProperties.find { it.name == selector } } + + fun takeIfIsFunc(): CallOrPropAccess? = if (func != null) this else null + + fun takeIfIsProp(): CallOrPropAccess? = if (prop != null) this else null + + @Suppress("UNCHECKED_CAST") + fun <T : Any?> v(): T { + val prop = prop!! + return try { + prop.getter.apply { isAccessible = true }.call(receiver) as T + } catch (e: KotlinNullPointerException) { + // Hack around kotlin-reflect bug KT-18480 + val jclass = clz.java + val customGetterName = prop.getter.name + val getterName = if (customGetterName.startsWith("<")) "get" + prop.name.capitalize() else customGetterName + val getter = jclass.getDeclaredMethod(getterName) + getter.isAccessible = true + + getter.invoke(receiver) as T + + } + } + + @Suppress("UNCHECKED_CAST") + fun v(x: Any?) { + (prop as KMutableProperty).setter.apply { isAccessible = true }.call(receiver, x) + } + + + } + + operator fun Any.get(s: String): CallOrPropAccess { + val clz = this.javaClass.kotlin + return CallOrPropAccess(this, clz, s) + } + + operator fun Any.get(s: String, clz: Class<*>): CallOrPropAccess { + val kclz = clz.kotlin + return CallOrPropAccess(this, kclz, s) + } + + operator fun Any.get(s: String, clz: KClass<*>): CallOrPropAccess { + return CallOrPropAccess(this, clz, s) + } + + inline infix fun Any.isInstance(clz: Class<*>?): Boolean = clz != null && clz.isAssignableFrom(this.javaClass) + inline infix fun Any.isNotInstance(clz: Class<*>?): Boolean = !(this isInstance clz) +}
\ No newline at end of file diff --git a/runners/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 65afad04..b6b8399c 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 @@ -1,12 +1,19 @@ +@file:Suppress("FunctionName") + package org.jetbrains.dokka.gradle +import com.android.build.gradle.api.AndroidSourceSet import groovy.lang.Closure import org.gradle.api.Action +import org.gradle.api.Project import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal 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.DokkaDefaults +import org.jetbrains.dokka.DokkaSourceSetID import org.jetbrains.dokka.Platform import java.io.File import java.io.Serializable @@ -14,8 +21,10 @@ import java.net.URL import java.util.concurrent.Callable import kotlin.reflect.KMutableProperty import kotlin.reflect.full.memberProperties +import org.gradle.api.tasks.SourceSet as GradleSourceSet +import org.jetbrains.kotlin.gradle.model.SourceSet as KotlinSourceSet -class GradleSourceRootImpl: SourceRoot, Serializable { +class GradleSourceRootImpl : SourceRoot, Serializable { override var path: String = "" set(value) { field = File(value).absolutePath @@ -24,34 +33,113 @@ class GradleSourceRootImpl: SourceRoot, Serializable { 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 = true - @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 @Transient var androidVariants: List<String> = emptyList() +open class GradleDokkaSourceSet constructor( + @Transient @get:Input val name: String, + @Transient @get:Internal internal val project: Project +) : DokkaSourceSet { + + @Input + @Optional + override var classpath: List<String> = emptyList() + + @Input + override var moduleDisplayName: String = "" + + @Input + override var displayName: String = "" + + @get:Internal + override val sourceSetID: DokkaSourceSetID = DokkaSourceSetID(project, name) + + @Input + override var sourceRoots: MutableList<SourceRoot> = mutableListOf() + + @Input + override var dependentSourceSets: MutableSet<DokkaSourceSetID> = mutableSetOf() + + @Input + override var samples: List<String> = emptyList() + + @Input + override var includes: List<String> = emptyList() + + @Input + override var includeNonPublic: Boolean = DokkaDefaults.includeNonPublic + + @Input + override var includeRootPackage: Boolean = DokkaDefaults.includeRootPackage + + @Input + override var reportUndocumented: Boolean = DokkaDefaults.reportUndocumented + + @Input + override var skipEmptyPackages: Boolean = DokkaDefaults.skipEmptyPackages + + @Input + override var skipDeprecated: Boolean = DokkaDefaults.skipDeprecated + + @Input + override var jdkVersion: Int = DokkaDefaults.jdkVersion + + @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 = DokkaDefaults.noStdlibLink + + @Input + override var noJdkLink: Boolean = DokkaDefaults.noJdkLink + + @Input + var noAndroidSdkLink: Boolean = false + + @Input + override var suppressedFiles: List<String> = emptyList() + + @Input + override var analysisPlatform: Platform = DokkaDefaults.analysisPlatform + + @Input + @Optional + var platform: String? = null + + @Internal + @Transient + var collectKotlinTasks: (() -> List<Any?>?)? = null + + fun DokkaSourceSetID(sourceSetName: String): DokkaSourceSetID { + return DokkaSourceSetID(project, sourceSetName) + } + + fun dependsOn(sourceSet: GradleSourceSet) { + dependsOn(DokkaSourceSetID(sourceSet.name)) + } + + fun dependsOn(sourceSet: DokkaSourceSet) { + dependsOn(sourceSet.sourceSetID) + } + + fun dependsOn(sourceSetName: String) { + dependsOn(DokkaSourceSetID(sourceSetName)) + } + + fun dependsOn(sourceSetID: DokkaSourceSetID) { + dependentSourceSets.add(sourceSetID) + } fun kotlinTasks(taskSupplier: Callable<List<Any>>) { collectKotlinTasks = { taskSupplier.call() } @@ -95,70 +183,76 @@ open class GradlePassConfigurationImpl(@Transient val name: String = ""): PassCo } fun externalDocumentationLink(c: Closure<Unit>) { - val builder = ConfigureUtil.configure(c, GradleExternalDocumentationLinkImpl.Builder()) - externalDocumentationLinks.add(builder.build()) + val link = ConfigureUtil.configure(c, GradleExternalDocumentationLinkImpl()) + externalDocumentationLinks.add(ExternalDocumentationLink.Builder(link.url, link.packageListUrl).build()) } - fun externalDocumentationLink(action: Action<in GradleExternalDocumentationLinkImpl.Builder>) { - val builder = GradleExternalDocumentationLinkImpl.Builder() - action.execute(builder) - externalDocumentationLinks.add(builder.build()) + fun externalDocumentationLink(action: Action<in GradleExternalDocumentationLinkImpl>) { + val link = GradleExternalDocumentationLinkImpl() + action.execute(link) + externalDocumentationLinks.add(ExternalDocumentationLink.Builder(link.url, link.packageListUrl).build()) } } +fun GradleDokkaSourceSet.dependsOn(sourceSet: KotlinSourceSet) { + dependsOn(DokkaSourceSetID(sourceSet.name)) +} + +fun GradleDokkaSourceSet.dependsOn(sourceSet: org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet) { + dependsOn(DokkaSourceSetID(sourceSet.name)) +} + +fun GradleDokkaSourceSet.dependsOn(sourceSet: AndroidSourceSet) { + dependsOn(DokkaSourceSetID(sourceSet.name)) +} + class GradleSourceLinkDefinitionImpl : SourceLinkDefinition, Serializable { override var path: String = "" override var url: String = "" override var lineSuffix: String? = null } -class GradleExternalDocumentationLinkImpl( - override val url: URL, - override val packageListUrl: URL -): ExternalDocumentationLink, Serializable { - open class Builder(open var url: URL? = null, - open var packageListUrl: URL? = null) { - - constructor(root: String, packageList: String? = null) : this(URL(root), packageList?.let { URL(it) }) +class GradleExternalDocumentationLinkImpl : ExternalDocumentationLink, Serializable { + override var url: URL = URL("http://") + override var packageListUrl: URL = URL("http://") +} - fun build(): ExternalDocumentationLink = - if (packageListUrl != null && url != null) - GradleExternalDocumentationLinkImpl(url!!, packageListUrl!!) - else if (url != null) - GradleExternalDocumentationLinkImpl(url!!, URL(url!!, "package-list")) - else - throw IllegalArgumentException("url or url && packageListUrl must not be null for external documentation link") - } +class GradleDokkaModuleDescription : DokkaModuleDescription { + override var name: String = "" + override var path: String = "" + override var docFile: String = "" } -class GradleDokkaConfigurationImpl: DokkaConfiguration { +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() + override var cacheRoot: String? = DokkaDefaults.cacheRoot + override var offlineMode: Boolean = DokkaDefaults.offlineMode + override var failOnWarning: Boolean = DokkaDefaults.failOnWarning + override var sourceSets: List<GradleDokkaSourceSet> = emptyList() + override var pluginsClasspath: List<File> = emptyList() + override var pluginsConfiguration: Map<String, String> = mutableMapOf() + override var modules: List<GradleDokkaModuleDescription> = emptyList() } -class GradlePackageOptionsImpl: PackageOptions, Serializable { +class GradlePackageOptionsImpl : PackageOptions, Serializable { override var prefix: String = "" - override var includeNonPublic: Boolean = false - override var reportUndocumented: Boolean = false - override var skipDeprecated: Boolean = false - override var suppress: Boolean = false + override var includeNonPublic: Boolean = DokkaDefaults.includeNonPublic + override var reportUndocumented: Boolean = DokkaDefaults.reportUndocumented + override var skipDeprecated: Boolean = DokkaDefaults.skipDeprecated + override var suppress: Boolean = DokkaDefaults.suppress } -fun GradlePassConfigurationImpl.copy(): GradlePassConfigurationImpl { - val newObj = GradlePassConfigurationImpl(this.name) +internal fun GradleDokkaSourceSet.copy(): GradleDokkaSourceSet { + val newObj = GradleDokkaSourceSet(this.name, this.project) 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)) + when (val value = field.getter.call(this)) { + is List<*> -> field.setter.call(newObj, value.toMutableList()) + is Set<*> -> field.setter.call(newObj, value.toMutableSet()) + 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/defaultDokkaOutputDirectory.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/defaultDokkaOutputDirectory.kt new file mode 100644 index 00000000..0a7ab534 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/defaultDokkaOutputDirectory.kt @@ -0,0 +1,13 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.Task +import java.io.File + +internal fun Task.defaultDokkaOutputDirectory(): File { + return defaultDokkaOutputDirectory(project.buildDir, name) +} + +internal fun defaultDokkaOutputDirectory(buildDir: File, taskName: String): File { + val formatClassifier = taskName.removePrefix("dokka").decapitalize() + return File(buildDir, "dokka${File.separator}$formatClassifier") +} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaConfigurations.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaConfigurations.kt new file mode 100644 index 00000000..20f54cc5 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokkaConfigurations.kt @@ -0,0 +1,41 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.attributes.Usage + +internal fun Project.maybeCreateDokkaDefaultPluginConfiguration(): Configuration { + return configurations.maybeCreate("dokkaPlugin") { + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, "java-runtime")) + isCanBeConsumed = false + } +} + +internal fun Project.maybeCreateDokkaDefaultRuntimeConfiguration(): Configuration { + return configurations.maybeCreate("dokkaRuntime") { + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, "java-runtime")) + isCanBeConsumed = false + } +} + +internal fun Project.maybeCreateDokkaPluginConfiguration(dokkaTaskName: String): Configuration { + return project.configurations.maybeCreate("${dokkaTaskName}Plugin") { + extendsFrom(maybeCreateDokkaDefaultPluginConfiguration()) + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, "java-runtime")) + isCanBeConsumed = false + defaultDependencies { dependencies -> + dependencies.add(project.dokkaArtifacts.dokkaBase) + } + } +} + +internal fun Project.maybeCreateDokkaRuntimeConfiguration(dokkaTaskName: String): Configuration { + return project.configurations.maybeCreate("${dokkaTaskName}Runtime") { + extendsFrom(maybeCreateDokkaDefaultRuntimeConfiguration()) + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, "java-runtime")) + isCanBeConsumed = false + defaultDependencies { dependencies -> + dependencies.add(project.dokkaArtifacts.dokkaCore) + } + } +} 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 7ed29c58..d70448f1 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,57 +2,49 @@ 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" +import org.gradle.kotlin.dsl.register 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")) + project.setupDokkaTasks("dokkaHtml") - private fun addConfiguration(project: Project) = - project.configurations.create("dokkaRuntime").apply { - defaultDependencies{ dependencies -> dependencies.add(project.dependencies.create("org.jetbrains.dokka:dokka-fatjar:${DokkaVersion.version}")) } + project.setupDokkaTasks("dokkaJavadoc") { + plugins.dependencies.add(project.dokkaArtifacts.javadocPlugin) } - protected open 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.setupDokkaTasks("dokkaGfm") { + plugins.dependencies.add(project.dokkaArtifacts.gfmPlugin) } - 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 + + project.setupDokkaTasks("dokkaJekyll") { + plugins.dependencies.add(project.dokkaArtifacts.jekyllPlugin) } } -} -object DokkaVersion { - var version: String? = null + /** + * Creates [DokkaTask], [DokkaMultimoduleTask] and [DokkaCollectorTask] for the given + * name and configuration. + */ + private fun Project.setupDokkaTasks(name: String, configuration: AbstractDokkaTask.() -> Unit = {}) { + project.maybeCreateDokkaPluginConfiguration(name) + project.maybeCreateDokkaRuntimeConfiguration(name) + project.tasks.register<DokkaTask>(name) { + configuration() + } - fun loadFrom(stream: InputStream) { - version = Properties().apply { - load(stream) - }.getProperty("dokka-version") + if (project.subprojects.isNotEmpty()) { + val multimoduleName = "${name}Multimodule" + project.maybeCreateDokkaPluginConfiguration(multimoduleName) + project.maybeCreateDokkaRuntimeConfiguration(multimoduleName) + project.tasks.register<DokkaMultimoduleTask>(multimoduleName) { + dokkaTaskNames = dokkaTaskNames + name + configuration() + } + + project.tasks.register<DokkaCollectorTask>("${name}Collector") { + dokkaTaskNames = dokkaTaskNames + name + } + } } } - -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 index 31892e8e..b6c5cbd8 100644 --- 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 @@ -1,5 +1,6 @@ package org.jetbrains.dokka.gradle +import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.UnknownDomainObjectException import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension @@ -7,25 +8,30 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinTarget -fun Project.isAndroidProject() = try { +internal fun Project.isAndroidProject() = try { project.extensions.getByName("android") true -} catch(e: UnknownDomainObjectException) { +} catch (e: UnknownDomainObjectException) { false -} catch(e: ClassNotFoundException) { +} catch (e: ClassNotFoundException) { false } -fun Project.isMultiplatformProject() = try { +internal fun Project.isNotMultiplatformProject() = !isMultiplatformProject() + +internal fun Project.isMultiplatformProject() = try { project.extensions.getByType(KotlinMultiplatformExtension::class.java) true -} catch(e: UnknownDomainObjectException) { +} catch (e: UnknownDomainObjectException) { false -} catch (e: NoClassDefFoundError){ +} catch (e: NoClassDefFoundError) { false -} catch(e: ClassNotFoundException) { +} catch (e: ClassNotFoundException) { false } -fun KotlinTarget.isAndroidTarget() = this.platformType == KotlinPlatformType.androidJvm -fun DokkaTask.isMultiplatformProject() = this.multiplatform.isNotEmpty()
\ No newline at end of file +internal fun KotlinTarget.isAndroidTarget() = this.platformType == KotlinPlatformType.androidJvm + +internal fun <T : Any> NamedDomainObjectContainer<T>.maybeCreate(name: String, configuration: T.() -> Unit): T { + return findByName(name) ?: create(name, configuration) +} diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt new file mode 100644 index 00000000..e981d6fe --- /dev/null +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AutomagicProxyTest.kt @@ -0,0 +1,48 @@ +package org.jetbrains.dokka.gradle + +import org.jetbrains.dokka.DokkaBootstrap +import org.jetbrains.dokka.gradle.AutomagicProxyTest.TestInterface +import java.util.function.BiConsumer +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + + +class AutomagicProxyTest { + + private class TestException(message: String, cause: Throwable?) : Exception(message, cause) + + private fun interface TestInterface { + @Throws(Throwable::class) + operator fun invoke(): Int + } + + @Test + fun `simple method invocation`() { + val instance = TestInterface { 0 } + val proxy = automagicTypedProxy<TestInterface>(instance.javaClass.classLoader, instance) + assertEquals(0, proxy()) + } + + @Test + fun `exception throw in DokkaBootstrap is not wrapped inside UndeclaredThrowableException`() { + val instanceThrowingTestException = object : DokkaBootstrap { + override fun configure(serializedConfigurationJSON: String, logger: BiConsumer<String, String>) = Unit + override fun generate() { + throw TestException("Test Exception Message", Exception("Cause Exception Message")) + } + } + + val proxy = automagicTypedProxy<DokkaBootstrap>( + instanceThrowingTestException.javaClass.classLoader, + instanceThrowingTestException + ) + + val exception = assertFailsWith<TestException> { + proxy.generate() + } + + assertEquals("Test Exception Message", exception.message) + assertEquals("Cause Exception Message", exception.cause?.message) + } +} diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaTasksTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaTasksTest.kt new file mode 100644 index 00000000..b948c540 --- /dev/null +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaTasksTest.kt @@ -0,0 +1,65 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.kotlin.dsl.withType +import org.gradle.testfixtures.ProjectBuilder +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertSame +import kotlin.test.assertTrue + +class DokkaTasksTest { + + @Test + fun `one task per format is registered`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("org.jetbrains.dokka") + + assertTrue( + project.tasks.findByName("dokkaHtml") is DokkaTask, + "Expected DokkaTask: dokkaHtml" + ) + + assertTrue( + project.tasks.findByName("dokkaGfm") is DokkaTask, + "Expected DokkaTask: dokkaGfm" + ) + + assertTrue( + project.tasks.findByName("dokkaJekyll") is DokkaTask, + "Expected DokkaTask: dokkaJekyll" + ) + + assertTrue( + project.tasks.findByName("dokkaJavadoc") is DokkaTask, + "Expected DokkaTask: dokkaJavadoc" + ) + } + + @Test + fun `dokka plugin configurations extend dokkaPlugin`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("org.jetbrains.dokka") + + val dokkaPluginsConfiguration = project.maybeCreateDokkaDefaultPluginConfiguration() + + project.tasks.withType<DokkaTask>().forEach { dokkaTask -> + assertSame( + dokkaTask.plugins.extendsFrom.single(), dokkaPluginsConfiguration, + "Expected dokka plugins configuration to extend default ${dokkaPluginsConfiguration.name} configuration" + ) + } + } + + @Test + fun `all dokka tasks are part of the documentation group`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("org.jetbrains.dokka") + project.tasks.filter { "dokka" in it.name.toLowerCase() }.forEach { dokkaTask -> + assertEquals( + JavaBasePlugin.DOCUMENTATION_GROUP, dokkaTask.group, + "Expected task: ${dokkaTask.path} group to be ${JavaBasePlugin.DOCUMENTATION_GROUP}" + ) + } + } +} diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt new file mode 100644 index 00000000..7b78fb55 --- /dev/null +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/KotlinDslDokkaTaskConfigurationTest.kt @@ -0,0 +1,97 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.testfixtures.ProjectBuilder +import org.jetbrains.dokka.DokkaSourceSetID +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension +import kotlin.test.Test +import kotlin.test.assertEquals + +class KotlinDslDokkaTaskConfigurationTest { + + @Test + fun `configure project using dokka extension function`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("org.jetbrains.dokka") + project.tasks.withType(DokkaTask::class.java).forEach { dokkaTask -> + dokkaTask.outputDirectory = "test" + } + + project.tasks.withType(DokkaTask::class.java).forEach { dokkaTask -> + assertEquals("test", dokkaTask.outputDirectory) + } + } + + @Test + fun `sourceSet dependsOn by String`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("org.jetbrains.dokka") + + project.tasks.withType(DokkaTask::class.java).forEach { dokkaTask -> + dokkaTask.dokkaSourceSets.run { + val commonMain = create("commonMain") + val jvmMain = create("jvmMain") { + it.dependsOn("commonMain") + } + + assertEquals( + 0, commonMain.dependentSourceSets.size, + "Expected no dependent source set in commonMain" + ) + + assertEquals( + 1, jvmMain.dependentSourceSets.size, + "Expected only one dependent source set in jvmMain" + ) + + assertEquals( + commonMain.sourceSetID, jvmMain.dependentSourceSets.single(), + "Expected jvmMain to depend on commonMain" + ) + + assertEquals( + DokkaSourceSetID(project.path, "commonMain"), commonMain.sourceSetID + ) + } + } + } + + @Test + fun `sourceSet dependsOn by DokkaSourceSet`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("org.jetbrains.dokka") + + project.tasks.withType(DokkaTask::class.java).first().run { + dokkaSourceSets.run { + val commonMain = create("commonMain") + val jvmMain = create("jvmMain") { + it.dependsOn(commonMain) + } + + assertEquals( + commonMain.sourceSetID, jvmMain.dependentSourceSets.single() + ) + } + } + } + + @Test + fun `sourceSet dependsOn by KotlinSourceSet`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("org.jetbrains.dokka") + project.plugins.apply("org.jetbrains.kotlin.jvm") + + val kotlin = project.extensions.getByName("kotlin") as KotlinJvmProjectExtension + + project.tasks.withType(DokkaTask::class.java).first().run { + dokkaSourceSets.run { + val special = create("special") { + it.dependsOn(kotlin.sourceSets.getByName("main")) + } + + assertEquals( + DokkaSourceSetID(project, "main"), special.dependentSourceSets.single() + ) + } + } + } +} |