diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/DokkaBasePlugin.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/DokkaBasePlugin.kt index 089911f69a..11aa495e89 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/DokkaBasePlugin.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/DokkaBasePlugin.kt @@ -28,6 +28,7 @@ import org.jetbrains.dokka.gradle.engine.parameters.DokkaSourceSetSpec import org.jetbrains.dokka.gradle.engine.parameters.KotlinPlatform import org.jetbrains.dokka.gradle.engine.parameters.VisibilityModifier import org.jetbrains.dokka.gradle.internal.* +import org.jetbrains.dokka.gradle.internal.PluginFeaturesService.Companion.pluginFeaturesService import org.jetbrains.dokka.gradle.tasks.* import java.io.File import javax.inject.Inject @@ -266,6 +267,7 @@ constructor( target.tasks.withType().configureEach { cacheDirectory.convention(dokkaExtension.dokkaCacheDirectory) workerLogFile.convention(temporaryDir.resolve("dokka-worker.log")) + dumpDokkaConfigurationDebugFile.convention(target.pluginFeaturesService.dumpDokkaConfigurationDebugFile) dokkaConfigurationJsonFile.convention(temporaryDir.resolve("dokka-configuration.json")) workerIsolation.convention(dokkaExtension.dokkaGeneratorIsolation) publicationEnabled.convention(true) diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/PluginFeaturesService.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/PluginFeaturesService.kt index 809403ce11..43bd365afa 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/PluginFeaturesService.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/internal/PluginFeaturesService.kt @@ -50,6 +50,9 @@ constructor( /** If `true`, suppress [k2AnalysisEnabled] messages. */ val k2AnalysisNoWarn: Property + /** @see PluginFeaturesService.dumpDokkaConfigurationDebugFile */ + val dumpDokkaConfigurationDebugFile: Property + /** [Project.getDisplayName] - only used for log messages. */ val projectDisplayName: Property @@ -231,6 +234,19 @@ constructor( } } + /** + * Enable saving the [org.jetbrains.dokka.DokkaConfiguration] used to run + * [org.jetbrains.dokka.DokkaGenerator] to a file. + * + * The configuration file is only useful for debugging. + * (For example, checking the generated configuration on CI, which might use Linux/Windows/macOS). + * + * @see org.jetbrains.dokka.gradle.tasks.DokkaGenerateTask.dokkaConfigurationJsonFile + */ + internal val dumpDokkaConfigurationDebugFile: Boolean by lazy { + parameters.dumpDokkaConfigurationDebugFile.getOrElse(false) + } + /** Values for [pluginMode]. */ private enum class PluginMode { V1Enabled, @@ -267,7 +283,7 @@ constructor( * ORG_GRADLE_PROJECT_org.jetbrains.dokka.gradle.enabledLogHtmlPublicationLink=false * ``` */ - val enableLogHtmlPublicationLink: Provider = + val enableLogHtmlPublicationLink: Provider = providers.gradleProperty("org.jetbrains.dokka.gradle.enableLogHtmlPublicationLink") .toBoolean() .orElse(true) @@ -296,6 +312,10 @@ constructor( private const val K2_ANALYSIS_NO_WARN_FLAG_PRETTY = "$K2_ANALYSIS_ENABLED_FLAG.noWarn" + /** @see PluginFeaturesService.dumpDokkaConfigurationDebugFile */ + private const val DUMP_DOKKA_CONFIG_DEBUG_FILE = + "org.jetbrains.dokka.internal.dumpDokkaConfigurationDebugFile" + @Suppress("ObjectPrivatePropertyName") private val `To learn about migrating read the migration guide` = /* language=text */ """ |To learn about migrating read the migration guide https://kotl.in/dokka-gradle-migration @@ -381,6 +401,8 @@ constructor( .toBoolean() ) + dumpDokkaConfigurationDebugFile.set(getFlag(DUMP_DOKKA_CONFIG_DEBUG_FILE).toBoolean()) + configureParamsDuringAccessorsGeneration(project) } } diff --git a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/tasks/DokkaGenerateTask.kt b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/tasks/DokkaGenerateTask.kt index 5f02fa25a8..1bd75b3702 100644 --- a/dokka-runners/dokka-gradle-plugin/src/main/kotlin/tasks/DokkaGenerateTask.kt +++ b/dokka-runners/dokka-gradle-plugin/src/main/kotlin/tasks/DokkaGenerateTask.kt @@ -97,8 +97,17 @@ constructor( abstract val workerLogFile: RegularFileProperty /** - * The [DokkaConfiguration] by Dokka Generator can be saved to a file for debugging purposes. - * To disable this behaviour set this property to `null`. + * Enable saving the [DokkaConfiguration] used to config Dokka Generator to a file, for debugging purposes. + * + * @see dokkaConfigurationJsonFile + */ + @InternalDokkaGradlePluginApi + @get:Optional + @get:Input + abstract val dumpDokkaConfigurationDebugFile: Property + + /** + * @see dumpDokkaConfigurationDebugFile */ @InternalDokkaGradlePluginApi @get:Optional @@ -205,6 +214,9 @@ constructor( private fun dumpDokkaConfigurationJson( dokkaConfiguration: DokkaConfiguration, ) { + if (dumpDokkaConfigurationDebugFile.orNull != true) { + return + } val destFile = dokkaConfigurationJsonFile.asFile.orNull ?: return destFile.parentFile.mkdirs() destFile.createNewFile() diff --git a/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/GradlePropertiesBuilder.kt b/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/GradlePropertiesBuilder.kt index ec18f48b28..1431837379 100644 --- a/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/GradlePropertiesBuilder.kt +++ b/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/GradlePropertiesBuilder.kt @@ -73,6 +73,8 @@ data class GradlePropertiesBuilder( var k2Analysis: Boolean? = null, var k2AnalysisNoWarn: Boolean? = null, var enableLogHtmlPublicationLink: Boolean? = false, + /** @see org.jetbrains.dokka.gradle.internal.PluginFeaturesService.dumpDokkaConfigurationDebugFile */ + var dumpDokkaConfigurationDebugFile: Boolean? = true, ) /** Gradle Daemon JVM args. */ @@ -107,6 +109,7 @@ data class GradlePropertiesBuilder( putNotNull("org.jetbrains.dokka.experimental.tryK2", k2Analysis) putNotNull("org.jetbrains.dokka.experimental.tryK2.noWarn", k2AnalysisNoWarn) putNotNull("org.jetbrains.dokka.gradle.enableLogHtmlPublicationLink", enableLogHtmlPublicationLink) + putNotNull("org.jetbrains.dokka.internal.dumpDokkaConfigurationDebugFile", dumpDokkaConfigurationDebugFile) } with(kotlin) { diff --git a/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt b/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt index 78469651a2..1604842f60 100644 --- a/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt +++ b/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt @@ -159,7 +159,7 @@ fun gradleKtsProjectTest( return gradleProjectTest( testProjectName = rootProjectNameValue, - baseDir = baseDir, + baseDir = baseDir.resolve(projectLocation), ) { settingsGradleKts = """ diff --git a/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/stringUtils.kt b/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/stringUtils.kt index 295e3d4f7d..8965eb450c 100644 --- a/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/stringUtils.kt +++ b/dokka-runners/dokka-gradle-plugin/src/testFixtures/kotlin/stringUtils.kt @@ -25,7 +25,7 @@ fun String.sortLines(separator: String = "\n") = /** Replace characters that don't match [isLetterOrDigit] with [replacement]. */ -internal fun String.replaceNonAlphaNumeric( +fun String.replaceNonAlphaNumeric( replacement: String = "-" ): String = asIterable().joinToString("") { c -> diff --git a/dokka-runners/dokka-gradle-plugin/src/testFunctional/kotlin/DumpDokkaConfigurationTest.kt b/dokka-runners/dokka-gradle-plugin/src/testFunctional/kotlin/DumpDokkaConfigurationTest.kt new file mode 100644 index 0000000000..4e7245dc79 --- /dev/null +++ b/dokka-runners/dokka-gradle-plugin/src/testFunctional/kotlin/DumpDokkaConfigurationTest.kt @@ -0,0 +1,143 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package org.jetbrains.dokka.gradle + +import io.kotest.core.spec.style.FunSpec +import io.kotest.core.test.TestScope +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.file.shouldExist +import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.jetbrains.dokka.gradle.internal.DokkaConstants.DOKKA_VERSION +import org.jetbrains.dokka.gradle.utils.* +import kotlin.io.path.invariantSeparatorsPathString +import kotlin.io.path.name +import kotlin.io.path.relativeToOrSelf + +class DumpDokkaConfigurationTest : FunSpec({ + + context("enabling dumpDokkaConfigurationDebugFile should dump configuration to file") { + + val project = initProject() + + project.gradleProperties { + dokka { + dumpDokkaConfigurationDebugFile = true + } + } + + project.runner + .addArguments( + ":dokkaGenerateModuleHtml", + ":dokkaGeneratePublicationHtml", + "--rerun-tasks", + ) + .forwardOutput() + .build { + test("expect dokka tasks run successfully") { + shouldHaveTasksWithOutcome( + ":dokkaGenerateModuleHtml" to SUCCESS, + ":dokkaGeneratePublicationHtml" to SUCCESS, + ) + } + + test("expect dokka tasks generate config files") { + project.dir("build/tmp") + .findFiles { it.name == "dokka-configuration.json" } + .map { it.relativeToOrSelf(project.projectDir).invariantSeparatorsPathString } + .toList() + .shouldContainExactlyInAnyOrder( + "build/tmp/dokkaGeneratePublicationHtml/dokka-configuration.json", + "build/tmp/dokkaGenerateModuleHtml/dokka-configuration.json" + ) + } + } + } + + context("implicit task outputs should not contain Dokka config file") { + listOf( + "dumpDokkaConfigurationDebugFile is not set" to null, + "dumpDokkaConfigurationDebugFile is disabled" to false, + ).forEach { (testName, value) -> + context("when $testName ") { + val project = initProject() + + project.gradleProperties { + dokka { + dumpDokkaConfigurationDebugFile = value + } + } + + project.runner + .forwardOutput() + .addArguments( + ":syncDokkaOutput", + "--rerun", + ) + .build { + test("expect syncDokkaOutput runs successfully") { + shouldHaveTasksWithOutcome( + ":syncDokkaOutput" to SUCCESS, + ) + } + + test("verify sync task collects Dokka output files") { + // Just a simple check to make sure that some files were copied, because testing the config + // files don't exist could mean that nothing was copied and something else broke. + project.file("build/tmp/syncDokkaOutput/module/module-descriptor.json") + .toFile() + .shouldExist() + project.file("build/tmp/syncDokkaOutput/publication/index.html") + .toFile() + .shouldExist() + } + + test("expect no Dokka config file in output") { + project + .dir("build/tmp/syncDokkaOutput") + .findFiles { it.name == "dokka-configuration.json" } + .map { it.relativeToOrSelf(project.projectDir).invariantSeparatorsPathString } + .toList() + .shouldBeEmpty() + } + } + } + } + } +}) + +private fun TestScope.initProject( + config: GradleProjectTest.() -> Unit = {}, +): GradleProjectTest { + val baseDirName = testCase.descriptor.ids() + .joinToString("/") { it.value.substringAfter("org.jetbrains.dokka.gradle.").replaceNonAlphaNumeric() } + + return gradleKtsProjectTest( + projectLocation = baseDirName, + rootProjectName = "DumpDokkaConfigurationTest", + ) { + buildGradleKts = """ + |plugins { + | kotlin("jvm") version embeddedKotlinVersion + | id("org.jetbrains.dokka") version "$DOKKA_VERSION" + |} + | + |val syncDokkaOutput by tasks.registering(Sync::class) { + | // fetch the implicit output files of the DokkaGenerate tasks + | from(tasks.dokkaGeneratePublicationHtml) { + | into("publication") + | } + | from(tasks.dokkaGenerateModuleHtml) { + | into("module") + | } + | into(temporaryDir) + |} + | + """.trimMargin() + + createKotlinFile("src/main/kotlin/Foo.kt", "class Foo") + + config() + } +}