From a19207ed6cbe20495b692c222c64feb949c530f6 Mon Sep 17 00:00:00 2001 From: Anton Lakotka Date: Tue, 29 Aug 2023 16:17:22 +0200 Subject: [PATCH 01/10] Use `compileDependencyFiles` as main source of compilation dependencies Additionally, for Native compilations explicitly include stdlib and platform dependencies from Kotlin Native distribution. Home location of Kotlin Native distribution is collected via Internal API of Kotlin Gradle Plugin. It is safe for few reasons: * konanHome property is accessed only for past Kotlin versions (up to 1.9.X) * In Kotlin 2.0 `compileDependencyFiles` will include native-specific dependencies --- gradle/libs.versions.toml | 1 + modules/dokkatoo-plugin/build.gradle.kts | 1 + .../kotlin/adapters/DokkatooKotlinAdapter.kt | 61 ++++++------------- .../KotlinNativeDistributionAccessor.kt | 39 ++++++++++++ .../kotlin/internal/parseKotlinVersion.kt | 23 +++++++ 5 files changed, 82 insertions(+), 43 deletions(-) create mode 100644 modules/dokkatoo-plugin/src/main/kotlin/internal/KotlinNativeDistributionAccessor.kt create mode 100644 modules/dokkatoo-plugin/src/main/kotlin/internal/parseKotlinVersion.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 691addb6..a1554406 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,5 +43,6 @@ gradlePlugin-dokkatoo = { module = "dev.adamko.dokkatoo:dokkatoo-plugin", versio gradlePlugin-bcvMu = { module = "dev.adamko.kotlin.binary_compatibility_validator:bcv-gradle-plugin", version.ref = "gradlePlugin-bcvMu" } gradlePlugin-gradlePublishPlugin = { module = "com.gradle.publish:plugin-publish-plugin", version.ref = "gradlePlugin-gradlePublishPlugin" } gradlePlugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +gradlePlugin-kotlin-klibCommonizerApi = { module = "org.jetbrains.kotlin:kotlin-klib-commonizer-api", version.ref = "kotlin" } [plugins] diff --git a/modules/dokkatoo-plugin/build.gradle.kts b/modules/dokkatoo-plugin/build.gradle.kts index bfba705b..d7bd7bbc 100644 --- a/modules/dokkatoo-plugin/build.gradle.kts +++ b/modules/dokkatoo-plugin/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(libs.kotlin.dokkaCore) compileOnly(libs.gradlePlugin.kotlin) + compileOnly(libs.gradlePlugin.kotlin.klibCommonizerApi) compileOnly(libs.gradlePlugin.android) compileOnly(libs.gradlePlugin.androidApi) diff --git a/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt b/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt index 7dfaf596..5ff399a0 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt @@ -9,16 +9,14 @@ import dev.adamko.dokkatoo.dokka.parameters.DokkaSourceSetIdSpec.Companion.dokka import dev.adamko.dokkatoo.dokka.parameters.DokkaSourceSetSpec import dev.adamko.dokkatoo.dokka.parameters.KotlinPlatform import dev.adamko.dokkatoo.internal.DokkatooInternalApi -import dev.adamko.dokkatoo.internal.collectIncomingFiles +import dev.adamko.dokkatoo.internal.KotlinNativeDistributionAccessor import dev.adamko.dokkatoo.internal.not +import dev.adamko.dokkatoo.internal.parseKotlinVersion import javax.inject.Inject import org.gradle.api.Named import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.ConfigurationContainer -import org.gradle.api.attributes.Usage.JAVA_API -import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileCollection import org.gradle.api.logging.Logging @@ -35,8 +33,10 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinNativeCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataCompilation /** * The [DokkatooKotlinAdapter] plugin will automatically register Kotlin source sets as Dokka source sets. @@ -48,7 +48,6 @@ abstract class DokkatooKotlinAdapter @Inject constructor( private val objects: ObjectFactory, private val providers: ProviderFactory, ) : Plugin { - override fun apply(project: Project) { logger.info("applied DokkatooKotlinAdapter to ${project.path}") @@ -75,7 +74,7 @@ abstract class DokkatooKotlinAdapter @Inject constructor( val compilationDetailsBuilder = KotlinCompilationDetailsBuilder( providers = providers, objects = objects, - configurations = project.configurations, + kotlinGradlePluginVersion = parseKotlinVersion(project.getKotlinPluginVersion()), projectPath = project.path, ) val allKotlinCompilationDetails: ListProperty = @@ -194,13 +193,14 @@ private data class KotlinCompilationDetails( val compileDependencyFiles: FileCollection, val dependentSourceSetNames: Set, val compilationClasspath: FileCollection, + val defaultSourceSetName: String, ) /** Utility class, encapsulating logic for building [KotlinCompilationDetails] */ private class KotlinCompilationDetailsBuilder( private val objects: ObjectFactory, private val providers: ProviderFactory, - private val configurations: ConfigurationContainer, + private val kotlinGradlePluginVersion: KotlinVersion?, /** Used for logging */ private val projectPath: String, ) { @@ -248,6 +248,7 @@ private class KotlinCompilationDetailsBuilder( compileDependencyFiles = compileDependencyFiles, dependentSourceSetNames = dependentSourceSetNames.toSet(), compilationClasspath = compilationClasspath, + defaultSourceSetName = compilation.defaultSourceSet.name ) } @@ -266,51 +267,25 @@ private class KotlinCompilationDetailsBuilder( private fun collectKotlinCompilationClasspath( compilation: KotlinCompilation<*>, ): FileCollection { - val compilationClasspath = objects.fileCollection() - fun collectConfiguration(named: String) { - configurations.collectIncomingFiles(named = named, collector = compilationClasspath) - - // need to fetch JAVA_RUNTIME files explicitly, because Android Gradle Plugin is weird and - // doesn't seem to register the attributes explicitly on its configurations - @Suppress("UnstableApiUsage") - configurations.collectIncomingFiles(named = named, collector = compilationClasspath) { - withVariantReselection() - attributes { - attribute(USAGE_ATTRIBUTE, objects.named(JAVA_API)) - } - lenient(true) + compilationClasspath.from(compilation.compileDependencyFiles) + + if (kotlinGradlePluginVersion != null && kotlinGradlePluginVersion <= KotlinVersion(1, 9, 255)) { + if (compilation is AbstractKotlinNativeCompilation) { + val konanDistribution = KotlinNativeDistributionAccessor(compilation.target.project) + // KT-61559: In Kotlin 2.0 this will be part of [compilation.compileDependencyFiles] + compilationClasspath.from(konanDistribution.stdlibDir) + compilationClasspath.from(konanDistribution.platformDependencies(compilation.konanTarget)) } } - - val standardConfigurations = buildSet { - addAll(compilation.relatedConfigurationNames) - addAll(compilation.kotlinSourceSets.flatMap { it.relatedConfigurationNames }) - } - - logger.info("[$projectPath] compilation ${compilation.name} has ${standardConfigurations.size} standard configurations $standardConfigurations") - - standardConfigurations.forEach { collectConfiguration(it) } - - if (compilation is AbstractKotlinNativeCompilation) { - // K/N doesn't correctly set task dependencies, the configuration - // `defaultSourceSet.implementationMetadataConfigurationName` - // will trigger a bunch of Gradle warnings about "using file outputs without task dependencies", - // so K/N compilations need to explicitly depend on the compilation tasks - // UPDATE: actually I think is wrong, it's a bug with the K/N 'commonize for IDE' tasks - // see: https://github.com/Kotlin/dokka/issues/2977 - collectConfiguration( - named = compilation.defaultSourceSet.implementationMetadataConfigurationName, -// builtBy = kotlinCompilation.compileKotlinTaskProvider - ) - } - return compilationClasspath } companion object { private fun KotlinCompilation<*>.isMain(): Boolean { return when (this) { + // metadata compilation is considered as 'main' because its outputs is publishable + is KotlinMetadataCompilation<*> -> true is KotlinJvmAndroidCompilation -> androidVariant is LibraryVariant || androidVariant is ApplicationVariant diff --git a/modules/dokkatoo-plugin/src/main/kotlin/internal/KotlinNativeDistributionAccessor.kt b/modules/dokkatoo-plugin/src/main/kotlin/internal/KotlinNativeDistributionAccessor.kt new file mode 100644 index 00000000..575127ad --- /dev/null +++ b/modules/dokkatoo-plugin/src/main/kotlin/internal/KotlinNativeDistributionAccessor.kt @@ -0,0 +1,39 @@ +@file:Suppress("INVISIBLE_REFERENCE") +package dev.adamko.dokkatoo.internal + +import java.io.File +import org.gradle.api.Project +import org.jetbrains.kotlin.commonizer.KonanDistribution +import org.jetbrains.kotlin.commonizer.platformLibsDir +import org.jetbrains.kotlin.commonizer.stdlib +import org.jetbrains.kotlin.compilerRunner.konanHome +import org.jetbrains.kotlin.konan.target.KonanTarget + +/** + * Provides access to the Kotlin/Native distribution components: + * * [stdlibDir] -- stdlib directory + * * [platformDependencies] -- list of directories to platform dependencies + * + * It uses Kotlin Gradle Plugin API that is guaranteed to be present in: + * 1.5 <= kotlinVersion <= 1.9 + * + * It should not be used with Kotlin versions later than 1.9 + */ +internal class KotlinNativeDistributionAccessor( + project: Project +) { + private val konanDistribution = KonanDistribution( + @Suppress("INVISIBLE_MEMBER") + project.konanHome + ) + + val stdlibDir: File = konanDistribution.stdlib + + fun platformDependencies(target: KonanTarget): List = konanDistribution + .platformLibsDir + .resolve(target.name) + .listLibraryFiles() + + private fun File.listLibraryFiles(): List = listFiles().orEmpty() + .filter { it.isDirectory || it.extension == "klib" } +} \ No newline at end of file diff --git a/modules/dokkatoo-plugin/src/main/kotlin/internal/parseKotlinVersion.kt b/modules/dokkatoo-plugin/src/main/kotlin/internal/parseKotlinVersion.kt new file mode 100644 index 00000000..e37d0e3a --- /dev/null +++ b/modules/dokkatoo-plugin/src/main/kotlin/internal/parseKotlinVersion.kt @@ -0,0 +1,23 @@ +package dev.adamko.dokkatoo.internal + +/** + * Accepts a full version string that contains the major, minor + * and patch versions divided by dots, such as "1.7.10". + * + * Does NOT parse and store custom suffixes, so `1.8.20-RC2` + * or `1.8.20-dev-42` will be viewed as `1.8.20`. + */ +internal fun parseKotlinVersion(fullVersionString: String): KotlinVersion? { + val versionParts = fullVersionString + .split(".", "-", limit = 4) + .takeIf { parts -> parts.size >= 3 && parts.subList(0, 3).all { it.isNumeric() } } + ?: return null + + return KotlinVersion( + major = versionParts[0].toInt(), + minor = versionParts[1].toInt(), + patch = versionParts[2].toInt() + ) +} + +private fun String.isNumeric() = this.isNotEmpty() && this.all { it.isDigit() } From bee587d2086983512243a9f4d30e424592f835dc Mon Sep 17 00:00:00 2001 From: Anton Lakotka Date: Tue, 29 Aug 2023 16:21:14 +0200 Subject: [PATCH 02/10] Don't add suppressed source sets to generator task Inputs This prevents from unnecessary dependency resolutions, including compile tasks. Particularly test source sets depend on compilations output of main source sets. And if test source sets are not skipped then Kotlin compilation will be triggered unnecessary. --- .../testExamples/kotlin/KotlinMultiplatformExampleTest.kt | 2 +- .../dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/KotlinMultiplatformExampleTest.kt b/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/KotlinMultiplatformExampleTest.kt index 3548a3a4..d43c9133 100644 --- a/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/KotlinMultiplatformExampleTest.kt +++ b/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/KotlinMultiplatformExampleTest.kt @@ -128,7 +128,7 @@ class KotlinMultiplatformExampleTest : FunSpec({ output shouldContainAll listOf( "> Task :dokkatooGeneratePublicationHtml UP-TO-DATE", "BUILD SUCCESSFUL", - "3 actionable tasks: 3 up-to-date", + "2 actionable tasks: 2 up-to-date", ) withClue("Dokka Generator should not be triggered, so check it doesn't log anything") { output shouldNotContain "Generation completed successfully" diff --git a/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt b/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt index 614a041b..0e0c4f60 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt @@ -98,7 +98,12 @@ constructor( publicationEnabled.convention(true) onlyIf("publication must be enabled") { publicationEnabled.getOrElse(true) } - generator.dokkaSourceSets.addAllLater(providers.provider { dokkatooExtension.dokkatooSourceSets }) + generator.dokkaSourceSets.addAllLater( + providers.provider { + // exclude suppressed source sets to avoid unnecessary dependency resolution for them + dokkatooExtension.dokkatooSourceSets.filterNot { it.suppress.get() } + } + ) generator.dokkaSourceSets.configureDefaults( sourceSetScopeConvention = dokkatooExtension.sourceSetScopeDefault From 0e939ffd2219aebd87b0bab0a82ccb9604f7bebe Mon Sep 17 00:00:00 2001 From: Anton Lakotka Date: Tue, 29 Aug 2023 16:25:25 +0200 Subject: [PATCH 03/10] Bump kotlin version in multiplatform-example project Kotlin 1.9.0 supports Gradle Configuration Cache for Multiplatform projects Dokka generation task now depends on `transform{sourceSetName}DependenciesMetadata` tasks from Kotlin Gradle Plugin that didn't support Gradle Configuration Cache in earlier versions of Kotlin. Making whole gradle build incompatible with configuration cache. --- examples/multiplatform-example/dokkatoo/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/multiplatform-example/dokkatoo/build.gradle.kts b/examples/multiplatform-example/dokkatoo/build.gradle.kts index 3d95fb15..2b7080d9 100644 --- a/examples/multiplatform-example/dokkatoo/build.gradle.kts +++ b/examples/multiplatform-example/dokkatoo/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") version "1.8.22" + kotlin("multiplatform") version "1.9.0" id("dev.adamko.dokkatoo") version "2.0.0-SNAPSHOT" } From 43cca3a490adb57e4e464ba59c6499d52e4301d2 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:24:54 +0200 Subject: [PATCH 04/10] remove unused 'compileDependencyFiles' --- .../src/main/kotlin/adapters/DokkatooKotlinAdapter.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt b/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt index 5ff399a0..1172126b 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt @@ -190,7 +190,6 @@ private data class KotlinCompilationDetails( val kotlinPlatform: KotlinPlatform, val allKotlinSourceSetsNames: Set, val mainCompilation: Boolean, - val compileDependencyFiles: FileCollection, val dependentSourceSetNames: Set, val compilationClasspath: FileCollection, val defaultSourceSetName: String, @@ -231,9 +230,6 @@ private class KotlinCompilationDetailsBuilder( val allKotlinSourceSetsNames = compilation.allKotlinSourceSets.map { it.name } + compilation.defaultSourceSet.name - val compileDependencyFiles = objects.fileCollection() - .from(providers.provider { compilation.compileDependencyFiles }) - val dependentSourceSetNames = compilation.defaultSourceSet.dependsOn.map { it.name } @@ -245,7 +241,6 @@ private class KotlinCompilationDetailsBuilder( kotlinPlatform = KotlinPlatform.fromString(compilation.platformType.name), allKotlinSourceSetsNames = allKotlinSourceSetsNames.toSet(), mainCompilation = compilation.isMain(), - compileDependencyFiles = compileDependencyFiles, dependentSourceSetNames = dependentSourceSetNames.toSet(), compilationClasspath = compilationClasspath, defaultSourceSetName = compilation.defaultSourceSet.name From 8f77c7019836cf1d1f34a593006c79d0be528ea6 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 30 Aug 2023 20:15:20 +0200 Subject: [PATCH 05/10] tidy up GradleTestKitUtils --- .../testFixtures/kotlin/GradleTestKitUtils.kt | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt b/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt index 3d2b38b6..05d91f68 100644 --- a/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt +++ b/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt @@ -56,19 +56,6 @@ class GradleProjectTest( } -///** -// * Load a project from the [GradleProjectTest.dokkaSrcIntegrationTestProjectsDir] -// */ -//fun gradleKtsProjectIntegrationTest( -// testProjectName: String, -// build: GradleProjectTest.() -> Unit, -//): GradleProjectTest = -// GradleProjectTest( -// baseDir = GradleProjectTest.dokkaSrcIntegrationTestProjectsDir, -// testProjectName = testProjectName, -// ).apply(build) - - /** * Builder for testing a Gradle project that uses Kotlin script DSL and creates default * `settings.gradle.kts` and `gradle.properties` files. @@ -85,10 +72,10 @@ fun gradleKtsProjectTest( settingsGradleKts = """ |rootProject.name = "test" | - |@Suppress("UnstableApiUsage") - |dependencyResolutionManagement { + |pluginManagement { | repositories { | mavenCentral() + | gradlePluginPortal() | maven(file("$testMavenRepoRelativePath")) { | mavenContent { | includeGroup("dev.adamko.dokkatoo") @@ -98,10 +85,10 @@ fun gradleKtsProjectTest( | } |} | - |pluginManagement { + |@Suppress("UnstableApiUsage") + |dependencyResolutionManagement { | repositories { | mavenCentral() - | gradlePluginPortal() | maven(file("$testMavenRepoRelativePath")) { | mavenContent { | includeGroup("dev.adamko.dokkatoo") @@ -241,12 +228,16 @@ fun ProjectDirectoryScope.findFiles(matcher: (File) -> Boolean): Sequence /** Set the content of `settings.gradle.kts` */ @delegate:Language("kts") -var ProjectDirectoryScope.settingsGradleKts: String by TestProjectFileDelegate("settings.gradle.kts") +var ProjectDirectoryScope.settingsGradleKts: String by TestProjectFileDelegate( + /* language=text */ "settings.gradle.kts" +) /** Set the content of `build.gradle.kts` */ @delegate:Language("kts") -var ProjectDirectoryScope.buildGradleKts: String by TestProjectFileDelegate("build.gradle.kts") +var ProjectDirectoryScope.buildGradleKts: String by TestProjectFileDelegate( + /* language=text */ "build.gradle.kts" +) /** Set the content of `settings.gradle` */ From 5d7eaf7cc452fc9b9e19b889d9cc6805e7650b99 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 30 Aug 2023 20:15:46 +0200 Subject: [PATCH 06/10] test more Kotlin versions and add K/N targets in KMP functional tests --- .../KotlinMultiplatformFunctionalTest.kt | 187 +++++++++++------- 1 file changed, 121 insertions(+), 66 deletions(-) diff --git a/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt b/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt index 55a6cdf6..965c4b4d 100644 --- a/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt +++ b/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt @@ -13,95 +13,119 @@ import io.kotest.matchers.string.shouldNotContain class KotlinMultiplatformFunctionalTest : FunSpec({ - context("when dokkatoo generates all formats") { - val project = initKotlinMultiplatformProject() - - project.runner - .addArguments( - "clean", - ":dokkatooGeneratePublicationHtml", - "--stacktrace", - ) - .forwardOutput() - .build { - test("expect build is successful") { - output shouldContain "BUILD SUCCESSFUL" - } - } + val kotlinVersionsToTest = listOf( + "1.8.22", + "1.9.10", + "1.9.20-dev-9102", + ) - test("expect all dokka workers are successful") { - project - .findFiles { it.name == "dokka-worker.log" } - .shouldBeSingleton { dokkaWorkerLog -> - dokkaWorkerLog.shouldBeAFile() - dokkaWorkerLog.readText().shouldNotContainAnyOf( - "[ERROR]", - "[WARN]", - ) - } - } + for (kotlinVersion in kotlinVersionsToTest) { + testKotlinMultiplatformProject(kotlinVersion) + } +}) - context("expect HTML site is generated") { - test("with expected HTML files") { - project.projectDir.resolve("build/dokka/html/index.html").shouldBeAFile() - project.projectDir.resolve("build/dokka/html/com/project/hello/Hello.html") - .shouldBeAFile() - } +private fun FunSpec.testKotlinMultiplatformProject( + kotlinVersion: String, +): Unit = context("when dokkatoo generates all formats for Kotlin v$kotlinVersion project") { + val project = initKotlinMultiplatformProject(kotlinVersion) - test("and dokka_parameters.json is generated") { - project.projectDir.resolve("build/dokka/html/dokka_parameters.json") - .shouldBeAFile() + project.runner + .addArguments( + "clean", + ":dokkatooGeneratePublicationHtml", + "--stacktrace", + ) + .forwardOutput() + .build { + test("expect build is successful") { + output shouldContain "BUILD SUCCESSFUL" } + } - test("with element-list") { - project.projectDir.resolve("build/dokka/html/test/package-list").shouldBeAFile() - project.projectDir.resolve("build/dokka/html/test/package-list").toFile().readText() - .sortLines() - .shouldContain( /* language=text */ """ - |${'$'}dokka.format:html-v1 - |${'$'}dokka.linkExtension:html - |${'$'}dokka.location:com.project////PointingToDeclaration/test/com.project/index.html - |${'$'}dokka.location:com.project//goodbye/#kotlinx.serialization.json.JsonObject/PointingToDeclaration/test/com.project/goodbye.html - |${'$'}dokka.location:com.project/Hello///PointingToDeclaration/test/com.project/-hello/index.html - |${'$'}dokka.location:com.project/Hello/Hello/#/PointingToDeclaration/test/com.project/-hello/-hello.html - |${'$'}dokka.location:com.project/Hello/sayHello/#kotlinx.serialization.json.JsonObject/PointingToDeclaration/test/com.project/-hello/say-hello.html - |com.project - """.trimMargin() - ) + test("expect all dokka workers are successful") { + project + .findFiles { it.name == "dokka-worker.log" } + .shouldBeSingleton { dokkaWorkerLog -> + dokkaWorkerLog.shouldBeAFile() + dokkaWorkerLog.readText().shouldNotContainAnyOf( + "[ERROR]", + "[WARN]", + ) } + } + + context("expect HTML site is generated") { + + test("with expected HTML files") { + project.projectDir.resolve("build/dokka/html/index.html").shouldBeAFile() + project.projectDir.resolve("build/dokka/html/com/project/hello/Hello.html") + .shouldBeAFile() + } + + test("and dokka_parameters.json is generated") { + project.projectDir.resolve("build/dokka/html/dokka_parameters.json") + .shouldBeAFile() + } + + test("with element-list") { + project.projectDir.resolve("build/dokka/html/test/package-list").shouldBeAFile() + project.projectDir.resolve("build/dokka/html/test/package-list").toFile().readText() + .sortLines() + .shouldContain( /* language=text */ """ + |${'$'}dokka.format:html-v1 + |${'$'}dokka.linkExtension:html + |${'$'}dokka.location:com.project////PointingToDeclaration/test/com.project/index.html + |${'$'}dokka.location:com.project//goodbye/#kotlinx.serialization.json.JsonObject/PointingToDeclaration/test/com.project/goodbye.html + |${'$'}dokka.location:com.project/Hello///PointingToDeclaration/test/com.project/-hello/index.html + |${'$'}dokka.location:com.project/Hello/Hello/#/PointingToDeclaration/test/com.project/-hello/-hello.html + |${'$'}dokka.location:com.project/Hello/sayHello/#kotlinx.serialization.json.JsonObject/PointingToDeclaration/test/com.project/-hello/say-hello.html + |com.project + """.trimMargin() + ) + } - test("expect no 'unknown class' message in HTML files") { - val htmlFiles = project.projectDir.toFile() - .resolve("build/dokka/html") - .walk() - .filter { it.isFile && it.extension == "html" } + test("expect no 'unknown class' message in HTML files") { + val htmlFiles = project.projectDir.toFile() + .resolve("build/dokka/html") + .walk() + .filter { it.isFile && it.extension == "html" } - htmlFiles.shouldNotBeEmpty() + htmlFiles.shouldNotBeEmpty() - htmlFiles.forEach { htmlFile -> - val relativePath = htmlFile.relativeTo(project.projectDir.toFile()) - withClue("$relativePath should not contain Error class: unknown class") { - htmlFile.useLines { lines -> - lines.shouldForAll { line -> line.shouldNotContain("Error class: unknown class") } - } + htmlFiles.forEach { htmlFile -> + val relativePath = htmlFile.relativeTo(project.projectDir.toFile()) + withClue("$relativePath should not contain Error class: unknown class") { + htmlFile.useLines { lines -> + lines.shouldForAll { line -> line.shouldNotContain("Error class: unknown class") } } } } } } -}) +} private fun initKotlinMultiplatformProject( + kotlinVersion: String, config: GradleProjectTest.() -> Unit = {}, ): GradleProjectTest { - return gradleKtsProjectTest("kotlin-multiplatform-project") { + return gradleKtsProjectTest("kotlin-multiplatform-project-v$kotlinVersion") { + settingsGradleKts = settingsGradleKts.replace( + """ + |pluginManagement { + | repositories { + """.trimMargin(), + """ + |pluginManagement { + | repositories { + | maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap") + """.trimMargin(), + ) settingsGradleKts += """ | |dependencyResolutionManagement { - | | repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) | | repositories { @@ -131,6 +155,32 @@ private fun initKotlinMultiplatformProject( | } | filter { includeGroup("com.yarnpkg") } | } + | maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap") + | + | // workaround for https://youtrack.jetbrains.com/issue/KT-51379 + | exclusiveContent { + | forRepository { + | ivy("https://download.jetbrains.com/kotlin/native/builds") { + | name = "Kotlin Native" + | patternLayout { + | listOf( + | "macos-x86_64", + | "macos-aarch64", + | "osx-x86_64", + | "osx-aarch64", + | "linux-x86_64", + | "windows-x86_64", + | ).forEach { os -> + | listOf("dev", "releases").forEach { stage -> + | artifact("${'$'}stage/[revision]${'$'}os/[artifact]-[revision].[ext]") + | } + | } + | } + | metadataSources { artifact() } + | } + | } + | filter { includeModuleByRegex(".*", ".*kotlin-native-prebuilt.*") } + | } | } |} | @@ -138,7 +188,7 @@ private fun initKotlinMultiplatformProject( buildGradleKts = """ |plugins { - | kotlin("multiplatform") version "1.8.22" + | kotlin("multiplatform") version "$kotlinVersion" | id("dev.adamko.dokkatoo") version "${DokkatooConstants.DOKKATOO_VERSION}" |} | @@ -147,6 +197,11 @@ private fun initKotlinMultiplatformProject( | js(IR) { | browser() | } + | linuxX64() + | macosX64() + | macosArm64() + | iosX64() + | mingwX64() | | sourceSets { | commonMain { From 80728b772afe1f0119dafe9dc45c72e5a08621a9 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 30 Aug 2023 20:46:57 +0200 Subject: [PATCH 07/10] disable Gradle Daemon in Gradle TestKit tests --- .../src/testFixtures/kotlin/GradleTestKitUtils.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt b/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt index 05d91f68..b92159c0 100644 --- a/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt +++ b/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt @@ -145,6 +145,8 @@ fun gradleGroovyProjectTest( gradleProperties = """ |kotlin.mpp.stability.nowarn=true |org.gradle.cache=true + |org.gradle.daemon=false + | """.trimMargin() build() From cb3494f6413a47b74ea39d79742a218154b51b56 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 30 Aug 2023 20:49:29 +0200 Subject: [PATCH 08/10] fix Kotlin Native tests in KotlinMultiplatformFunctionalTest --- .../KotlinMultiplatformFunctionalTest.kt | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt b/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt index 965c4b4d..403cf225 100644 --- a/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt +++ b/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt @@ -172,7 +172,7 @@ private fun initKotlinMultiplatformProject( | "windows-x86_64", | ).forEach { os -> | listOf("dev", "releases").forEach { stage -> - | artifact("${'$'}stage/[revision]${'$'}os/[artifact]-[revision].[ext]") + artifact("${'$'}stage/[revision]/${'$'}os/[artifact]-[revision].[ext]") | } | } | } @@ -267,34 +267,30 @@ private fun initKotlinMultiplatformProject( ) } - dir("src/jvmMain/kotlin/") { - createKotlinFile( - "goodbyeJvm.kt", - """ - |package com.project - | - |import kotlinx.serialization.json.JsonObject - | - |/** JVM implementation - prints `goodbye` and [json] to the console */ - |actual fun goodbye(json: JsonObject) = println("[JVM] goodbye ${'$'}json") - | - """.trimMargin() - ) - } + listOf( + "jvm", + "js", + "linuxX64", + "macosX64", + "macosArm64", + "iosX64", + "mingwX64", + ).forEach { target -> - dir("src/jsMain/kotlin/") { - createKotlinFile( - "goodbyeJs.kt", - """ + dir("src/${target}Main/kotlin/") { + createKotlinFile( + "goodbye_${target}.kt", + """ |package com.project | |import kotlinx.serialization.json.JsonObject | - |/** JS implementation - prints `goodbye` and [json] to the console */ - |actual fun goodbye(json: JsonObject) = println("[JS] goodbye ${'$'}json") + |/** $target implementation - prints `goodbye` and [json] to the console */ + |actual fun goodbye(json: JsonObject) = println("[target] goodbye ${'$'}json") | """.trimMargin() - ) + ) + } } config() From 76351611897bbed1108a6e2ef99b1a85317921d9 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 30 Aug 2023 20:53:00 +0200 Subject: [PATCH 09/10] refactoring & tidying - use KotlinToolingVersion to get the Kotlin version - rename 'isMain' properties/functions to 'isPublished', because it makes a bit more sense - fetch the konanHome prop by using extraProperties --- .../src/main/kotlin/DokkatooBasePlugin.kt | 26 +++-- .../src/main/kotlin/DokkatooExtension.kt | 11 ++ .../kotlin/adapters/DokkatooKotlinAdapter.kt | 102 +++++++++++++----- .../KotlinNativeDistributionAccessor.kt | 39 ------- .../kotlin/internal/parseKotlinVersion.kt | 23 ---- 5 files changed, 105 insertions(+), 96 deletions(-) delete mode 100644 modules/dokkatoo-plugin/src/main/kotlin/internal/KotlinNativeDistributionAccessor.kt delete mode 100644 modules/dokkatoo-plugin/src/main/kotlin/internal/parseKotlinVersion.kt diff --git a/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt b/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt index 0e0c4f60..4e1b850d 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt @@ -23,6 +23,7 @@ import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.TaskContainer import org.gradle.kotlin.dsl.* @@ -98,12 +99,12 @@ constructor( publicationEnabled.convention(true) onlyIf("publication must be enabled") { publicationEnabled.getOrElse(true) } - generator.dokkaSourceSets.addAllLater( - providers.provider { - // exclude suppressed source sets to avoid unnecessary dependency resolution for them - dokkatooExtension.dokkatooSourceSets.filterNot { it.suppress.get() } - } - ) + generator.dokkaSourceSets.addAllLater( + providers.provider { + // exclude suppressed source sets as early as possible, to avoid unnecessary dependency resolution + dokkatooExtension.dokkatooSourceSets.filterNot { it.suppress.get() } + } + ) generator.dokkaSourceSets.configureDefaults( sourceSetScopeConvention = dokkatooExtension.sourceSetScopeDefault @@ -120,6 +121,15 @@ constructor( moduleName.convention(providers.provider { project.name }) moduleVersion.convention(providers.provider { project.version.toString() }) modulePath.convention(project.pathAsFilePath()) + konanHome.convention( + providers + .provider { + // konanHome is set into in extraProperties: + // https://github.com/JetBrains/kotlin/blob/v1.9.0/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt#L35-L38 + project.extensions.extraProperties.get("konanHome") as? String? + } + .map { File(it) } + ) sourceSetScopeDefault.convention(project.path) dokkatooPublicationDirectory.convention(layout.buildDirectory.dir("dokka")) @@ -259,6 +269,10 @@ constructor( private fun RegularFileProperty.convention(file: File): RegularFileProperty = convention(objects.fileProperty().fileValue(file)) + // workaround for https://github.com/gradle/gradle/issues/23708 + private fun RegularFileProperty.convention(file: Provider): RegularFileProperty = + convention(objects.fileProperty().fileProvider(file)) + companion object { const val EXTENSION_NAME = "dokkatoo" diff --git a/modules/dokkatoo-plugin/src/main/kotlin/DokkatooExtension.kt b/modules/dokkatoo-plugin/src/main/kotlin/DokkatooExtension.kt index 4a80549a..a64be5d2 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/DokkatooExtension.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/DokkatooExtension.kt @@ -6,6 +6,7 @@ import dev.adamko.dokkatoo.internal.* import java.io.Serializable import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory import org.gradle.api.plugins.ExtensionAware import org.gradle.api.provider.Property @@ -45,6 +46,16 @@ constructor( */ abstract val sourceSetScopeDefault: Property + /** + * The Konan home directory, which contains libraries for Kotlin/Native development. + * + * This is only required as a workaround to fetch the compile-time dependencies in Kotlin/Native + * projects with a version below 2.0. + */ + // This property should be removed when Dokkatoo only supports KGP 2 or higher. + @DokkatooInternalApi + abstract val konanHome: RegularFileProperty + /** * Configuration for creating Dokka Publications. * diff --git a/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt b/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt index 1172126b..bc18b92a 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt @@ -4,14 +4,14 @@ import com.android.build.gradle.api.ApplicationVariant import com.android.build.gradle.api.LibraryVariant import dev.adamko.dokkatoo.DokkatooBasePlugin import dev.adamko.dokkatoo.DokkatooExtension +import dev.adamko.dokkatoo.adapters.DokkatooKotlinAdapter.Companion.currentKotlinToolingVersion import dev.adamko.dokkatoo.dokka.parameters.DokkaSourceSetIdSpec import dev.adamko.dokkatoo.dokka.parameters.DokkaSourceSetIdSpec.Companion.dokkaSourceSetIdSpec import dev.adamko.dokkatoo.dokka.parameters.DokkaSourceSetSpec import dev.adamko.dokkatoo.dokka.parameters.KotlinPlatform import dev.adamko.dokkatoo.internal.DokkatooInternalApi -import dev.adamko.dokkatoo.internal.KotlinNativeDistributionAccessor import dev.adamko.dokkatoo.internal.not -import dev.adamko.dokkatoo.internal.parseKotlinVersion +import java.io.File import javax.inject.Inject import org.gradle.api.Named import org.gradle.api.NamedDomainObjectContainer @@ -27,6 +27,9 @@ import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.api.provider.SetProperty import org.gradle.kotlin.dsl.* +import org.jetbrains.kotlin.commonizer.KonanDistribution +import org.jetbrains.kotlin.commonizer.platformLibsDir +import org.jetbrains.kotlin.commonizer.stdlib import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension @@ -37,6 +40,8 @@ import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinNativeCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataCompilation +import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion /** * The [DokkatooKotlinAdapter] plugin will automatically register Kotlin source sets as Dokka source sets. @@ -48,6 +53,7 @@ abstract class DokkatooKotlinAdapter @Inject constructor( private val objects: ObjectFactory, private val providers: ProviderFactory, ) : Plugin { + override fun apply(project: Project) { logger.info("applied DokkatooKotlinAdapter to ${project.path}") @@ -74,8 +80,7 @@ abstract class DokkatooKotlinAdapter @Inject constructor( val compilationDetailsBuilder = KotlinCompilationDetailsBuilder( providers = providers, objects = objects, - kotlinGradlePluginVersion = parseKotlinVersion(project.getKotlinPluginVersion()), - projectPath = project.path, + konanHome = dokkatooExtension.konanHome.asFile, ) val allKotlinCompilationDetails: ListProperty = compilationDetailsBuilder.createCompilationDetails( @@ -126,7 +131,7 @@ abstract class DokkatooKotlinAdapter @Inject constructor( val kssClasspath = determineClasspath(details) register(details.name) dss@{ - suppress.set(!details.isMainSourceSet()) + suppress.set(!details.isPublishedSourceSet()) sourceRoots.from(details.sourceDirectories) classpath.from(kssClasspath) analysisPlatform.set(kssPlatform) @@ -156,9 +161,9 @@ abstract class DokkatooKotlinAdapter @Inject constructor( @DokkatooInternalApi companion object { - private val logger = Logging.getLogger(DokkatooKotlinAdapter::class.java) + /** Try and get [KotlinProjectExtension], or `null` if it's not present */ private fun ExtensionContainer.findKotlinExtension(): KotlinProjectExtension? = try { findByType() @@ -174,6 +179,13 @@ abstract class DokkatooKotlinAdapter @Inject constructor( else -> throw e } } + + /** Get the version of the Kotlin Gradle Plugin currently used to compile the project */ + // Must be lazy, else tests fail (because the KGP plugin isn't accessible) + internal val currentKotlinToolingVersion: KotlinToolingVersion by lazy { + val kgpVersion = getKotlinPluginVersion(logger) + KotlinToolingVersion(kgpVersion) + } } } @@ -189,7 +201,7 @@ private data class KotlinCompilationDetails( val target: String, val kotlinPlatform: KotlinPlatform, val allKotlinSourceSetsNames: Set, - val mainCompilation: Boolean, + val publishedCompilation: Boolean, val dependentSourceSetNames: Set, val compilationClasspath: FileCollection, val defaultSourceSetName: String, @@ -199,11 +211,8 @@ private data class KotlinCompilationDetails( private class KotlinCompilationDetailsBuilder( private val objects: ObjectFactory, private val providers: ProviderFactory, - private val kotlinGradlePluginVersion: KotlinVersion?, - /** Used for logging */ - private val projectPath: String, + private val konanHome: Provider, ) { - private val logger = Logging.getLogger(KotlinCompilationDetails::class.java) fun createCompilationDetails( kotlinProjectExtension: KotlinProjectExtension, @@ -240,7 +249,7 @@ private class KotlinCompilationDetailsBuilder( target = compilation.target.name, kotlinPlatform = KotlinPlatform.fromString(compilation.platformType.name), allKotlinSourceSetsNames = allKotlinSourceSetsNames.toSet(), - mainCompilation = compilation.isMain(), + publishedCompilation = compilation.isPublished(), dependentSourceSetNames = dependentSourceSetNames.toSet(), compilationClasspath = compilationClasspath, defaultSourceSetName = compilation.defaultSourceSet.name @@ -263,28 +272,65 @@ private class KotlinCompilationDetailsBuilder( compilation: KotlinCompilation<*>, ): FileCollection { val compilationClasspath = objects.fileCollection() - compilationClasspath.from(compilation.compileDependencyFiles) - - if (kotlinGradlePluginVersion != null && kotlinGradlePluginVersion <= KotlinVersion(1, 9, 255)) { - if (compilation is AbstractKotlinNativeCompilation) { - val konanDistribution = KotlinNativeDistributionAccessor(compilation.target.project) - // KT-61559: In Kotlin 2.0 this will be part of [compilation.compileDependencyFiles] - compilationClasspath.from(konanDistribution.stdlibDir) - compilationClasspath.from(konanDistribution.platformDependencies(compilation.konanTarget)) - } + + // collect dependency files from 'regular' Kotlin compilations + compilationClasspath.from(providers.provider { compilation.compileDependencyFiles }) + + // apply workaround for Kotlin/Native, which will be fixed in Kotlin 2.0 + // (see KT-61559: K/N dependencies will be part of `compilation.compileDependencyFiles`) + if ( + currentKotlinToolingVersion < KotlinToolingVersion("2.0.0") + && + compilation is AbstractKotlinNativeCompilation + ) { + compilationClasspath.from( + konanHome.map { konanHome -> + kotlinNativeDependencies(konanHome, compilation.konanTarget) + } + ) } + return compilationClasspath } + private fun kotlinNativeDependencies(konanHome: File, target: KonanTarget): FileCollection { + val konanDistribution = KonanDistribution(konanHome) + + val dependencies = objects.fileCollection() + + dependencies.from(konanDistribution.stdlib) + + // Konan library files for a specific target + dependencies.from( + konanDistribution.platformLibsDir + .resolve(target.name) + .listFiles() + .orEmpty() + .filter { it.isDirectory || it.extension == "klib" } + ) + + return dependencies + } + companion object { - private fun KotlinCompilation<*>.isMain(): Boolean { + + /** + * Determine if a [KotlinCompilation] is 'publishable', and so should be enabled by default + * when creating a Dokka publication. + * + * Typically, 'main' compilations are publishable and 'test' compilations should be suppressed. + * This can be overridden manually, though. + * + * @see DokkaSourceSetSpec.suppress + */ + private fun KotlinCompilation<*>.isPublished(): Boolean { return when (this) { - // metadata compilation is considered as 'main' because its outputs is publishable is KotlinMetadataCompilation<*> -> true - is KotlinJvmAndroidCompilation -> + + is KotlinJvmAndroidCompilation -> androidVariant is LibraryVariant || androidVariant is ApplicationVariant - else -> + else -> name == MAIN_COMPILATION_NAME } } @@ -310,10 +356,10 @@ private abstract class KotlinSourceSetDetails @Inject constructor( /** The specific compilations used to build this source set */ abstract val compilations: ListProperty - /** Estimate if this Kotlin source set are 'main' sources (as opposed to 'test' sources). */ - fun isMainSourceSet(): Provider = + /** Estimate if this Kotlin source set contains 'published' sources */ + fun isPublishedSourceSet(): Provider = compilations.map { values -> - values.any { it.mainCompilation } + values.any { it.publishedCompilation } } override fun getName(): String = named diff --git a/modules/dokkatoo-plugin/src/main/kotlin/internal/KotlinNativeDistributionAccessor.kt b/modules/dokkatoo-plugin/src/main/kotlin/internal/KotlinNativeDistributionAccessor.kt deleted file mode 100644 index 575127ad..00000000 --- a/modules/dokkatoo-plugin/src/main/kotlin/internal/KotlinNativeDistributionAccessor.kt +++ /dev/null @@ -1,39 +0,0 @@ -@file:Suppress("INVISIBLE_REFERENCE") -package dev.adamko.dokkatoo.internal - -import java.io.File -import org.gradle.api.Project -import org.jetbrains.kotlin.commonizer.KonanDistribution -import org.jetbrains.kotlin.commonizer.platformLibsDir -import org.jetbrains.kotlin.commonizer.stdlib -import org.jetbrains.kotlin.compilerRunner.konanHome -import org.jetbrains.kotlin.konan.target.KonanTarget - -/** - * Provides access to the Kotlin/Native distribution components: - * * [stdlibDir] -- stdlib directory - * * [platformDependencies] -- list of directories to platform dependencies - * - * It uses Kotlin Gradle Plugin API that is guaranteed to be present in: - * 1.5 <= kotlinVersion <= 1.9 - * - * It should not be used with Kotlin versions later than 1.9 - */ -internal class KotlinNativeDistributionAccessor( - project: Project -) { - private val konanDistribution = KonanDistribution( - @Suppress("INVISIBLE_MEMBER") - project.konanHome - ) - - val stdlibDir: File = konanDistribution.stdlib - - fun platformDependencies(target: KonanTarget): List = konanDistribution - .platformLibsDir - .resolve(target.name) - .listLibraryFiles() - - private fun File.listLibraryFiles(): List = listFiles().orEmpty() - .filter { it.isDirectory || it.extension == "klib" } -} \ No newline at end of file diff --git a/modules/dokkatoo-plugin/src/main/kotlin/internal/parseKotlinVersion.kt b/modules/dokkatoo-plugin/src/main/kotlin/internal/parseKotlinVersion.kt deleted file mode 100644 index e37d0e3a..00000000 --- a/modules/dokkatoo-plugin/src/main/kotlin/internal/parseKotlinVersion.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.adamko.dokkatoo.internal - -/** - * Accepts a full version string that contains the major, minor - * and patch versions divided by dots, such as "1.7.10". - * - * Does NOT parse and store custom suffixes, so `1.8.20-RC2` - * or `1.8.20-dev-42` will be viewed as `1.8.20`. - */ -internal fun parseKotlinVersion(fullVersionString: String): KotlinVersion? { - val versionParts = fullVersionString - .split(".", "-", limit = 4) - .takeIf { parts -> parts.size >= 3 && parts.subList(0, 3).all { it.isNumeric() } } - ?: return null - - return KotlinVersion( - major = versionParts[0].toInt(), - minor = versionParts[1].toInt(), - patch = versionParts[2].toInt() - ) -} - -private fun String.isNumeric() = this.isNotEmpty() && this.all { it.isDigit() } From 0ac98a8990975a538af4aa7d65a4525ab675e4fd Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:11:44 +0200 Subject: [PATCH 10/10] KotlinMultiplatformFunctionalTest moved to a separate PR because GitHub Actions doesn't like them (OOM), so I'll deal with them in https://github.com/adamko-dev/dokkatoo/pull/115 --- .../testFixtures/kotlin/GradleTestKitUtils.kt | 31 ++- .../KotlinMultiplatformFunctionalTest.kt | 225 +++++++----------- 2 files changed, 106 insertions(+), 150 deletions(-) diff --git a/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt b/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt index b92159c0..3d2b38b6 100644 --- a/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt +++ b/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt @@ -56,6 +56,19 @@ class GradleProjectTest( } +///** +// * Load a project from the [GradleProjectTest.dokkaSrcIntegrationTestProjectsDir] +// */ +//fun gradleKtsProjectIntegrationTest( +// testProjectName: String, +// build: GradleProjectTest.() -> Unit, +//): GradleProjectTest = +// GradleProjectTest( +// baseDir = GradleProjectTest.dokkaSrcIntegrationTestProjectsDir, +// testProjectName = testProjectName, +// ).apply(build) + + /** * Builder for testing a Gradle project that uses Kotlin script DSL and creates default * `settings.gradle.kts` and `gradle.properties` files. @@ -72,10 +85,10 @@ fun gradleKtsProjectTest( settingsGradleKts = """ |rootProject.name = "test" | - |pluginManagement { + |@Suppress("UnstableApiUsage") + |dependencyResolutionManagement { | repositories { | mavenCentral() - | gradlePluginPortal() | maven(file("$testMavenRepoRelativePath")) { | mavenContent { | includeGroup("dev.adamko.dokkatoo") @@ -85,10 +98,10 @@ fun gradleKtsProjectTest( | } |} | - |@Suppress("UnstableApiUsage") - |dependencyResolutionManagement { + |pluginManagement { | repositories { | mavenCentral() + | gradlePluginPortal() | maven(file("$testMavenRepoRelativePath")) { | mavenContent { | includeGroup("dev.adamko.dokkatoo") @@ -145,8 +158,6 @@ fun gradleGroovyProjectTest( gradleProperties = """ |kotlin.mpp.stability.nowarn=true |org.gradle.cache=true - |org.gradle.daemon=false - | """.trimMargin() build() @@ -230,16 +241,12 @@ fun ProjectDirectoryScope.findFiles(matcher: (File) -> Boolean): Sequence /** Set the content of `settings.gradle.kts` */ @delegate:Language("kts") -var ProjectDirectoryScope.settingsGradleKts: String by TestProjectFileDelegate( - /* language=text */ "settings.gradle.kts" -) +var ProjectDirectoryScope.settingsGradleKts: String by TestProjectFileDelegate("settings.gradle.kts") /** Set the content of `build.gradle.kts` */ @delegate:Language("kts") -var ProjectDirectoryScope.buildGradleKts: String by TestProjectFileDelegate( - /* language=text */ "build.gradle.kts" -) +var ProjectDirectoryScope.buildGradleKts: String by TestProjectFileDelegate("build.gradle.kts") /** Set the content of `settings.gradle` */ diff --git a/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt b/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt index 403cf225..55a6cdf6 100644 --- a/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt +++ b/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt @@ -13,119 +13,95 @@ import io.kotest.matchers.string.shouldNotContain class KotlinMultiplatformFunctionalTest : FunSpec({ - val kotlinVersionsToTest = listOf( - "1.8.22", - "1.9.10", - "1.9.20-dev-9102", - ) - - for (kotlinVersion in kotlinVersionsToTest) { - testKotlinMultiplatformProject(kotlinVersion) - } -}) + context("when dokkatoo generates all formats") { + val project = initKotlinMultiplatformProject() + + project.runner + .addArguments( + "clean", + ":dokkatooGeneratePublicationHtml", + "--stacktrace", + ) + .forwardOutput() + .build { + test("expect build is successful") { + output shouldContain "BUILD SUCCESSFUL" + } + } + test("expect all dokka workers are successful") { + project + .findFiles { it.name == "dokka-worker.log" } + .shouldBeSingleton { dokkaWorkerLog -> + dokkaWorkerLog.shouldBeAFile() + dokkaWorkerLog.readText().shouldNotContainAnyOf( + "[ERROR]", + "[WARN]", + ) + } + } -private fun FunSpec.testKotlinMultiplatformProject( - kotlinVersion: String, -): Unit = context("when dokkatoo generates all formats for Kotlin v$kotlinVersion project") { - val project = initKotlinMultiplatformProject(kotlinVersion) + context("expect HTML site is generated") { - project.runner - .addArguments( - "clean", - ":dokkatooGeneratePublicationHtml", - "--stacktrace", - ) - .forwardOutput() - .build { - test("expect build is successful") { - output shouldContain "BUILD SUCCESSFUL" + test("with expected HTML files") { + project.projectDir.resolve("build/dokka/html/index.html").shouldBeAFile() + project.projectDir.resolve("build/dokka/html/com/project/hello/Hello.html") + .shouldBeAFile() } - } - test("expect all dokka workers are successful") { - project - .findFiles { it.name == "dokka-worker.log" } - .shouldBeSingleton { dokkaWorkerLog -> - dokkaWorkerLog.shouldBeAFile() - dokkaWorkerLog.readText().shouldNotContainAnyOf( - "[ERROR]", - "[WARN]", - ) + test("and dokka_parameters.json is generated") { + project.projectDir.resolve("build/dokka/html/dokka_parameters.json") + .shouldBeAFile() } - } - - context("expect HTML site is generated") { - test("with expected HTML files") { - project.projectDir.resolve("build/dokka/html/index.html").shouldBeAFile() - project.projectDir.resolve("build/dokka/html/com/project/hello/Hello.html") - .shouldBeAFile() - } - - test("and dokka_parameters.json is generated") { - project.projectDir.resolve("build/dokka/html/dokka_parameters.json") - .shouldBeAFile() - } - - test("with element-list") { - project.projectDir.resolve("build/dokka/html/test/package-list").shouldBeAFile() - project.projectDir.resolve("build/dokka/html/test/package-list").toFile().readText() - .sortLines() - .shouldContain( /* language=text */ """ - |${'$'}dokka.format:html-v1 - |${'$'}dokka.linkExtension:html - |${'$'}dokka.location:com.project////PointingToDeclaration/test/com.project/index.html - |${'$'}dokka.location:com.project//goodbye/#kotlinx.serialization.json.JsonObject/PointingToDeclaration/test/com.project/goodbye.html - |${'$'}dokka.location:com.project/Hello///PointingToDeclaration/test/com.project/-hello/index.html - |${'$'}dokka.location:com.project/Hello/Hello/#/PointingToDeclaration/test/com.project/-hello/-hello.html - |${'$'}dokka.location:com.project/Hello/sayHello/#kotlinx.serialization.json.JsonObject/PointingToDeclaration/test/com.project/-hello/say-hello.html - |com.project - """.trimMargin() - ) - } + test("with element-list") { + project.projectDir.resolve("build/dokka/html/test/package-list").shouldBeAFile() + project.projectDir.resolve("build/dokka/html/test/package-list").toFile().readText() + .sortLines() + .shouldContain( /* language=text */ """ + |${'$'}dokka.format:html-v1 + |${'$'}dokka.linkExtension:html + |${'$'}dokka.location:com.project////PointingToDeclaration/test/com.project/index.html + |${'$'}dokka.location:com.project//goodbye/#kotlinx.serialization.json.JsonObject/PointingToDeclaration/test/com.project/goodbye.html + |${'$'}dokka.location:com.project/Hello///PointingToDeclaration/test/com.project/-hello/index.html + |${'$'}dokka.location:com.project/Hello/Hello/#/PointingToDeclaration/test/com.project/-hello/-hello.html + |${'$'}dokka.location:com.project/Hello/sayHello/#kotlinx.serialization.json.JsonObject/PointingToDeclaration/test/com.project/-hello/say-hello.html + |com.project + """.trimMargin() + ) + } - test("expect no 'unknown class' message in HTML files") { - val htmlFiles = project.projectDir.toFile() - .resolve("build/dokka/html") - .walk() - .filter { it.isFile && it.extension == "html" } + test("expect no 'unknown class' message in HTML files") { + val htmlFiles = project.projectDir.toFile() + .resolve("build/dokka/html") + .walk() + .filter { it.isFile && it.extension == "html" } - htmlFiles.shouldNotBeEmpty() + htmlFiles.shouldNotBeEmpty() - htmlFiles.forEach { htmlFile -> - val relativePath = htmlFile.relativeTo(project.projectDir.toFile()) - withClue("$relativePath should not contain Error class: unknown class") { - htmlFile.useLines { lines -> - lines.shouldForAll { line -> line.shouldNotContain("Error class: unknown class") } + htmlFiles.forEach { htmlFile -> + val relativePath = htmlFile.relativeTo(project.projectDir.toFile()) + withClue("$relativePath should not contain Error class: unknown class") { + htmlFile.useLines { lines -> + lines.shouldForAll { line -> line.shouldNotContain("Error class: unknown class") } + } } } } } } -} +}) private fun initKotlinMultiplatformProject( - kotlinVersion: String, config: GradleProjectTest.() -> Unit = {}, ): GradleProjectTest { - return gradleKtsProjectTest("kotlin-multiplatform-project-v$kotlinVersion") { + return gradleKtsProjectTest("kotlin-multiplatform-project") { - settingsGradleKts = settingsGradleKts.replace( - """ - |pluginManagement { - | repositories { - """.trimMargin(), - """ - |pluginManagement { - | repositories { - | maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap") - """.trimMargin(), - ) settingsGradleKts += """ | |dependencyResolutionManagement { + | | repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) | | repositories { @@ -155,32 +131,6 @@ private fun initKotlinMultiplatformProject( | } | filter { includeGroup("com.yarnpkg") } | } - | maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap") - | - | // workaround for https://youtrack.jetbrains.com/issue/KT-51379 - | exclusiveContent { - | forRepository { - | ivy("https://download.jetbrains.com/kotlin/native/builds") { - | name = "Kotlin Native" - | patternLayout { - | listOf( - | "macos-x86_64", - | "macos-aarch64", - | "osx-x86_64", - | "osx-aarch64", - | "linux-x86_64", - | "windows-x86_64", - | ).forEach { os -> - | listOf("dev", "releases").forEach { stage -> - artifact("${'$'}stage/[revision]/${'$'}os/[artifact]-[revision].[ext]") - | } - | } - | } - | metadataSources { artifact() } - | } - | } - | filter { includeModuleByRegex(".*", ".*kotlin-native-prebuilt.*") } - | } | } |} | @@ -188,7 +138,7 @@ private fun initKotlinMultiplatformProject( buildGradleKts = """ |plugins { - | kotlin("multiplatform") version "$kotlinVersion" + | kotlin("multiplatform") version "1.8.22" | id("dev.adamko.dokkatoo") version "${DokkatooConstants.DOKKATOO_VERSION}" |} | @@ -197,11 +147,6 @@ private fun initKotlinMultiplatformProject( | js(IR) { | browser() | } - | linuxX64() - | macosX64() - | macosArm64() - | iosX64() - | mingwX64() | | sourceSets { | commonMain { @@ -267,30 +212,34 @@ private fun initKotlinMultiplatformProject( ) } - listOf( - "jvm", - "js", - "linuxX64", - "macosX64", - "macosArm64", - "iosX64", - "mingwX64", - ).forEach { target -> + dir("src/jvmMain/kotlin/") { + createKotlinFile( + "goodbyeJvm.kt", + """ + |package com.project + | + |import kotlinx.serialization.json.JsonObject + | + |/** JVM implementation - prints `goodbye` and [json] to the console */ + |actual fun goodbye(json: JsonObject) = println("[JVM] goodbye ${'$'}json") + | + """.trimMargin() + ) + } - dir("src/${target}Main/kotlin/") { - createKotlinFile( - "goodbye_${target}.kt", - """ + dir("src/jsMain/kotlin/") { + createKotlinFile( + "goodbyeJs.kt", + """ |package com.project | |import kotlinx.serialization.json.JsonObject | - |/** $target implementation - prints `goodbye` and [json] to the console */ - |actual fun goodbye(json: JsonObject) = println("[target] goodbye ${'$'}json") + |/** JS implementation - prints `goodbye` and [json] to the console */ + |actual fun goodbye(json: JsonObject) = println("[JS] goodbye ${'$'}json") | """.trimMargin() - ) - } + ) } config()