From e3c1b2bdbfe20e602fbf570df946edc94266e5ff Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Wed, 5 Aug 2020 22:56:16 +0200 Subject: API refinement for AbstractDokkaParentTask and DokkaMultiModuleTask --- core/src/main/kotlin/defaultConfiguration.kt | 2 +- .../gradle/projects/it-basic/build.gradle.kts | 2 - .../dokka/it/gradle/MultiModule0IntegrationTest.kt | 74 ++++++ .../dokka/it/gradle/Multimodule0IntegrationTest.kt | 75 ------ .../intellij-dependency/build.gradle.kts | 4 - .../dokka/gradle/AbstractDokkaParentTask.kt | 103 +++++---- .../jetbrains/dokka/gradle/AbstractDokkaTask.kt | 35 ++- .../jetbrains/dokka/gradle/DokkaCollectorTask.kt | 15 +- .../dokka/gradle/DokkaMultiModuleFileLayout.kt | 52 +++++ .../jetbrains/dokka/gradle/DokkaMultiModuleTask.kt | 69 ++++++ .../jetbrains/dokka/gradle/DokkaMultimoduleTask.kt | 42 ---- .../kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt | 10 +- .../gradle/TaskDependencyInternalWithAdditions.kt | 2 +- .../dokka/gradle/checkChildDokkaTasksIsNotEmpty.kt | 43 ++++ .../kotlin/org/jetbrains/dokka/gradle/dokka.kt | 7 - .../main/kotlin/org/jetbrains/dokka/gradle/main.kt | 27 ++- .../jetbrains/dokka/gradle/toDokkaSourceSetImpl.kt | 1 - .../kotlin/org/jetbrains/dokka/gradle/utils.kt | 10 + .../dokka/gradle/AbstractDokkaParentTaskTest.kt | 255 ++++++++++++++------- .../dokka/gradle/DokkaCollectorTaskTest.kt | 26 ++- .../dokka/gradle/DokkaConfigurationJsonTest.kt | 14 +- .../gradle/DokkaConfigurationSerializableTest.kt | 13 +- .../dokka/gradle/DokkaMultiModuleFileLayoutTest.kt | 100 ++++++++ .../dokka/gradle/DokkaMultiModuleTaskTest.kt | 88 ++++++- .../jetbrains/dokka/gradle/DokkaPluginApplyTest.kt | 84 +++++++ .../org/jetbrains/dokka/gradle/DokkaTasksTest.kt | 65 ------ .../gradle/KotlinDslDokkaTaskConfigurationTest.kt | 11 +- 27 files changed, 840 insertions(+), 389 deletions(-) create mode 100644 integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt delete mode 100644 integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Multimodule0IntegrationTest.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTask.kt delete mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt create mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/checkChildDokkaTasksIsNotEmpty.kt delete mode 100644 runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokka.kt create mode 100644 runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayoutTest.kt create mode 100644 runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaPluginApplyTest.kt delete mode 100644 runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaTasksTest.kt diff --git a/core/src/main/kotlin/defaultConfiguration.kt b/core/src/main/kotlin/defaultConfiguration.kt index 384fa27e..ec461ffd 100644 --- a/core/src/main/kotlin/defaultConfiguration.kt +++ b/core/src/main/kotlin/defaultConfiguration.kt @@ -32,7 +32,7 @@ data class DokkaSourceSetImpl( override val jdkVersion: Int = DokkaDefaults.jdkVersion, override val sourceLinks: Set = emptySet(), override val perPackageOptions: List = emptyList(), - override var externalDocumentationLinks: Set = emptySet(), + override val externalDocumentationLinks: Set = emptySet(), override val languageVersion: String? = null, override val apiVersion: String? = null, override val noStdlibLink: Boolean = DokkaDefaults.noStdlibLink, diff --git a/integration-tests/gradle/projects/it-basic/build.gradle.kts b/integration-tests/gradle/projects/it-basic/build.gradle.kts index 1840ba94..43d3a0f3 100644 --- a/integration-tests/gradle/projects/it-basic/build.gradle.kts +++ b/integration-tests/gradle/projects/it-basic/build.gradle.kts @@ -28,5 +28,3 @@ tasks.withType { } } } - -buildDir.resolve("") diff --git a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt new file mode 100644 index 00000000..0184d150 --- /dev/null +++ b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/MultiModule0IntegrationTest.kt @@ -0,0 +1,74 @@ +package org.jetbrains.dokka.it.gradle + +import org.gradle.testkit.runner.TaskOutcome +import org.junit.runners.Parameterized +import java.io.File +import kotlin.test.* + +class MultiModule0IntegrationTest(override val versions: BuildVersions) : AbstractGradleIntegrationTest() { + companion object { + @get:JvmStatic + @get:Parameterized.Parameters(name = "{0}") + val versions = BuildVersions.permutations( + gradleVersions = listOf("6.5.1", "6.1.1"), + kotlinVersions = listOf("1.4.0-rc") + ) + } + + @BeforeTest + fun prepareProjectFiles() { + val templateProjectDir = File("projects", "it-multimodule-0") + templateProjectDir.listFiles().orEmpty() + .filter { it.isFile } + .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) } + File(templateProjectDir, "moduleA").copyRecursively(File(projectDir, "moduleA")) + } + + @Test + fun execute() { + val result = createGradleRunner( + ":moduleA:dokkaHtmlMultiModule", + ":moduleA:dokkaGfmMultiModule", + ":moduleA:dokkaJekyllMultiModule", + "-i", "-s" + ).buildRelaxed() + + assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaHtmlMultiModule")).outcome) + assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaGfmMultiModule")).outcome) + assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaJekyllMultiModule")).outcome) + assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleB:dokkaHtml")).outcome) + assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleC:dokkaHtml")).outcome) + assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleB:dokkaGfm")).outcome) + assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleC:dokkaGfm")).outcome) + assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleB:dokkaJekyll")).outcome) + assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleC:dokkaJekyll")).outcome) + + + val outputDir = File(projectDir, "moduleA/build/dokka/htmlMultiModule") + assertTrue(outputDir.isDirectory, "Missing dokka output directory") + + assertTrue( + outputDir.allHtmlFiles().any(), + "Expected at least one html file being generated" + ) + + outputDir.allHtmlFiles().forEach { file -> + assertContainsNoErrorClass(file) + assertNoUnresolvedLinks(file) + assertNoHrefToMissingLocalFileOrDirectory(file) + } + + val modulesFile = File(outputDir, "-modules.html") + assertTrue(modulesFile.isFile, "Missing -modules.html file") + + val modulesFileText = modulesFile.readText() + assertTrue( + "moduleB" in modulesFileText, + "Expected moduleB being mentioned in -modules.html" + ) + assertTrue( + "moduleC" in modulesFileText, + "Expected moduleC being mentioned in -modules.html" + ) + } +} diff --git a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Multimodule0IntegrationTest.kt b/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Multimodule0IntegrationTest.kt deleted file mode 100644 index 70b5832d..00000000 --- a/integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Multimodule0IntegrationTest.kt +++ /dev/null @@ -1,75 +0,0 @@ -package org.jetbrains.dokka.it.gradle - -import org.gradle.testkit.runner.TaskOutcome -import org.junit.runners.Parameterized -import java.io.File -import kotlin.test.* - -class Multimodule0IntegrationTest(override val versions: BuildVersions) : AbstractGradleIntegrationTest() { - companion object { - @get:JvmStatic - @get:Parameterized.Parameters(name = "{0}") - val versions = BuildVersions.permutations( - gradleVersions = listOf("6.5.1", "6.1.1"), - kotlinVersions = listOf("1.4.0-rc") - ) - } - - @BeforeTest - fun prepareProjectFiles() { - val templateProjectDir = File("projects", "it-multimodule-0") - templateProjectDir.listFiles().orEmpty() - .filter { it.isFile } - .forEach { topLevelFile -> topLevelFile.copyTo(File(projectDir, topLevelFile.name)) } - File(templateProjectDir, "moduleA").copyRecursively(File(projectDir, "moduleA")) - } - - @Test - fun execute() { - val result = createGradleRunner( - ":moduleA:dokkaHtmlMultimodule", - ":moduleA:dokkaGfmMultimodule", - ":moduleA:dokkaJekyllMultimodule", - "-i", "-s" - ).buildRelaxed() - - assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaHtmlMultimodule")).outcome) - assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaGfmMultimodule")).outcome) - assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:dokkaJekyllMultimodule")).outcome) - assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleB:dokkaHtml")).outcome) - assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleC:dokkaHtml")).outcome) - assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleB:dokkaGfm")).outcome) - assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleC:dokkaGfm")).outcome) - assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleB:dokkaJekyll")).outcome) - assertEquals(TaskOutcome.SUCCESS, assertNotNull(result.task(":moduleA:moduleC:dokkaJekyll")).outcome) - - - val outputDir = File(projectDir, "moduleA/build/dokka/htmlMultimodule") - assertTrue(outputDir.isDirectory, "Missing dokka output directory") - - assertTrue( - outputDir.allHtmlFiles().any(), - "Expected at least one html file being generated" - ) - - outputDir.allHtmlFiles().forEach { file -> - assertContainsNoErrorClass(file) - assertNoUnresolvedLinks(file) - assertNoHrefToMissingLocalFileOrDirectory(file) - } - - val modulesFile = File(outputDir, "-modules.html") - assertTrue(modulesFile.isFile, "Missing -modules.html file") - - val modulesFileText = modulesFile.readText() - assertTrue( - "moduleB" in modulesFileText, - "Expected moduleB being mentioned in -modules.html" - ) - assertTrue( - "moduleC" in modulesFileText, - "Expected moduleC being mentioned in -modules.html" - ) - - } -} diff --git a/kotlin-analysis/intellij-dependency/build.gradle.kts b/kotlin-analysis/intellij-dependency/build.gradle.kts index 4ff68e4e..8e080374 100644 --- a/kotlin-analysis/intellij-dependency/build.gradle.kts +++ b/kotlin-analysis/intellij-dependency/build.gradle.kts @@ -30,10 +30,6 @@ dependencies { val idea_version: String by project intellijCore("com.jetbrains.intellij.idea:intellij-core:$idea_version") implementation(intellijCoreAnalysis()) - - val kotlin_version: String by project - api("org.jetbrains.kotlin:kotlin-compiler:$kotlin_version") - } tasks { 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 index bf8308bf..c782197e 100644 --- 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 @@ -1,73 +1,86 @@ package org.jetbrains.dokka.gradle import org.gradle.api.Project -import org.gradle.api.tasks.Input +import org.gradle.api.Task 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() + internal var childDokkaTaskPaths: Set = emptySet() + private set @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 + internal val childDokkaTasks: Set + get() = childDokkaTaskPaths + .mapNotNull { path -> project.tasks.findByPath(path) } + .map(::checkIsAbstractDokkaTask) + .toSet() + + /* By task reference */ + fun addChildTask(task: AbstractDokkaTask) { + childDokkaTaskPaths = childDokkaTaskPaths + task.path + } + + fun removeChildTask(task: AbstractDokkaTask) { + childDokkaTaskPaths = childDokkaTaskPaths - task.path + } + + /* By path */ + fun addChildTask(path: String) { + childDokkaTaskPaths = childDokkaTaskPaths + project.absoluteProjectPath(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) + fun removeChildTask(path: String) { + childDokkaTaskPaths = childDokkaTaskPaths - project.absoluteProjectPath(path) } - /** - * 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) + /* By project reference and name */ + fun addChildTasks(projects: Iterable, childTasksName: String) { + projects.forEach { project -> + addChildTask(project.absoluteProjectPath(childTasksName)) + } } - /** - * Includes the [project] and all its subprojects to participate in this parent task. - * @see addSubproject - */ - fun addAllProjects(project: Project) { - project.allprojects.forEach(::addSubproject) + fun removeChildTasks(projects: Iterable, childTasksName: String) { + projects.forEach { project -> + removeChildTask(project.absoluteProjectPath(childTasksName)) + } } - protected fun findSubprojectDokkaTasks(dokkaTaskNames: Set): List { - return dokkaTaskNames.flatMap { dokkaTaskName -> findSubprojectDokkaTasks(dokkaTaskName) } + fun addSubprojectChildTasks(childTasksName: String) { + addChildTasks(project.subprojects, childTasksName) } - private fun findSubprojectDokkaTasks(dokkaTaskName: String): List { - return subprojects.mapNotNull { subproject -> subproject.tasks.findByName(dokkaTaskName) as? DokkaTask } + fun removeSubprojectChildTasks(childTasksName: String) { + removeChildTasks(project.subprojects, childTasksName) + } + + fun removeChildTasks(project: Project) { + childDokkaTaskPaths = childDokkaTaskPaths.filter { path -> + parsePath(path).parent != parsePath(project.path) + }.toSet() + } + + fun removeChildTasks(projects: Iterable) { + projects.forEach { project -> removeChildTasks(project) } + } + + private fun checkIsAbstractDokkaTask(task: Task): AbstractDokkaTask { + if (task is AbstractDokkaTask) { + return task + } + throw IllegalArgumentException( + "Only tasks of type ${AbstractDokkaTask::class.java.name} can be added as child for " + + "${AbstractDokkaParentTask::class.java.name} tasks.\n" + + "Found task ${task.path} of type ${task::class.java.name} added to $path" + ) } } + 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 6413d788..5deaac49 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,12 +1,20 @@ +@file:Suppress("UnstableApiUsage") + package org.jetbrains.dokka.gradle +import groovy.lang.Closure +import org.gradle.api.Action import org.gradle.api.DefaultTask +import org.gradle.api.Task import org.gradle.api.artifacts.Configuration import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.mapProperty import org.jetbrains.dokka.DokkaBootstrap import org.jetbrains.dokka.DokkaConfigurationImpl -import org.jetbrains.dokka.plugability.Configurable +import org.jetbrains.dokka.DokkaDefaults import org.jetbrains.dokka.toJsonString import java.io.File import java.util.function.BiConsumer @@ -14,23 +22,26 @@ import kotlin.reflect.KClass abstract class AbstractDokkaTask( private val bootstrapClass: KClass = DokkaBootstrap::class -) : DefaultTask(), Configurable { +) : DefaultTask() { @OutputDirectory - var outputDirectory: File = defaultDokkaOutputDirectory() + val outputDirectory: Property = project.objects.safeProperty() + .safeConvention(defaultDokkaOutputDirectory()) @Optional @InputDirectory - var cacheRoot: File? = null + val cacheRoot: Property = project.objects.safeProperty() @Input - var failOnWarning: Boolean = false + val failOnWarning: Property = project.objects.safeProperty() + .safeConvention(DokkaDefaults.failOnWarning) @Input - var offlineMode: Boolean = false + val offlineMode: Property = project.objects.safeProperty() + .safeConvention(DokkaDefaults.offlineMode) @Input - override val pluginsConfiguration: MutableMap = mutableMapOf() + val pluginsConfiguration: MapProperty = project.objects.mapProperty() @Classpath val plugins: Configuration = project.maybeCreateDokkaPluginConfiguration(name) @@ -38,8 +49,16 @@ abstract class AbstractDokkaTask( @Classpath val runtime: Configuration = project.maybeCreateDokkaRuntimeConfiguration(name) + final override fun doFirst(action: Action): Task { + return super.doFirst(action) + } + + final override fun doFirst(action: Closure<*>): Task { + return super.doFirst(action) + } + @TaskAction - protected open fun generateDocumentation() { + internal open fun generateDocumentation() { DokkaBootstrap(runtime, bootstrapClass).apply { configure(buildDokkaConfiguration().toJsonString(), createProxyLogger()) generate() 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 4762c333..37571fc5 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 @@ -4,16 +4,21 @@ import org.jetbrains.dokka.DokkaConfigurationImpl open class DokkaCollectorTask : AbstractDokkaParentTask() { + override fun generateDocumentation() { + checkChildDokkaTasksIsNotEmpty() + super.generateDocumentation() + } + override fun buildDokkaConfiguration(): DokkaConfigurationImpl { val initialDokkaConfiguration = DokkaConfigurationImpl( - outputDir = outputDirectory, - cacheRoot = cacheRoot, - failOnWarning = failOnWarning, - offlineMode = offlineMode, + outputDir = outputDirectory.getSafe(), + cacheRoot = cacheRoot.getSafe(), + failOnWarning = failOnWarning.getSafe(), + offlineMode = offlineMode.getSafe(), pluginsClasspath = plugins.resolve().toSet(), ) - val subprojectDokkaConfigurations = dokkaTasks.map { dokkaTask -> dokkaTask.buildDokkaConfiguration() } + val subprojectDokkaConfigurations = childDokkaTasks.map { dokkaTask -> dokkaTask.buildDokkaConfiguration() } return subprojectDokkaConfigurations.fold(initialDokkaConfiguration) { acc, it: DokkaConfigurationImpl -> acc.copy( sourceSets = acc.sourceSets + it.sourceSets, diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout.kt new file mode 100644 index 00000000..8cca98d5 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayout.kt @@ -0,0 +1,52 @@ +package org.jetbrains.dokka.gradle + +import java.io.File + +interface DokkaMultiModuleFileLayout { + fun targetChildOutputDirectory(parent: AbstractDokkaParentTask, child: AbstractDokkaTask): File + + object NoCopy : DokkaMultiModuleFileLayout { + override fun targetChildOutputDirectory(parent: AbstractDokkaParentTask, child: AbstractDokkaTask): File { + return child.outputDirectory.getSafe() + } + } + + object CompactInParent : DokkaMultiModuleFileLayout { + override fun targetChildOutputDirectory(parent: AbstractDokkaParentTask, child: AbstractDokkaTask): File { + val relativeProjectPath = parent.project.relativeProjectPath(child.project.path) + val relativeFilePath = relativeProjectPath.replace(":", File.separator) + check(!File(relativeFilePath).isAbsolute) { "Unexpected absolute path $relativeFilePath" } + return parent.outputDirectory.getSafe().resolve(relativeFilePath) + } + } +} + +internal fun DokkaMultiModuleTask.targetChildOutputDirectory( + child: AbstractDokkaTask +): File { + return fileLayout.targetChildOutputDirectory(this, child) +} + +internal fun DokkaMultiModuleTask.copyChildOutputDirectories() { + childDokkaTasks.forEach { child -> + fileLayout.copyChildOutputDirectory(this, child) + } +} + +internal fun DokkaMultiModuleFileLayout.copyChildOutputDirectory( + parent: AbstractDokkaParentTask, child: AbstractDokkaTask +) { + val targetChildOutputDirectory = parent.project.file(targetChildOutputDirectory(parent, child)) + val sourceChildOutputDirectory = child.outputDirectory.getSafe() + + if (!sourceChildOutputDirectory.exists()) { + return + } + + if (sourceChildOutputDirectory.absoluteFile == targetChildOutputDirectory.absoluteFile) { + return + } + + sourceChildOutputDirectory.copyRecursively(targetChildOutputDirectory, overwrite = false) +} + 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..29e0d76e --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTask.kt @@ -0,0 +1,69 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.internal.tasks.TaskDependencyInternal +import org.gradle.api.tasks.* +import org.jetbrains.dokka.DokkaConfigurationImpl +import org.jetbrains.dokka.DokkaModuleDescriptionImpl +import org.jetbrains.dokka.DokkaMultimoduleBootstrapImpl +import java.io.File + +@Suppress("unused") // Shall provide source compatibility if possible +@Deprecated("Use 'DokkaMultimoduleTask' instead", ReplaceWith("DokkaMultimoduleTask")) +typealias DokkaMultimoduleTask = DokkaMultiModuleTask + + +open class DokkaMultiModuleTask : AbstractDokkaParentTask(DokkaMultimoduleBootstrapImpl::class) { + + /** + * Name of the file containing all necessary module information. + * This file has to be placed inside the subproject root directory. + */ + @Internal + var documentationFileName: String = "README.md" + + @Internal + var fileLayout: DokkaMultiModuleFileLayout = DokkaMultiModuleFileLayout.CompactInParent + + @get:InputFiles + internal val childDocumentationFiles: Iterable + get() = childDokkaTasks.map { task -> task.project.projectDir.resolve(documentationFileName) } + + @get:InputFiles + internal val sourceChildOutputDirectories: Iterable + get() = childDokkaTasks.map { task -> task.outputDirectory.getSafe() } + + @get:OutputDirectories + internal val targetChildOutputDirectories: Iterable + get() = childDokkaTasks.map { task -> targetChildOutputDirectory(task) } + + @Internal + override fun getTaskDependencies(): TaskDependencyInternal { + return super.getTaskDependencies() + childDokkaTasks + } + + override fun generateDocumentation() { + checkChildDokkaTasksIsNotEmpty() + copyChildOutputDirectories() + super.generateDocumentation() + } + + override fun buildDokkaConfiguration(): DokkaConfigurationImpl { + return DokkaConfigurationImpl( + outputDir = outputDirectory.getSafe(), + cacheRoot = cacheRoot.getSafe(), + pluginsConfiguration = pluginsConfiguration.getSafe(), + failOnWarning = failOnWarning.getSafe(), + offlineMode = offlineMode.getSafe(), + pluginsClasspath = plugins.resolve().toSet(), + modules = childDokkaTasks.map { dokkaTask -> + DokkaModuleDescriptionImpl( + name = dokkaTask.project.name, + path = targetChildOutputDirectory(dokkaTask).relativeTo(outputDirectory.getSafe()), + docFile = dokkaTask.project.projectDir.resolve(documentationFileName).absoluteFile + ) + } + ) + } +} + + 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 deleted file mode 100644 index 48a9721f..00000000 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.jetbrains.dokka.gradle - -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 - -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" - - @Internal - override fun getTaskDependencies(): TaskDependencyInternal { - return super.getTaskDependencies() + dokkaTasks - } - - override fun buildDokkaConfiguration(): DokkaConfigurationImpl { - return DokkaConfigurationImpl( - outputDir = outputDirectory, - cacheRoot = cacheRoot, - pluginsConfiguration = pluginsConfiguration, - failOnWarning = failOnWarning, - offlineMode = offlineMode, - pluginsClasspath = plugins.resolve().toSet(), - modules = dokkaTasks.map { dokkaTask -> - DokkaModuleDescriptionImpl( - name = dokkaTask.project.name, - path = dokkaTask.outputDirectory.relativeTo(outputDirectory), - docFile = dokkaTask.project.projectDir.resolve(documentationFileName).absoluteFile - ) - } - ) - } -} 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 c5713d16..6f92abdf 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 @@ -26,12 +26,12 @@ open class DokkaTask : AbstractDokkaTask(DokkaBootstrapImpl::class) { override fun buildDokkaConfiguration(): DokkaConfigurationImpl { return DokkaConfigurationImpl( - outputDir = outputDirectory, - cacheRoot = cacheRoot, - offlineMode = offlineMode, - failOnWarning = failOnWarning, + outputDir = outputDirectory.getSafe(), + cacheRoot = cacheRoot.getSafe(), + offlineMode = offlineMode.getSafe(), + failOnWarning = failOnWarning.getSafe(), sourceSets = dokkaSourceSets.build(), - pluginsConfiguration = pluginsConfiguration, + pluginsConfiguration = pluginsConfiguration.getSafe(), pluginsClasspath = plugins.resolve() ) } diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/TaskDependencyInternalWithAdditions.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/TaskDependencyInternalWithAdditions.kt index fb648c02..969b1aa1 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/TaskDependencyInternalWithAdditions.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/TaskDependencyInternalWithAdditions.kt @@ -5,7 +5,7 @@ import org.gradle.api.internal.tasks.AbstractTaskDependency import org.gradle.api.internal.tasks.TaskDependencyInternal import org.gradle.api.internal.tasks.TaskDependencyResolveContext -operator fun TaskDependencyInternal.plus(tasks: Iterable): TaskDependencyInternal { +internal operator fun TaskDependencyInternal.plus(tasks: Iterable): TaskDependencyInternal { return TaskDependencyInternalWithAdditions(this, tasks.toSet()) } diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/checkChildDokkaTasksIsNotEmpty.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/checkChildDokkaTasksIsNotEmpty.kt new file mode 100644 index 00000000..a47ea4cd --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/checkChildDokkaTasksIsNotEmpty.kt @@ -0,0 +1,43 @@ +package org.jetbrains.dokka.gradle + +import org.jetbrains.dokka.DokkaException + +fun AbstractDokkaParentTask.checkChildDokkaTasksIsNotEmpty() { + if (childDokkaTaskPaths.isEmpty()) { + throw DokkaException( + """ + The ${this::class.java.simpleName} $path has no configured child tasks. + Add some dokka tasks like e.g.: + + tasks.named("$name") { + addChildTask(..) + addChildTasks(subprojects, "...") + //... + } + """.trimIndent() + ) + } + + if (childDokkaTasks.isEmpty()) { + throw DokkaException( + """ + The ${this::class.java.simpleName} $path could not find any registered child task. + child tasks: $childDokkaTaskPaths + + Please make sure to apply the dokka plugin to all included (sub)-projects individually e.g.: + + // subproject build.gradle.kts + plugins { + id("org.jetbrains.dokka") + } + + or + + // parent build.gradle.kts + subprojects { + plugins.apply("org.jetbrains.dokka") + } + """ + ) + } +} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokka.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokka.kt deleted file mode 100644 index 2625f64c..00000000 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/dokka.kt +++ /dev/null @@ -1,7 +0,0 @@ -import org.gradle.api.Project -import org.gradle.kotlin.dsl.withType -import org.jetbrains.dokka.gradle.DokkaTask - -fun Project.dokka(configuration: DokkaTask.() -> Unit) { - tasks.withType().configureEach(configuration) -} 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 71fad405..8ba28e83 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 @@ -1,5 +1,6 @@ package org.jetbrains.dokka.gradle +import org.gradle.api.DefaultTask import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.register @@ -11,7 +12,7 @@ open class DokkaPlugin : Plugin { project.setupDokkaTasks( name = "dokkaJavadoc", - multimoduleTaskSupported = false + multiModuleTaskSupported = false ) { plugins.dependencies.add(project.dokkaArtifacts.javadocPlugin) } @@ -26,12 +27,12 @@ open class DokkaPlugin : Plugin { } /** - * Creates [DokkaTask], [DokkaMultimoduleTask] for the given + * Creates [DokkaTask], [DokkaMultiModuleTask] for the given * name and configuration. */ private fun Project.setupDokkaTasks( name: String, - multimoduleTaskSupported: Boolean = true, + multiModuleTaskSupported: Boolean = true, collectorTaskSupported: Boolean = true, configuration: AbstractDokkaTask.() -> Unit = {} ) { @@ -42,18 +43,24 @@ open class DokkaPlugin : Plugin { } if (project.subprojects.isNotEmpty()) { - if (multimoduleTaskSupported) { - val multimoduleName = "${name}Multimodule" - project.maybeCreateDokkaPluginConfiguration(multimoduleName) - project.maybeCreateDokkaRuntimeConfiguration(multimoduleName) - project.tasks.register(multimoduleName) { - dokkaTaskNames = dokkaTaskNames + name + if (multiModuleTaskSupported) { + val multiModuleName = "${name}MultiModule" + project.maybeCreateDokkaPluginConfiguration(multiModuleName) + project.maybeCreateDokkaRuntimeConfiguration(multiModuleName) + project.tasks.register(multiModuleName) { + addSubprojectChildTasks(name) configuration() } + project.tasks.register("${name}Multimodule") { + dependsOn(multiModuleName) + doLast { + logger.warn("'Multimodule' is deprecated. Use 'MultiModule' instead") + } + } } if (collectorTaskSupported) { project.tasks.register("${name}Collector") { - dokkaTaskNames = dokkaTaskNames + name + addSubprojectChildTasks(name) } } } diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/toDokkaSourceSetImpl.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/toDokkaSourceSetImpl.kt index 64d44157..c815a8a3 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/toDokkaSourceSetImpl.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/toDokkaSourceSetImpl.kt @@ -63,7 +63,6 @@ private fun GradleDokkaSourceSetBuilder.externalDocumentationLinksWithDefaults() .toSet() } - private fun GradleDokkaSourceSetBuilder.suppressedFilesWithDefaults(): Set { val suppressedFilesForAndroid = if (project.isAndroidProject()) { val generatedRoot = project.buildDir.resolve("generated").absoluteFile 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 28c16a2a..7a69c409 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 @@ -3,8 +3,10 @@ package org.jetbrains.dokka.gradle import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.UnknownDomainObjectException +import org.gradle.api.provider.HasMultipleValues import org.gradle.api.provider.Property import org.gradle.api.provider.Provider +import org.gradle.util.Path import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType @@ -18,6 +20,14 @@ internal infix fun Property.by(value: Provider) { this.set(value) } +internal infix fun HasMultipleValues.by(values: Iterable) { + this.set(values) +} + +internal fun parsePath(path: String): Path { + return Path.path(path) +} + internal val Project.kotlinOrNull: KotlinProjectExtension? get() = try { project.extensions.findByType(KotlinProjectExtension::class.java) diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTaskTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTaskTest.kt index da0f3f35..f0e05637 100644 --- a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTaskTest.kt +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaParentTaskTest.kt @@ -1,107 +1,200 @@ package org.jetbrains.dokka.gradle -import org.gradle.kotlin.dsl.register -import org.gradle.kotlin.dsl.withType +import org.gradle.api.Project +import org.gradle.api.UnknownTaskException +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.getByName import org.gradle.testfixtures.ProjectBuilder +import org.jetbrains.dokka.DokkaConfigurationImpl import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertTrue +import kotlin.test.assertFailsWith class AbstractDokkaParentTaskTest { private val rootProject = ProjectBuilder.builder().build() private val subproject0 = ProjectBuilder.builder().withName("subproject0").withParent(rootProject).build() private val subproject1 = ProjectBuilder.builder().withName("subproject1").withParent(rootProject).build() - private val subSubproject0a = ProjectBuilder.builder().withName("subSubproject0a").withParent(subproject0).build() + private val subSubproject0 = ProjectBuilder.builder().withName("subSubproject0").withParent(subproject0).build() init { - rootProject.allprojects { project -> project.plugins.apply("org.jetbrains.dokka") } + rootProject.subprojects { project -> + project.tasks.create("dokkaTask") + } } - private val parentTasks = rootProject.tasks.withType().toList() + private val parentTask = rootProject.tasks.create("parent") + @Test - fun `at least one parent task is registered`() { - assertTrue( - parentTasks.isNotEmpty(), - "Expected at least one ${AbstractDokkaParentTask::class.simpleName} task in rootProject" + fun `add and remove tasks by reference`() { + assertEquals( + emptySet(), parentTask.childDokkaTasks, + "Expected no childDokkaTasks by default" + ) + + parentTask.addChildTask(subproject0.dokkaTask) + assertEquals( + setOf(subproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subproject0.dokkaTask.path} being registered as child task" + ) + + parentTask.addChildTask(subproject1.dokkaTask) + assertEquals( + setOf(subproject0.dokkaTask, subproject1.dokkaTask), parentTask.childDokkaTasks, + "Expected both dokka tasks being present" + ) + + parentTask.removeChildTask(subproject0.dokkaTask) + assertEquals( + setOf(subproject1.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subproject0.dokkaTask.path} being removed from child tasks" + ) + + parentTask.addChildTask(subSubproject0.dokkaTask) + assertEquals( + setOf(subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subSubproject0.dokkaTask.path} being added as child task" + ) + + parentTask.addChildTask(subSubproject0.dokkaTask) + assertEquals( + setOf(subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected no effect for adding a task twice" ) } @Test - fun `configuring subprojects`() { - parentTasks.forEach { task -> - assertEquals( - setOf(":subproject0", ":subproject1", ":subproject0:subSubproject0a"), task.subprojectPaths, - "Expected all sub projects registered by default" - ) - - assertEquals( - listOf(subproject0, subproject1, subSubproject0a), task.subprojects, - "Expected all sub projects registered by default" - ) - - assertEquals(3, task.dokkaTasks.size, "Expected three referenced dokka tasks") - assertTrue(listOf(subproject0, subproject1, subSubproject0a).all { project -> - task.dokkaTasks.any { task -> task in project.tasks } - }, "Expected all sub projects to contribute to referenced dokka tasks") - - task.removeSubproject(subproject0) - assertEquals( - setOf(":subproject1", ":subproject0:subSubproject0a"), task.subprojectPaths, - "Expected subproject0 to be removed (without removing its children)" - ) - - task.addSubproject(subproject0) - assertEquals( - setOf(":subproject1", ":subproject0:subSubproject0a", ":subproject0"), task.subprojectPaths, - "Expected subproject0 being added again" - ) - - task.addSubproject(subproject0) - assertEquals( - setOf(":subproject1", ":subproject0:subSubproject0a", ":subproject0"), task.subprojectPaths, - "Expected adding same project twice to be ignored" - ) - - task.removeAllProjects(subproject0) - assertEquals( - setOf(":subproject1"), task.subprojectPaths, - "Expected subproject0 and subSubproject0a to be removed" - ) - - task.addAllProjects(subproject0) - assertEquals( - setOf(":subproject1", ":subproject0", ":subproject0:subSubproject0a"), task.subprojectPaths, - "Expected subproject0 and subSubproject0a to be added again" - ) - } + fun `add and remove by absolute path`() { + parentTask.addChildTask(":subproject0:dokkaTask") + assertEquals( + setOf(subproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subproject0.dokkaTask.path} as child task" + ) + + parentTask.addChildTask(":subproject0:subSubproject0:dokkaTask") + assertEquals( + setOf(subproject0.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subSubproject0.dokkaTask.path} being added as child task" + ) + + parentTask.removeChildTask(":subproject0:dokkaTask") + assertEquals( + setOf(subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subproject0.dokkaTask.path} being removed as child task" + ) } @Test - fun `configure dokkaTaskNames`() { - parentTasks.forEach { task -> - assertEquals( - 3, task.dokkaTasks.size, - "Expected 3 tasks referenced by default" - ) - - val customDokkaTaskName = "custom${task.name}" - task.dokkaTaskNames = setOf(customDokkaTaskName) - assertTrue(task.dokkaTasks.isEmpty(), "Expected no $customDokkaTaskName. Found: ${task.dokkaTasks}") - - rootProject.subprojects { subproject -> - subproject.tasks.register(customDokkaTaskName) - } - - assertEquals( - 3, task.dokkaTasks.size, - "Expected three $customDokkaTaskName found" - ) - - task.dokkaTasks.forEach { dokkaTask -> - assertEquals(customDokkaTaskName, dokkaTask.name) - } - } + fun `add and remove by relative path`() { + parentTask.addChildTask("subproject0:dokkaTask") + assertEquals( + setOf(subproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subproject0.dokkaTask.path} as child task" + ) + + parentTask.addChildTask("subproject0:subSubproject0:dokkaTask") + assertEquals( + setOf(subproject0.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subSubproject0.dokkaTask.path} being added as child task" + ) + + parentTask.removeChildTask("subproject0:dokkaTask") + assertEquals( + setOf(subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subproject0.dokkaTask.path} being removed as child task" + ) + } + + @Test + fun `add and remove by relative path ob subproject0`() { + val parentTask = subproject0.tasks.create("parent") + + parentTask.addChildTask("subSubproject0:dokkaTask") + assertEquals( + setOf(subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subSubproject0.dokkaTask.path} being registered as child" + ) + + parentTask.removeChildTask("subSubproject0:dokkaTask") + assertEquals( + emptySet(), parentTask.childDokkaTasks, + "Expected ${subSubproject0.dokkaTask.path} being removed as child" + ) + } + + @Test + fun `add and remove by project and name`() { + parentTask.addChildTasks(rootProject.subprojects, "dokkaTask") + assertEquals( + setOf(subproject0.dokkaTask, subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected all subproject tasks being registered as child task" + ) + + parentTask.removeChildTasks(rootProject.subprojects, "dokkaTask") + assertEquals( + emptySet(), parentTask.childDokkaTasks, + "Expected all tasks being removed" + ) + + parentTask.addChildTasks(listOf(subproject0), "dokkaTask") + assertEquals( + setOf(subproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected only ${subproject0.dokkaTask.path} being registered as child" + ) + + parentTask.addSubprojectChildTasks("dokkaTask") + assertEquals( + setOf(subproject0.dokkaTask, subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected all subproject tasks being registered as child task" + ) + + parentTask.removeSubprojectChildTasks("dokkaTask") + assertEquals( + emptySet(), parentTask.childDokkaTasks, + "Expected all tasks being removed" + ) + + parentTask.addSubprojectChildTasks("dokkaTask") + assertEquals( + setOf(subproject0.dokkaTask, subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected all subproject tasks being registered as child task" + ) + + parentTask.removeChildTasks(subproject0) + assertEquals( + setOf(subproject1.dokkaTask, subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected only ${subproject0.dokkaTask.path} being removed" + ) + + parentTask.addSubprojectChildTasks("dokkaTask") + parentTask.removeChildTasks(listOf(subproject0, subproject1)) + assertEquals( + setOf(subSubproject0.dokkaTask), parentTask.childDokkaTasks, + "Expected ${subproject0.dokkaTask.path} and ${subproject1.dokkaTask.path} being removed" + ) + } + + @Test + fun `adding invalid path will not throw exception`() { + parentTask.addChildTask(":some:stupid:path") + parentTask.childDokkaTasks + } + + @Test + fun `adding non dokka task will throw exception`() { + val badTask = rootProject.tasks.create("badTask") + parentTask.addChildTask(badTask.path) + assertFailsWith { parentTask.childDokkaTasks } + } +} + +internal open class TestDokkaParentTask : AbstractDokkaParentTask() { + override fun buildDokkaConfiguration(): DokkaConfigurationImpl { + throw NotImplementedError() } } + +private val Project.dokkaTask: DokkaTask get() = tasks.getByName("dokkaTask") + + diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTaskTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTaskTest.kt index 18128e34..d7f0e946 100644 --- a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTaskTest.kt +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaCollectorTaskTest.kt @@ -1,13 +1,14 @@ package org.jetbrains.dokka.gradle -import org.gradle.api.artifacts.Dependency -import org.gradle.api.artifacts.FileCollectionDependency +import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.withType import org.gradle.testfixtures.ProjectBuilder import org.jetbrains.dokka.DokkaConfigurationImpl +import org.jetbrains.dokka.DokkaException import java.io.File import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertTrue class DokkaCollectorTaskTest { @@ -32,15 +33,15 @@ class DokkaCollectorTaskTest { val collectorTasks = rootProject.tasks.withType() collectorTasks.configureEach { task -> - task.outputDirectory = File("customOutputDirectory") - task.cacheRoot = File("customCacheRoot") - task.failOnWarning = true - task.offlineMode = true + task.outputDirectory by File("customOutputDirectory") + task.cacheRoot by File("customCacheRoot") + task.failOnWarning by true + task.offlineMode by true } assertTrue(collectorTasks.isNotEmpty(), "Expected at least one collector task") - collectorTasks.forEach { task -> + collectorTasks.toList().forEach { task -> val dokkaConfiguration = task.buildDokkaConfiguration() assertEquals( DokkaConfigurationImpl( @@ -48,17 +49,24 @@ class DokkaCollectorTaskTest { cacheRoot = File("customCacheRoot"), failOnWarning = true, offlineMode = true, - sourceSets = task.dokkaTasks + sourceSets = task.childDokkaTasks .map { it.buildDokkaConfiguration() } .map { it.sourceSets } .reduce { acc, list -> acc + list }, - pluginsClasspath = task.dokkaTasks + pluginsClasspath = task.childDokkaTasks .map { it.plugins.resolve() } .reduce { acc, mutableSet -> acc + mutableSet } ), dokkaConfiguration ) } + } + @Test + fun `with no child tasks throws DokkaException`() { + val project = ProjectBuilder.builder().build() + val collectorTask = project.tasks.create("collector") + project.configurations.all { configuration -> configuration.withDependencies { it.clear() } } + assertFailsWith { collectorTask.generateDocumentation() } } } diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationJsonTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationJsonTest.kt index 7a3bc2bd..29532877 100644 --- a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationJsonTest.kt +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationJsonTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("UnstableApiUsage") + package org.jetbrains.dokka.gradle import org.gradle.kotlin.dsl.withType @@ -19,12 +21,12 @@ class DokkaConfigurationJsonTest { dependencies.clear() } dokkaTask.apply { - this.failOnWarning = true - this.offlineMode = true - this.outputDirectory = File("customOutputDir") - this.cacheRoot = File("customCacheRoot") - this.pluginsConfiguration["0"] = "a" - this.pluginsConfiguration["1"] = "b" + this.failOnWarning by true + this.offlineMode by true + this.outputDirectory by File("customOutputDir") + this.cacheRoot by File("customCacheRoot") + this.pluginsConfiguration.put("0", "a") + this.pluginsConfiguration.put("1", "b") this.dokkaSourceSets.create("main") { sourceSet -> sourceSet.moduleDisplayName by "moduleDisplayName" sourceSet.displayName by "customSourceSetDisplayName" diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationSerializableTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationSerializableTest.kt index e4dc5afc..f22a5b8c 100644 --- a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationSerializableTest.kt +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaConfigurationSerializableTest.kt @@ -13,6 +13,7 @@ import java.net.URL import kotlin.test.Test import kotlin.test.assertEquals +@Suppress("UnstableApiUsage") class DokkaConfigurationSerializableTest { @get:Rule @@ -27,12 +28,12 @@ class DokkaConfigurationSerializableTest { dependencies.clear() } dokkaTask.apply { - this.failOnWarning = true - this.offlineMode = true - this.outputDirectory = File("customOutputDir") - this.cacheRoot = File("customCacheRoot") - this.pluginsConfiguration["0"] = "a" - this.pluginsConfiguration["1"] = "b" + this.failOnWarning by true + this.offlineMode by true + this.outputDirectory by File("customOutputDir") + this.cacheRoot by File("customCacheRoot") + this.pluginsConfiguration.put("0", "a") + this.pluginsConfiguration.put("1", "b") this.dokkaSourceSets.create("main") { sourceSet -> sourceSet.moduleDisplayName by "moduleDisplayName" sourceSet.displayName by "customSourceSetDisplayName" diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayoutTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayoutTest.kt new file mode 100644 index 00000000..36abacfc --- /dev/null +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleFileLayoutTest.kt @@ -0,0 +1,100 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.kotlin.dsl.create +import org.gradle.testfixtures.ProjectBuilder +import org.jetbrains.dokka.gradle.DokkaMultiModuleFileLayout.CompactInParent +import org.jetbrains.dokka.gradle.DokkaMultiModuleFileLayout.NoCopy +import java.io.File +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class DokkaMultiModuleFileLayoutTest { + + @Test + fun `no copy`() { + val project = ProjectBuilder.builder().build() + val child = project.tasks.create("child") + val parent = project.tasks.create("parent") + child.outputDirectory by File("some/path") + + assertEquals( + File("some/path"), NoCopy.targetChildOutputDirectory(parent, child), + "Expected original file path returned" + ) + } + + @Test + fun `compact in parent`() { + val rootProject = ProjectBuilder.builder().build() + val parentProject = ProjectBuilder.builder().withName("parent").withParent(rootProject).build() + val intermediateProject = ProjectBuilder.builder().withName("intermediate").withParent(parentProject).build() + val childProject = ProjectBuilder.builder().withName("child").withParent(intermediateProject).build() + + val parentTask = parentProject.tasks.create("parentTask") + val childTask = childProject.tasks.create("childTask") + + val targetOutputDirectory = CompactInParent.targetChildOutputDirectory(parentTask, childTask) + assertEquals( + parentTask.outputDirectory.getSafe().resolve("intermediate/child"), targetOutputDirectory, + "Expected nested file structure representing project structure" + ) + } + + @Test + fun copyChildOutputDirectory() { + /* Prepare */ + val project = ProjectBuilder.builder().build() + val childTask = project.tasks.create("child") + val parentTask = project.tasks.create("parent") + + val sourceOutputDirectory = childTask.outputDirectory.getSafe() + sourceOutputDirectory.mkdirs() + sourceOutputDirectory.resolve("some.file").writeText("some text") + val subFolder = sourceOutputDirectory.resolve("subFolder") + subFolder.mkdirs() + subFolder.resolve("other.file").writeText("other text") + + val layout = object : DokkaMultiModuleFileLayout { + override fun targetChildOutputDirectory(parent: AbstractDokkaParentTask, child: AbstractDokkaTask): File { + return parent.project.file("target/output") + } + } + layout.copyChildOutputDirectory(parentTask, childTask) + + /* Assertions */ + val targetOutputDirectory = project.file("target/output") + assertTrue( + targetOutputDirectory.exists() && targetOutputDirectory.isDirectory, + "Expected target output directory ${targetOutputDirectory.path} to exist" + ) + + val targetSomeFile = targetOutputDirectory.resolve("some.file") + assertTrue( + targetSomeFile.exists() && targetSomeFile.isFile, + "Expected sample file to exist in target output directory" + ) + + assertEquals( + "some text", targetSomeFile.readText(), + "Expected content to be written into sample file" + ) + + val targetSubFolder = targetOutputDirectory.resolve("subFolder") + assertTrue( + targetSubFolder.exists() && targetSubFolder.isDirectory, + "Expected sub folder being present in target output directory" + ) + + val targetOtherFile = targetSubFolder.resolve("other.file") + assertTrue( + targetOtherFile.exists() && targetOtherFile.isFile, + "Expected nested 'other.file' being copied into target" + ) + + assertEquals( + "other text", targetOtherFile.readText(), + "Expected content to be written into 'other.file'" + ) + } +} diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTaskTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTaskTest.kt index 8814a897..a7c9946d 100644 --- a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTaskTest.kt +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTaskTest.kt @@ -1,13 +1,17 @@ +@file:Suppress("UnstableApiUsage") + package org.jetbrains.dokka.gradle import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.withType import org.gradle.testfixtures.ProjectBuilder import org.jetbrains.dokka.DokkaConfigurationImpl +import org.jetbrains.dokka.DokkaException import org.jetbrains.dokka.DokkaModuleDescriptionImpl import java.io.File import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertTrue class DokkaMultiModuleTaskTest { @@ -47,19 +51,19 @@ class DokkaMultiModuleTaskTest { @Test fun buildDokkaConfiguration() { childProject.tasks.withType().configureEach { task -> - task.outputDirectory = task.project.buildDir.resolve("output") + task.outputDirectory by task.project.buildDir.resolve("output") } - val multimoduleTasks = rootProject.tasks.withType() + val multimoduleTasks = rootProject.tasks.withType() assertTrue(multimoduleTasks.isNotEmpty(), "Expected at least one multimodule task") multimoduleTasks.configureEach { task -> task.documentationFileName = "customDocumentationFileName.md" - task.outputDirectory = task.project.buildDir.resolve("customOutputDirectory") - task.cacheRoot = File("customCacheRoot") - task.pluginsConfiguration["pluginA"] = "configA" - task.failOnWarning = true - task.offlineMode = true + task.outputDirectory by task.project.buildDir.resolve("customOutputDirectory") + task.cacheRoot by File("customCacheRoot") + task.pluginsConfiguration.put("pluginA", "configA") + task.failOnWarning by true + task.offlineMode by true } multimoduleTasks.forEach { task -> @@ -75,7 +79,7 @@ class DokkaMultiModuleTaskTest { modules = listOf( DokkaModuleDescriptionImpl( name = "child", - path = File("../../child/build/output"), + path = File("child"), docFile = childProject.projectDir.resolve("customDocumentationFileName.md") ) ) @@ -87,7 +91,7 @@ class DokkaMultiModuleTaskTest { @Test fun `setting dokkaTaskNames declares proper task dependencies`() { - val multimoduleTasks = rootProject.tasks.withType() + val multimoduleTasks = rootProject.tasks.withType() assertTrue(multimoduleTasks.isNotEmpty(), "Expected at least one multimodule task") multimoduleTasks.toList().forEach { task -> @@ -102,11 +106,75 @@ class DokkaMultiModuleTaskTest { val customDokkaTask = childProject.tasks.create("customDokkaTask") multimoduleTasks.toList().forEach { task -> - task.dokkaTaskNames += "customDokkaTask" + task.addSubprojectChildTasks("customDokkaTask") val dependencies = task.taskDependencies.getDependencies(task).toSet() assertEquals(2, dependencies.size, "Expected two dependencies") assertTrue(customDokkaTask in dependencies, "Expected 'customDokkaTask' in dependencies") } } + + @Test + fun `multimodule task with no child tasks throws DokkaException`() { + val project = ProjectBuilder.builder().build() + val multimodule = project.tasks.create("multimodule") + project.configurations.configureEach { it.withDependencies { it.clear() } } + assertFailsWith { multimodule.generateDocumentation() } + } + + @Test + fun childDocumentationFiles() { + val parent = ProjectBuilder.builder().build() + val child = ProjectBuilder.builder().withName("child").withParent(parent).build() + + val parentTask = parent.tasks.create("parent") + val childTask = child.tasks.create("child") + + parentTask.addChildTask(childTask) + parentTask.documentationFileName = "module.txt" + + assertEquals( + listOf(parent.file("child/module.txt")), parentTask.childDocumentationFiles, + "Expected child documentation file being present" + ) + } + + @Test + fun sourceChildOutputDirectories() { + val parent = ProjectBuilder.builder().build() + val child = ProjectBuilder.builder().withName("child").withParent(parent).build() + + val parentTask = parent.tasks.create("parent") + val childTask = child.tasks.create("child") + + parentTask.addChildTask(childTask) + childTask.outputDirectory by child.file("custom/output") + + assertEquals( + listOf(parent.file("child/custom/output")), parentTask.sourceChildOutputDirectories, + "Expected child output directory being present" + ) + } + + @Test + fun targetChildOutputDirectories() { + val parent = ProjectBuilder.builder().build() + val child = ProjectBuilder.builder().withName("child").withParent(parent).build() + + val parentTask = parent.tasks.create("parent") + val childTask = child.tasks.create("child") + + parentTask.addChildTask(childTask) + parentTask.fileLayout = object : DokkaMultiModuleFileLayout { + override fun targetChildOutputDirectory(parent: AbstractDokkaParentTask, child: AbstractDokkaTask): File { + return parent.project.buildDir.resolve(child.name) + } + } + + assertEquals( + listOf(parent.project.buildDir.resolve("child")), parentTask.targetChildOutputDirectories, + "Expected child target output directory being present" + ) + + } } diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaPluginApplyTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaPluginApplyTest.kt new file mode 100644 index 00000000..db218b9f --- /dev/null +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaPluginApplyTest.kt @@ -0,0 +1,84 @@ +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 DokkaPluginApplyTest { + + @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().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}" + ) + } + } + + @Test + fun `parent dokka tasks have children configured`() { + val root = ProjectBuilder.builder().withName("root").build() + val child = ProjectBuilder.builder().withName("child").withParent(root).build() + root.plugins.apply("org.jetbrains.dokka") + child.plugins.apply("org.jetbrains.dokka") + + val parentTasks = root.tasks.withType() + assertTrue(parentTasks.isNotEmpty(), "Expected at least one parent task being created") + + parentTasks.toList().forEach { parentTask -> + assertEquals(1, parentTask.childDokkaTasks.size, "Expected one child dokka task") + assertEquals( + child, parentTask.childDokkaTasks.single().project, + "Expected child dokka task from child project" + ) + } + } +} 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 deleted file mode 100644 index b948c540..00000000 --- a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/DokkaTasksTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -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().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 index 013ad3f2..6a356b79 100644 --- 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 @@ -1,6 +1,6 @@ package org.jetbrains.dokka.gradle -import dokka +import org.gradle.kotlin.dsl.withType import org.gradle.testfixtures.ProjectBuilder import org.jetbrains.dokka.DokkaSourceSetID import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension @@ -9,17 +9,16 @@ import kotlin.test.Test import kotlin.test.assertEquals class KotlinDslDokkaTaskConfigurationTest { - @Test - fun `configure project using dokka extension function`() { + fun `configure dokka task`() { val project = ProjectBuilder.builder().build() project.plugins.apply("org.jetbrains.dokka") - project.dokka { - outputDirectory = File("test") + project.tasks.withType().configureEach { + it.outputDirectory by File("test") } project.tasks.withType(DokkaTask::class.java).forEach { dokkaTask -> - assertEquals(File("test"), dokkaTask.outputDirectory) + assertEquals(File("test"), dokkaTask.outputDirectory.getSafe()) } } -- cgit