From e0f937a35371cd90b22b571a3b1c7c88c4fcd447 Mon Sep 17 00:00:00 2001 From: Marcin Aman Date: Wed, 24 Mar 2021 12:03:35 +0100 Subject: [PATCH] Reintroduce multimodule documentation --- core/src/main/kotlin/configuration.kt | 1 + core/src/main/kotlin/defaultConfiguration.kt | 1 + .../TestDokkaConfigurationBuilder.kt | 4 +- docs/src/doc/docs/user_guide/gradle/usage.md | 3 + plugins/all-modules-page/build.gradle.kts | 1 + .../src/main/kotlin/MultimodulePageCreator.kt | 14 ++++ .../templates/MultiModuleDocumentationTest.kt | 65 +++++++++++++++++++ runners/cli/src/main/kotlin/cli/main.kt | 7 ++ .../dokka/gradle/AbstractDokkaTask.kt | 1 - .../dokka/gradle/DokkaMultiModuleTask.kt | 14 ++-- .../dokka/gradle/DokkaMultiModuleTaskTest.kt | 9 ++- 11 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 plugins/all-modules-page/src/test/kotlin/templates/MultiModuleDocumentationTest.kt diff --git a/core/src/main/kotlin/configuration.kt b/core/src/main/kotlin/configuration.kt index 1501ab2fd4..2b8798d7e2 100644 --- a/core/src/main/kotlin/configuration.kt +++ b/core/src/main/kotlin/configuration.kt @@ -100,6 +100,7 @@ interface DokkaConfiguration : Serializable { val pluginsConfiguration: List val delayTemplateSubstitution: Boolean val suppressObviousFunctions: Boolean + val includes: Set enum class SerializationFormat : Serializable { JSON, XML diff --git a/core/src/main/kotlin/defaultConfiguration.kt b/core/src/main/kotlin/defaultConfiguration.kt index ec1dde8ed1..aa91fa1a05 100644 --- a/core/src/main/kotlin/defaultConfiguration.kt +++ b/core/src/main/kotlin/defaultConfiguration.kt @@ -17,6 +17,7 @@ data class DokkaConfigurationImpl( override val failOnWarning: Boolean = DokkaDefaults.failOnWarning, override val delayTemplateSubstitution: Boolean = false, override val suppressObviousFunctions: Boolean = DokkaDefaults.suppressObviousFunctions, + override val includes: Set = emptySet(), ) : DokkaConfiguration data class PluginConfigurationImpl( diff --git a/core/test-api/src/main/kotlin/testApi/testRunner/TestDokkaConfigurationBuilder.kt b/core/test-api/src/main/kotlin/testApi/testRunner/TestDokkaConfigurationBuilder.kt index 82f6da3aca..c0c449fb05 100644 --- a/core/test-api/src/main/kotlin/testApi/testRunner/TestDokkaConfigurationBuilder.kt +++ b/core/test-api/src/main/kotlin/testApi/testRunner/TestDokkaConfigurationBuilder.kt @@ -35,6 +35,7 @@ class TestDokkaConfigurationBuilder { var failOnWarning: Boolean = false var modules: List = emptyList() var suppressObviousFunctions: Boolean = DokkaDefaults.suppressObviousFunctions + var includes: List = emptyList() private val lazySourceSets = mutableListOf>() fun build() = DokkaConfigurationImpl( @@ -48,7 +49,8 @@ class TestDokkaConfigurationBuilder { pluginsConfiguration = pluginsConfigurations, modules = modules, failOnWarning = failOnWarning, - suppressObviousFunctions = suppressObviousFunctions + suppressObviousFunctions = suppressObviousFunctions, + includes = includes.toSet(), ) fun sourceSets(block: SourceSetsBuilder.() -> Unit) { diff --git a/docs/src/doc/docs/user_guide/gradle/usage.md b/docs/src/doc/docs/user_guide/gradle/usage.md index 19c10fd432..ff5a54f573 100644 --- a/docs/src/doc/docs/user_guide/gradle/usage.md +++ b/docs/src/doc/docs/user_guide/gradle/usage.md @@ -209,6 +209,9 @@ kotlin { // Kotlin Multiplatform plugin configuration tasks.withType().configureEach { // custom output directory outputDirectory.set(buildDir.resolve("dokka")) + + // path to project documentation to display on all modules page + includes.from(listOf(file("project_description.md"))) dokkaSourceSets { named("customNameMain") { // The same name as in Kotlin Multiplatform plugin, so the sources are fetched automatically diff --git a/plugins/all-modules-page/build.gradle.kts b/plugins/all-modules-page/build.gradle.kts index 9c5fe1c32e..0fe46579d6 100644 --- a/plugins/all-modules-page/build.gradle.kts +++ b/plugins/all-modules-page/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { testImplementation(project(":plugins:base:base-test-utils")) testImplementation(project(":plugins:gfm")) testImplementation(project(":plugins:gfm:gfm-template-processing")) + testImplementation(project(":core:content-matcher-test-utils")) val coroutines_version: String by project implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") diff --git a/plugins/all-modules-page/src/main/kotlin/MultimodulePageCreator.kt b/plugins/all-modules-page/src/main/kotlin/MultimodulePageCreator.kt index c84aef7e4c..782ee16eca 100644 --- a/plugins/all-modules-page/src/main/kotlin/MultimodulePageCreator.kt +++ b/plugins/all-modules-page/src/main/kotlin/MultimodulePageCreator.kt @@ -3,6 +3,7 @@ package org.jetbrains.dokka.allModulesPage import org.jetbrains.dokka.DokkaConfiguration.DokkaModuleDescription import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.parsers.MarkdownParser import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.Module import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentationParsingContext import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentation @@ -26,6 +27,7 @@ import org.jetbrains.dokka.utilities.DokkaLogger import org.jetbrains.dokka.versioning.ReplaceVersionsCommand import org.jetbrains.dokka.versioning.VersioningConfiguration import org.jetbrains.dokka.versioning.VersioningPlugin +import java.io.File class MultimodulePageCreator( private val context: DokkaContext, @@ -49,6 +51,15 @@ class MultimodulePageCreator( configuration(context)?.let { group(extra = PropertyContainer.withAll(InsertTemplateExtra(ReplaceVersionsCommand))) { } } + getMultiModuleDocumentation(context.configuration.includes).takeIf { it.isNotEmpty() }?.let { nodes -> + group(kind = ContentKind.Cover) { + nodes.forEach { node -> + group { + node.children.forEach { comment(it.root) } + } + } + } + } header(2, "All modules:") table(styles = setOf(MultimoduleTable)) { header { group { text("Name") } } @@ -83,6 +94,9 @@ class MultimodulePageCreator( ) } + private fun getMultiModuleDocumentation(files: Set): List = + files.map { MarkdownParser({ null }, it.absolutePath).parse(it.readText()) } + private fun getDisplayedModuleDocumentation(module: DokkaModuleDescription): P? { val parsingContext = ModuleAndPackageDocumentationParsingContext(logger) diff --git a/plugins/all-modules-page/src/test/kotlin/templates/MultiModuleDocumentationTest.kt b/plugins/all-modules-page/src/test/kotlin/templates/MultiModuleDocumentationTest.kt new file mode 100644 index 0000000000..11a03bc4cb --- /dev/null +++ b/plugins/all-modules-page/src/test/kotlin/templates/MultiModuleDocumentationTest.kt @@ -0,0 +1,65 @@ +package org.jetbrains.dokka.allModulesPage.templates + +import matchers.content.* +import org.jetbrains.dokka.allModulesPage.MultiModuleAbstractTest +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.pages.ContentKind +import org.jetbrains.dokka.pages.ContentResolvedLink +import org.jetbrains.dokka.pages.MultimoduleRootPageNode +import org.junit.Rule +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.rules.TemporaryFolder +import kotlin.test.assertEquals + +class MultiModuleDocumentationTest : MultiModuleAbstractTest() { + @get:Rule + val folder: TemporaryFolder = TemporaryFolder() + + val documentationContent = """ + # Sample project + Sample documentation with [external link](https://www.google.pl) + """.trimIndent() + + @BeforeEach + fun setup() { + folder.create() + folder.root.resolve("README.md").writeText(documentationContent) + } + + @Test + fun `documentation should be included in all modules page`() { + val configuration = dokkaConfiguration { + includes = listOf(folder.root.resolve("README.md")) + } + + testFromData(configuration, preserveOutputLocation = true) { + allModulesPageCreationStage = { rootPage -> + (rootPage as? MultimoduleRootPageNode)?.content?.dfs { it.dci.kind == ContentKind.Cover }?.children?.firstOrNull() + ?.assertNode { + group { + group { + group { + header(1) { + +"Sample project" + } + group { + +"Sample documentation with " + link { + +"external link" + check { + assertEquals( + "https://www.google.pl", + (this as ContentResolvedLink).address + ) + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/runners/cli/src/main/kotlin/cli/main.kt b/runners/cli/src/main/kotlin/cli/main.kt index b11ed0dc0c..42ac180684 100644 --- a/runners/cli/src/main/kotlin/cli/main.kt +++ b/runners/cli/src/main/kotlin/cli/main.kt @@ -76,6 +76,13 @@ class GlobalArguments(args: Array) : DokkaConfiguration { override val suppressObviousFunctions: Boolean by lazy{ !noSuppressObviousFunctions } + private val _includes by parser.option( + ArgTypeFile, + description = "Markdown files that would be displayed in multi-module page separated by the semicolon `;`)" + ).delimiter(";") + + override val includes: Set by lazy { _includes.toSet() } + val globalPackageOptions by parser.option( ArgType.String, description = "List of package source sets in format \"prefix,-deprecated,-privateApi,+warnUndocumented,+suppress;...\" " 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 d2506f7ae6..6c1e6ab63a 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 @@ -19,7 +19,6 @@ import org.jetbrains.dokka.plugability.ConfigurableBlock import org.jetbrains.dokka.plugability.DokkaPlugin import java.io.File import java.util.function.BiConsumer -import kotlin.reflect.KClass import kotlin.reflect.full.createInstance abstract class AbstractDokkaTask : DefaultTask() { diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTask.kt index c68b53b845..b068320459 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTask.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultiModuleTask.kt @@ -1,11 +1,9 @@ package org.jetbrains.dokka.gradle +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.internal.tasks.TaskDependencyInternal import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.OutputDirectories +import org.gradle.api.tasks.* import org.jetbrains.dokka.DokkaConfigurationImpl import org.jetbrains.dokka.DokkaModuleDescriptionImpl import java.io.File @@ -17,6 +15,9 @@ typealias DokkaMultimoduleTask = DokkaMultiModuleTask private typealias TaskPath = String abstract class DokkaMultiModuleTask : AbstractDokkaParentTask() { + @InputFiles + @Optional + val includes: ConfigurableFileCollection = project.files() @Internal val fileLayout: Property = project.objects.safeProperty() @@ -32,7 +33,7 @@ abstract class DokkaMultiModuleTask : AbstractDokkaParentTask() { @get:Input internal val childDokkaTaskIncludes: Map> - get() = childDokkaTasks.filterIsInstance().associate { task -> + get() = childDokkaTasks.filterIsInstance().associate { task -> task.path to task.dokkaSourceSets.flatMap { it.includes }.toSet() } @@ -61,7 +62,8 @@ abstract class DokkaMultiModuleTask : AbstractDokkaParentTask() { includes = childDokkaTaskIncludes[dokkaTask.path].orEmpty(), sourceOutputDirectory = dokkaTask.outputDirectory.getSafe() ) - } + }, + includes = includes.toSet(), ) } 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 cc9d8706aa..54f01fa599 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 @@ -22,7 +22,7 @@ class DokkaMultiModuleTaskTest { .withProjectDir(rootProject.projectDir.resolve("child")) .withParent(rootProject).build() - private val childDokkaTask = childProject.tasks.create("childDokkaTask") + private val childDokkaTask = childProject.tasks.create("childDokkaTask") private val multiModuleTask = rootProject.tasks.create("multiModuleTask").apply { addChildTask(childDokkaTask) @@ -53,6 +53,7 @@ class DokkaMultiModuleTaskTest { fun buildDokkaConfiguration() { val include1 = childDokkaTask.project.file("include1.md") val include2 = childDokkaTask.project.file("include2.md") + val topLevelInclude = multiModuleTask.project.file("README.md") childDokkaTask.apply { dokkaSourceSets.create("main") @@ -69,6 +70,7 @@ class DokkaMultiModuleTaskTest { pluginsConfiguration.add(PluginConfigurationImpl("pluginA", DokkaConfiguration.SerializationFormat.JSON, """ { "key" : "value2" } """)) failOnWarning by true offlineMode by true + includes.from(listOf(topLevelInclude)) } val dokkaConfiguration = multiModuleTask.buildDokkaConfiguration() @@ -81,6 +83,7 @@ class DokkaMultiModuleTaskTest { pluginsClasspath = emptyList(), failOnWarning = true, offlineMode = true, + includes = setOf(topLevelInclude), modules = listOf( DokkaModuleDescriptionImpl( name = "child", @@ -100,7 +103,7 @@ class DokkaMultiModuleTaskTest { assertEquals(1, dependenciesInitial.size, "Expected one dependency") val dependency = dependenciesInitial.single() - assertTrue(dependency is DokkaTask, "Expected dependency to be of Type ${DokkaTask::class.simpleName}") + assertTrue(dependency is DokkaTaskPartial, "Expected dependency to be of Type ${DokkaTaskPartial::class.simpleName}") assertEquals(childProject, dependency.project, "Expected dependency from child project") @@ -138,7 +141,7 @@ class DokkaMultiModuleTaskTest { } val secondChildDokkaTaskInclude = childProject.file("include4") - val secondChildDokkaTask = childProject.tasks.create("secondChildDokkaTask") { + val secondChildDokkaTask = childProject.tasks.create("secondChildDokkaTask") { dokkaSourceSets.create("main") { it.includes.from(secondChildDokkaTaskInclude) }