aboutsummaryrefslogtreecommitdiff
path: root/dokka-runners
diff options
context:
space:
mode:
authorAdam <897017+aSemy@users.noreply.github.com>2023-10-20 00:39:12 +1300
committerGitHub <noreply@github.com>2023-10-19 13:39:12 +0200
commit35d15601f2d129a7d3db67dd9e2f4c41c87ef083 (patch)
treef9098cb5b79fc31b4a393347f5cebcf9d87dd139 /dokka-runners
parent8016c1face1283952e228aee348487bf0421ab90 (diff)
downloaddokka-35d15601f2d129a7d3db67dd9e2f4c41c87ef083.tar.gz
dokka-35d15601f2d129a7d3db67dd9e2f4c41c87ef083.tar.bz2
dokka-35d15601f2d129a7d3db67dd9e2f4c41c87ef083.zip
Contribute Dokkatoo (#3188)
Diffstat (limited to 'dokka-runners')
-rw-r--r--dokka-runners/dokkatoo/.gitattributes51
-rw-r--r--dokka-runners/dokkatoo/.gitignore71
-rw-r--r--dokka-runners/dokkatoo/build.gradle.kts47
-rw-r--r--dokka-runners/dokkatoo/buildSrc/build.gradle.kts19
-rw-r--r--dokka-runners/dokkatoo/buildSrc/settings.gradle.kts25
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/android-setup.gradle.kts78
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/base.gradle.kts155
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokka-source-downloader.gradle.kts68
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokkatoo-example-projects-base.gradle.kts27
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokkatoo-example-projects.gradle.kts160
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/gradle-plugin-variants.gradle.kts44
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/java-base.gradle.kts19
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/kotlin-gradle-plugin.gradle.kts37
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publish-test.gradle.kts93
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts137
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkaSourceDownloaderSettings.kt13
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkaTemplateProjectSettings.kt96
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkatooExampleProjectsSettings.kt62
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishTestSettings.kt19
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishingSettings.kt68
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/tasks/SetupDokkaProjects.kt73
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/tasks/UpdateDokkatooExampleProjects.kt49
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt118
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/intellij.kt45
-rw-r--r--dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/strings.kt26
-rw-r--r--dokka-runners/dokkatoo/devOps/release.main.kts415
-rw-r--r--dokka-runners/dokkatoo/examples/.gitignore10
-rw-r--r--dokka-runners/dokkatoo/examples/README.md18
-rw-r--r--dokka-runners/dokkatoo/examples/build.gradle.kts48
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokka/README.md17
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokka/build.gradle.kts35
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokka/demo.pngbin0 -> 77918 bytes
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokka/ktor-logo.pngbin0 -> 179624 bytes
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokka/logo-styles.css20
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokka/settings.gradle.kts1
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokka/src/main/kotlin/demo/HelloWorld.kt20
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/build.gradle.kts18
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/ktor-logo.pngbin0 -> 179624 bytes
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/logo-styles.css20
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/src/main/kotlin/demo/HelloWorld.kt20
-rw-r--r--dokka-runners/dokkatoo/examples/gradle-example/dokka/Module.md7
-rw-r--r--dokka-runners/dokkatoo/examples/gradle-example/dokka/README.md22
-rw-r--r--dokka-runners/dokkatoo/examples/gradle-example/dokka/build.gradle.kts37
-rw-r--r--dokka-runners/dokkatoo/examples/gradle-example/dokka/demo.pngbin0 -> 71039 bytes
-rw-r--r--dokka-runners/dokkatoo/examples/gradle-example/dokka/settings.gradle.kts1
-rw-r--r--dokka-runners/dokkatoo/examples/gradle-example/dokka/src/main/kotlin/demo/HelloWorld.kt20
-rw-r--r--dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/Module.md7
-rw-r--r--dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/build.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/src/main/kotlin/demo/HelloWorld.kt20
-rw-r--r--dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/README.md19
-rw-r--r--dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/build.gradle.kts21
-rw-r--r--dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/demo.pngbin0 -> 101974 bytes
-rw-r--r--dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/settings.gradle.kts1
-rw-r--r--dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/src/main/kotlin/demo/HelloWorld.kt20
-rw-r--r--dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/examples/library-publishing-example/dokka/README.md41
-rw-r--r--dokka-runners/dokkatoo/examples/library-publishing-example/dokka/build.gradle.kts39
-rw-r--r--dokka-runners/dokkatoo/examples/library-publishing-example/dokka/settings.gradle.kts1
-rw-r--r--dokka-runners/dokkatoo/examples/library-publishing-example/dokka/src/main/kotlin/demo/HelloWorld.kt20
-rw-r--r--dokka-runners/dokkatoo/examples/library-publishing-example/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/README.md25
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/build.gradle.kts5
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/demo.pngbin0 -> 93395 bytes
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/build.gradle.kts38
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/ModuleA.md5
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/build.gradle.kts16
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/ModuleB.md5
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/build.gradle.kts16
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokka/settings.gradle.kts15
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/build.gradle.kts8
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/settings.gradle.kts21
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/src/main/kotlin/dokka-convention.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/build.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/ModuleA.md5
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/build.gradle.kts21
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/ModuleB.md5
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/build.gradle.kts21
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/settings.gradle.kts21
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/README.md29
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/build.gradle.kts42
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/demo.pngbin0 -> 183500 bytes
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/settings.gradle.kts2
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/CommonCoroutineExtensions.kt15
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/CommonDateUtils.kt14
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/common/Foo.kt7
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/customJdk9/kotlin/org/kotlintest/jdk9/CustomSourceSetFile.kt11
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsCoroutineExtensions.kt11
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsDateUtils.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsFunctions.kt18
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JavaAnnotation.java19
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmCoroutineExtensions.kt11
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmDateUtils.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmFunctions.kt35
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/CInterop.kt15
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/LinuxCoroutineExtensions.kt11
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/LinuxDateUtils.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/macosMain/kotlin/org/kotlintestmpp/MacOsCoroutineExtensions.kt11
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/macosMain/kotlin/org/kotlintestmpp/MacOsDateUtils.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/build.gradle.kts39
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/CommonCoroutineExtensions.kt15
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/CommonDateUtils.kt14
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/common/Foo.kt7
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/customJdk9/kotlin/org/kotlintest/jdk9/CustomSourceSetFile.kt11
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsCoroutineExtensions.kt11
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsDateUtils.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsFunctions.kt18
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JavaAnnotation.java19
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmCoroutineExtensions.kt11
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmDateUtils.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmFunctions.kt35
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/CInterop.kt15
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/LinuxCoroutineExtensions.kt11
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/LinuxDateUtils.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/macosMain/kotlin/org/kotlintestmpp/MacOsCoroutineExtensions.kt11
-rw-r--r--dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/macosMain/kotlin/org/kotlintestmpp/MacOsDateUtils.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/README.md25
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/build.gradle.kts19
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/demo.pngbin0 -> 35812 bytes
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/build.gradle.kts27
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/build.gradle.kts1
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt18
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/FancyAPI.kt10
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/build.gradle.kts1
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt10
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/Functions.kt8
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/settings.gradle.kts5
-rw-r--r--dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/gradle.properties12
-rw-r--r--dokka-runners/dokkatoo/gradle/libs.versions.toml48
-rw-r--r--dokka-runners/dokkatoo/modules/docs/build.gradle.kts58
-rw-r--r--dokka-runners/dokkatoo/modules/docs/images/banner.svg100
-rw-r--r--dokka-runners/dokkatoo/modules/docs/images/logo-icon.svg84
-rw-r--r--dokka-runners/dokkatoo/modules/docs/images/social_preview_banner.pngbin0 -> 38562 bytes
-rw-r--r--dokka-runners/dokkatoo/modules/docs/images/social_preview_banner.svg106
-rw-r--r--dokka-runners/dokkatoo/modules/docs/style/logo-styles.css44
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/build.gradle.kts281
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/.gitignore15
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-googletv-license2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-arm-dbt-license2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-license2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-preview-license2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/google-gdk-license2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/intel-android-extra-license2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/mips-android-sysimage-license2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/build.gradle.kts18
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/settings.gradle.kts5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/AndroidManifest.xml1
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/java/it/android/AndroidSpecificClass.kt16
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/java/it/android/IntegrationTestActivity.kt22
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/template.root.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/template.settings.gradle.kts38
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/build.gradle.kts32
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/settings.gradle.kts19
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/AndroidManifest.xml1
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/java/it/android/AndroidSpecificClass.kt16
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/java/it/android/IntegrationTestActivity.kt22
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/build.gradle54
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/settings.gradle.kts5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/src/main/java/it/basic/java/SampleJavaClass.java17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/src/main/kotlin/it/basic/PublicClass.kt48
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/template.root.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/template.settings.gradle.kts38
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokkatoo/settings.gradle16
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/build.gradle.kts63
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/custom-resource.svg3
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/custom-style-to-add.css1
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/logo-styles.css3
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/settings.gradle.kts5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/java/it/basic/java/SampleJavaClass.java17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/RootPackageClass.kt8
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/basic/PublicClass.kt69
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/internal/InternalClass.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/overriddenVisibility/VisiblePrivateClass.kt12
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/protected/ProtectedClass.kt10
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/test/kotlin/it/basic/TestClass.kt17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/template.root.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/template.settings.gradle.kts38
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/build.gradle.kts64
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/custom-resource.svg3
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/custom-style-to-add.css1
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/logo-styles.css3
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/java/it/basic/java/SampleJavaClass.java17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/RootPackageClass.kt8
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/basic/PublicClass.kt69
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/internal/InternalClass.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/overriddenVisibility/VisiblePrivateClass.kt12
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/protected/ProtectedClass.kt10
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/test/kotlin/it/basic/TestClass.kt17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/build.gradle.kts1
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/build.gradle.kts6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/README.md2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/build.gradle.kts4
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/src/main/kotlin/org/jetbrains/dokka/it/moduleB/ModuleB.kt6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/README.md2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/build.gradle.kts4
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/src/main/kotlin/org/jetbrains/dokka/it/moduleC/ModuleC.kt6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/settings.gradle.kts5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/template.root.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/template.settings.gradle.kts38
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/build.gradle.kts21
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/settings.gradle.kts5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/RootPackageClass.kt26
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/basic/PublicClass.kt53
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/internal/InternalClass.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/template.root.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/template.settings.gradle.kts38
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/build.gradle.kts1
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/build.gradle.kts6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/Module.md6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/build.gradle.kts13
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/src/main/kotlin/org/jetbrains/dokka/it/moduleB/ModuleB.kt6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/Module.md2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/build.gradle.kts12
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/src/main/kotlin/org/jetbrains/dokka/it/moduleC/ModuleC.kt6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleD/build.gradle.kts6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleD/src/main/kotlin/org/jetbrains/dokka/it/moduleD/ModuleC.kt6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/settings.gradle.kts6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/template.root.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/template.settings.gradle.kts38
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/build.gradle18
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/build.gradle0
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/FirstClass.kt11
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/FirstSubclass.kt12
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/Main.kt8
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/noPackage.kt3
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/build.gradle14
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/NoPackageClass.kt1
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/bar/SecondClass.kt21
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/foo/ThirdClass.kt11
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/settings.gradle.kts4
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/template.root.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/template.settings.gradle.kts38
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/build.gradle43
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/first/build.gradle0
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/first/src/main/kotlin/foo/FirstClass.kt11
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/second/build.gradle3
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/second/src/main/kotlin/bar/SecondClass.kt21
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/settings.gradle.kts4
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/template.root.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/template.settings.gradle.kts38
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/build.gradle.kts48
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/settings.gradle.kts2
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/CommonMainClass.kt8
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/ExpectedClass.kt5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/coroutines.kt5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/desktopMain/kotlin/it/mpp0/CPointerExtension.kt11
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/desktopMain/kotlin/it/mpp0/ExpectedClass.kt5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jsMain/kotlin/it/mpp0/ExpectedClass.kt5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jsMain/kotlin/it/mpp0/runBlocking.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/ExpectedClass.kt11
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/JvmOnlyClass.kt13
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/runBlocking.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/CPointerExtension.kt11
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/ExpectedClass.kt5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/runBlocking.kt13
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/macosMain/kotlin/it/mpp0/ExpectedClass.kt5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/macosMain/kotlin/it/mpp0/runBlocking.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/template.root.gradle.kts23
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/template.settings.gradle.kts38
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokkatoo/settings.gradle.kts17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/CustomFormatExampleTest.kt197
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/GradleExampleTest.kt190
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/KotlinMultiplatformExampleTest.kt226
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/MultimoduleExampleTest.kt244
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testFixtures/kotlin/templateProjectUtils.kt17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/kotlin/AndroidProjectIntegrationTest.kt166
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/kotlin/BasicProjectIntegrationTest.kt153
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/childProjectA.json83
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/childProjectB.json83
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/parentProject.json55
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/readme.md5
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/api/dokkatoo-plugin.api397
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/build.gradle.kts254
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt355
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooExtension.kt130
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooPlugin.kt32
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooAndroidAdapter.kt214
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooJavaAdapter.kt40
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt459
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/distributions/DokkatooConfigurationAttributes.kt59
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/DokkaPublication.kt122
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaExternalDocumentationLinkSpec.kt120
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaGeneratorParametersSpec.kt93
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaModuleDescriptionSpec.kt49
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaPackageOptionsSpec.kt84
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaParametersKxs.kt78
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceLinkSpec.kt106
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceSetIdSpec.kt61
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceSetSpec.kt366
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/HasConfigurableVisibilityModifiers.kt14
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/KotlinPlatform.kt54
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/VisibilityModifier.kt42
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaModuleDescriptionBuilder.kt33
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaParametersBuilder.kt77
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaSourceSetBuilder.kt112
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaHtmlPluginParameters.kt129
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaPluginParametersBaseSpec.kt32
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaPluginParametersBuilder.kt232
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaVersioningPluginParameters.kt101
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatDependencyContainers.kt152
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatPlugin.kt174
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatTasks.kt105
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooGfmPlugin.kt14
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooHtmlPlugin.kt72
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooJavadocPlugin.kt14
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooJekyllPlugin.kt14
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/DokkatooInternalApi.kt37
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/LoggerAdapter.kt65
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/collectionsUtils.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleExtensionAccessors.kt9
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleTypealiases.kt20
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt187
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/kotlinxSerializationUtils.kt36
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/stringUtils.kt11
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/uriUtils.kt9
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooGenerateTask.kt187
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooPrepareModuleDescriptorTask.kt62
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooTask.kt22
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/LogHtmlPublicationLinkTask.kt156
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/workers/DokkaGeneratorWorker.kt77
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/DokkatooPluginTest.kt76
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/DokkaExternalDocumentationLinkSpecTest.kt102
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/DokkaSourceLinkSpecTest.kt58
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/KotlinPlatformTest.kt37
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/VisibilityModifierTest.kt17
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaModuleDescriptionBuilderTest.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaParametersBuilderTest.kt7
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaSourceSetBuilderTest.kt198
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt274
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/KotestProjectConfig.kt10
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/fileTree.kt61
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/files.kt6
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/gradleRunnerUtils.kt47
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestCollectionMatchers.kt20
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestConditions.kt10
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt130
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestStringMatchers.kt65
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/samWithReceiverWorkarounds.kt77
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/stringUtils.kt21
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/systemVariableProviders.kt40
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/text.kt24
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/DokkatooPluginFunctionalTest.kt205
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/GradlePluginProjectIntegrationTest.kt110
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt247
-rw-r--r--dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/MultiModuleFunctionalTest.kt468
-rw-r--r--dokka-runners/dokkatoo/settings.gradle.kts95
365 files changed, 15465 insertions, 0 deletions
diff --git a/dokka-runners/dokkatoo/.gitattributes b/dokka-runners/dokkatoo/.gitattributes
new file mode 100644
index 00000000..2ba525d5
--- /dev/null
+++ b/dokka-runners/dokkatoo/.gitattributes
@@ -0,0 +1,51 @@
+# https://help.github.com/articles/dealing-with-line-endings/
+# https://github.com/alexkaratarakis/gitattributes
+
+* text=auto
+
+# The above will handle all files NOT found below
+
+*.json text
+*.toml text
+*.xml text
+*.yaml text
+*.yml text
+.editorconfig text
+.env text
+
+# Documentation
+*.md text diff=markdown
+*.txt text
+LICENSE text
+
+# JVM
+*.java text diff=java
+*.kt text diff=kotlin
+*.kts text diff=kotlin
+*.properties text
+*.jar binary
+
+
+# Linux start script should use lf
+gradlew text eol=lf
+*.bash text eol=lf
+*.sh text eol=lf
+
+# These are Windows script files and should use crlf
+*.bat text eol=crlf
+*.cmd text eol=crlf
+
+# SVG treated as an asset (binary) by default.
+*.svg text
+
+# Exclude external libs from GitHub language stats https://github.com/github/linguist/blob/v7.24.1/docs/overrides.md
+examples/** linguist-documentation
+examples/*/dokka linguist-vendored
+modules/dokkatoo-plugin-integration-tests/projects/**dokka/ linguist-vendored
+modules/dokkatoo-plugin-integration-tests/projects/**dokkatoo/ linguist-documentation
+
+# Exclude files from exporting
+
+.gitattributes export-ignore
+.gitignore export-ignore
+.gitkeep export-ignore
diff --git a/dokka-runners/dokkatoo/.gitignore b/dokka-runners/dokkatoo/.gitignore
new file mode 100644
index 00000000..e733d08a
--- /dev/null
+++ b/dokka-runners/dokkatoo/.gitignore
@@ -0,0 +1,71 @@
+### Gradle ###
+.gradle
+build/
+
+!gradle/wrapper/gradle-wrapper.jar
+!gradle/wrapper/gradle-wrapper.properties
+
+
+### Kotlin/JVM ###
+*.class
+*.log
+
+hs_err_pid*
+replay_pid*
+*.hprof
+
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+
+### IntelliJ ###
+.idea/**
+!.idea/codeStyles/
+!.idea/codeStyles/**
+
+
+### Eclipse ###
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+.settings/
+.loadpath
+.recommenders
+.classpath
+
+.apt_generated/
+.apt_generated_test/
+.project
+
+
+### Linux ###
+*~
+.fuse_hidden*
+.Trash-*
+.nfs*
+
+
+### Windows ###
+[Dd]esktop.ini
+$RECYCLE.BIN/
+*.lnk
+
+
+### macOS ###
+.DS_Store
+._*
+
+# Icon must end with two \r
+Icon
+
+
+###########################
diff --git a/dokka-runners/dokkatoo/build.gradle.kts b/dokka-runners/dokkatoo/build.gradle.kts
new file mode 100644
index 00000000..67184998
--- /dev/null
+++ b/dokka-runners/dokkatoo/build.gradle.kts
@@ -0,0 +1,47 @@
+import buildsrc.utils.excludeGeneratedGradleDsl
+import buildsrc.utils.initIdeProjectLogo
+
+plugins {
+ buildsrc.conventions.base
+ idea
+}
+
+group = "org.jetbrains.dokka.dokkatoo"
+version = "2.1.0-SNAPSHOT"
+
+
+idea {
+ module {
+ excludeGeneratedGradleDsl(layout)
+
+ excludeDirs.apply {
+ // exclude .gradle, IDE dirs from nested projects (e.g. example & template projects)
+ // so IntelliJ project-wide search isn't cluttered with irrelevant files
+ val excludedDirs = setOf(
+ ".idea",
+ ".gradle",
+ "build",
+ "gradle/wrapper",
+ "ANDROID_SDK",
+ )
+ addAll(
+ projectDir.walk().filter { file ->
+ excludedDirs.any {
+ file.invariantSeparatorsPath.endsWith(it)
+ }
+ }
+ )
+ }
+ }
+}
+
+initIdeProjectLogo("modules/docs/images/logo-icon.svg")
+
+val dokkatooVersion by tasks.registering {
+ description = "prints the Dokkatoo project version (used during release to verify the version)"
+ group = "help"
+ val version = providers.provider { project.version }
+ doLast {
+ logger.quiet("${version.orNull}")
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/build.gradle.kts b/dokka-runners/dokkatoo/buildSrc/build.gradle.kts
new file mode 100644
index 00000000..832a98b9
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/build.gradle.kts
@@ -0,0 +1,19 @@
+import org.gradle.kotlin.dsl.support.expectedKotlinDslPluginsVersion
+
+plugins {
+ `kotlin-dsl`
+}
+
+dependencies {
+ implementation("org.gradle.kotlin:gradle-kotlin-dsl-plugins:$expectedKotlinDslPluginsVersion")
+ implementation(libs.gradlePlugin.bcvMu)
+ implementation(libs.gradlePlugin.dokkatoo)
+ implementation(libs.gradlePlugin.gradlePublishPlugin)
+ implementation("org.jetbrains.kotlin:kotlin-serialization:$embeddedKotlinVersion")
+}
+
+java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(11))
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/settings.gradle.kts b/dokka-runners/dokkatoo/buildSrc/settings.gradle.kts
new file mode 100644
index 00000000..855fe5d9
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/settings.gradle.kts
@@ -0,0 +1,25 @@
+rootProject.name = "buildSrc"
+
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+
+ repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
+
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+
+ versionCatalogs {
+ create("libs") {
+ from(files("../gradle/libs.versions.toml"))
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/android-setup.gradle.kts b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/android-setup.gradle.kts
new file mode 100644
index 00000000..ed22d799
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/android-setup.gradle.kts
@@ -0,0 +1,78 @@
+package buildsrc.conventions
+
+import org.jetbrains.kotlin.util.suffixIfNot
+
+
+/**
+ * Utilities for preparing Android projects
+ */
+
+plugins {
+ base
+ id("buildsrc.conventions.base")
+}
+
+
+val androidSdkDirPath: Provider<String> = providers
+ // first try getting the SDK installed on via GitHub step setup-android
+ .environmentVariable("ANDROID_SDK_ROOT").map(::File)
+ // else get the project-local SDK
+ .orElse(layout.projectDirectory.file("projects/ANDROID_SDK").asFile)
+ .map { it.invariantSeparatorsPath }
+
+
+val createAndroidLocalPropertiesFile by tasks.registering {
+
+ val localPropertiesFile = temporaryDir.resolve("local.properties")
+ outputs.file(localPropertiesFile).withPropertyName("localPropertiesFile")
+
+ val androidSdkDirPath = androidSdkDirPath
+ inputs.property("androidSdkDirPath", androidSdkDirPath)
+
+ doLast {
+ localPropertiesFile.apply {
+ parentFile.mkdirs()
+ createNewFile()
+ writeText(
+ """
+ |# DO NOT EDIT - Generated by $path
+ |
+ |sdk.dir=${androidSdkDirPath.get()}
+ |
+ """.trimMargin()
+ )
+ }
+ }
+}
+
+
+val updateAndroidLocalProperties by tasks.registering {
+
+ // find all local.properties files
+ val localPropertiesFiles = layout.projectDirectory.dir("projects")
+ .asFileTree
+ .matching { include("**/local.properties") }
+ .files
+
+ outputs.files(localPropertiesFiles).withPropertyName("localPropertiesFiles")
+
+ val androidSdkDirPath = androidSdkDirPath
+ inputs.property("androidSdkDirPath", androidSdkDirPath)
+
+ doLast {
+ localPropertiesFiles
+ .filter { it.exists() }
+ .forEach { file ->
+ file.writeText(
+ file.useLines { lines ->
+ lines.joinToString("\n") { line ->
+ when {
+ line.startsWith("sdk.dir=") -> "sdk.dir=${androidSdkDirPath.get()}"
+ else -> line
+ }
+ }.suffixIfNot("\n")
+ }
+ )
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/base.gradle.kts b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/base.gradle.kts
new file mode 100644
index 00000000..60bfa2fe
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/base.gradle.kts
@@ -0,0 +1,155 @@
+package buildsrc.conventions
+
+import java.time.Duration
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+
+/**
+ * A convention plugin that sets up common config and sensible defaults for all subprojects.
+ */
+
+plugins {
+ base
+}
+
+if (project != rootProject) {
+ project.version = rootProject.version
+ project.group = rootProject.group
+}
+
+tasks.withType<AbstractArchiveTask>().configureEach {
+ // https://docs.gradle.org/current/userguide/working_with_files.html#sec:reproducible_archives
+ isPreserveFileTimestamps = false
+ isReproducibleFileOrder = true
+}
+
+tasks.withType<AbstractTestTask>().configureEach {
+ timeout.set(Duration.ofMinutes(60))
+
+ testLogging {
+ showCauses = true
+ showExceptions = true
+ showStackTraces = true
+ showStandardStreams = true
+ events(
+ TestLogEvent.PASSED,
+ TestLogEvent.FAILED,
+ TestLogEvent.SKIPPED,
+ TestLogEvent.STARTED,
+ TestLogEvent.STANDARD_ERROR,
+ TestLogEvent.STANDARD_OUT,
+ )
+ }
+}
+
+tasks.withType<AbstractCopyTask>().configureEach {
+ includeEmptyDirs = false
+}
+
+val updateTestReportCss by tasks.registering {
+ description = "Hack so the Gradle test reports have dark mode"
+ // the CSS is based on https://github.com/gradle/gradle/pull/12177
+
+ mustRunAfter(tasks.withType<Test>())
+ mustRunAfter(tasks.withType<TestReport>())
+
+ val cssFiles = layout.buildDirectory.asFileTree.matching {
+ include("reports/**/css/base-style.css")
+ include("reports/**/css/style.css")
+ }
+
+ outputs.files(cssFiles.files)
+
+ doLast {
+ cssFiles.forEach { cssFile ->
+ val fileContent = cssFile.readText()
+
+ if ("/* Dark mode */" in fileContent) {
+ return@forEach
+ } else {
+ when (cssFile.name) {
+ "base-style.css" -> cssFile.writeText(
+ fileContent + """
+
+ /* Dark mode */
+ @media (prefers-color-scheme: dark) {
+ html {
+ background: black;
+ }
+ body, a, a:visited {
+ color: #E7E7E7FF;
+ }
+ #footer, #footer a {
+ color: #cacaca;
+ }
+ ul.tabLinks li {
+ border: solid 1px #cacaca;
+ background-color: #151515;
+ }
+ ul.tabLinks li:hover {
+ background-color: #383838;
+ }
+ ul.tabLinks li.selected {
+ background-color: #002d32;
+ border-color: #007987;
+ }
+ div.tab th, div.tab table {
+ border-bottom: solid #d0d0d0 1px;
+ }
+ span.code pre {
+ background-color: #0a0a0a;
+ border: solid 1px #5f5f5f;
+ }
+ }
+ """.trimIndent()
+ )
+
+ "style.css" -> cssFile.writeText(
+ fileContent + """
+
+ /* Dark mode */
+ @media (prefers-color-scheme: dark) {
+ .breadcrumbs, .breadcrumbs a {
+ color: #9b9b9b;
+ }
+ #successRate, .summaryGroup {
+ border: solid 2px #d0d0d0;
+ }
+ .success, .success a {
+ color: #7fff7f;
+ }
+ div.success, #successRate.success {
+ background-color: #001c00;
+ border-color: #7fff7f;
+ }
+ .failures, .failures a {
+ color: #a30000;
+ }
+ .skipped, .skipped a {
+ color: #a26d13;
+ }
+ div.failures, #successRate.failures {
+ background-color: #170000;
+ border-color: #a30000;
+ }
+ }
+ """.trimIndent()
+ )
+ }
+ }
+ }
+ }
+}
+
+tasks.withType<Test>().configureEach {
+ finalizedBy(updateTestReportCss)
+}
+
+tasks.withType<TestReport>().configureEach {
+ finalizedBy(updateTestReportCss)
+}
+
+tasks.matching { it.name == "validatePlugins" }.configureEach {
+ // prevent warning
+ // Task ':validatePlugins' uses this output of task ':updateTestReportCss' without declaring an explicit or implicit dependency.
+ mustRunAfter(updateTestReportCss)
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokka-source-downloader.gradle.kts b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokka-source-downloader.gradle.kts
new file mode 100644
index 00000000..69e384e1
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokka-source-downloader.gradle.kts
@@ -0,0 +1,68 @@
+package buildsrc.conventions
+
+import buildsrc.settings.DokkaSourceDownloaderSettings
+import buildsrc.utils.asConsumer
+import buildsrc.utils.asProvider
+import buildsrc.utils.dropDirectories
+import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE
+import org.gradle.kotlin.dsl.support.serviceOf
+
+plugins {
+ id("buildsrc.conventions.base")
+}
+
+val dsdExt: DokkaSourceDownloaderSettings = extensions.create<DokkaSourceDownloaderSettings>(
+ DokkaSourceDownloaderSettings.EXTENSION_NAME
+)
+
+val kotlinDokkaSource by configurations.creating<Configuration> {
+ asConsumer()
+ attributes {
+ attribute(USAGE_ATTRIBUTE, objects.named("externals-dokka-src"))
+ }
+}
+
+val kotlinDokkaSourceElements by configurations.registering {
+ asProvider()
+ attributes {
+ attribute(USAGE_ATTRIBUTE, objects.named("externals-dokka-src"))
+ }
+}
+
+dependencies {
+ kotlinDokkaSource(dsdExt.dokkaVersion.map { "kotlin:dokka:$it@zip" })
+}
+
+val prepareDokkaSource by tasks.registering(Sync::class) {
+ group = "dokka setup"
+ description = "Download & unpack Kotlin Dokka source code"
+
+ inputs.property("dokkaVersion", dsdExt.dokkaVersion).optional(false)
+
+ val archives = serviceOf<ArchiveOperations>()
+
+ from(
+ kotlinDokkaSource.incoming
+ .artifacts
+ .resolvedArtifacts
+ .map { artifacts ->
+ artifacts.map { archives.zipTree(it.file) }
+ }
+ ) {
+ // drop the first dir (dokka-$version)
+ eachFile {
+ relativePath = relativePath.dropDirectories(1)
+ }
+ }
+
+ into(temporaryDir)
+
+ exclude(
+ "*.github",
+ "*.gradle",
+ "**/gradlew",
+ "**/gradlew.bat",
+ "**/gradle/wrapper/gradle-wrapper.jar",
+ "**/gradle/wrapper/gradle-wrapper.properties",
+ )
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokkatoo-example-projects-base.gradle.kts b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokkatoo-example-projects-base.gradle.kts
new file mode 100644
index 00000000..5c2c45fa
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokkatoo-example-projects-base.gradle.kts
@@ -0,0 +1,27 @@
+package buildsrc.conventions
+
+import buildsrc.utils.asConsumer
+import buildsrc.utils.asProvider
+
+plugins {
+ id("buildsrc.conventions.base")
+}
+
+
+val exampleProjectsAttribute: Attribute<String> =
+ Attribute.of("example-projects", String::class.java)
+
+dependencies.attributesSchema {
+ attribute(exampleProjectsAttribute)
+}
+
+
+val exampleProjects by configurations.registering {
+ asConsumer()
+ attributes { attribute(exampleProjectsAttribute, "dokka") }
+}
+
+val exampleProjectsElements by configurations.registering {
+ asProvider()
+ attributes { attribute(exampleProjectsAttribute, "dokka") }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokkatoo-example-projects.gradle.kts b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokkatoo-example-projects.gradle.kts
new file mode 100644
index 00000000..c6994a83
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/dokkatoo-example-projects.gradle.kts
@@ -0,0 +1,160 @@
+package buildsrc.conventions
+
+import buildsrc.settings.*
+import buildsrc.tasks.*
+import buildsrc.utils.*
+
+plugins {
+ id("buildsrc.conventions.base")
+ id("buildsrc.conventions.dokka-source-downloader")
+ id("buildsrc.conventions.maven-publish-test")
+ id("buildsrc.conventions.dokkatoo-example-projects-base")
+}
+
+val mavenPublishTestExtension = extensions.getByType<MavenPublishTestSettings>()
+val dokkaTemplateProjectSettings =
+ extensions.create<DokkaTemplateProjectSettings>(
+ DokkaTemplateProjectSettings.EXTENSION_NAME,
+ { project.copySpec() }
+ ).apply {
+ this.destinationBaseDir.convention(layout.projectDirectory)
+ }
+
+val prepareDokkaSource by tasks.existing(Sync::class)
+
+dokkaTemplateProjectSettings.dokkaSourceDir.convention(
+ prepareDokkaSource.flatMap {
+ layout.dir(providers.provider {
+ it.destinationDir
+ })
+ }
+)
+
+tasks.withType<SetupDokkaProjects>().configureEach {
+ dependsOn(prepareDokkaSource)
+
+ dokkaSourceDir.convention(dokkaTemplateProjectSettings.dokkaSourceDir)
+ destinationBaseDir.convention(dokkaTemplateProjectSettings.destinationBaseDir)
+
+ templateProjects.addAllLater(provider {
+ dokkaTemplateProjectSettings.templateProjects
+ })
+}
+
+val setupDokkaTemplateProjects by tasks.registering(SetupDokkaProjects::class)
+
+fun createDokkatooExampleProjectsSettings(
+ projectDir: Directory = project.layout.projectDirectory
+): DokkatooExampleProjectsSettings {
+ return extensions.create<DokkatooExampleProjectsSettings>(
+ DokkatooExampleProjectsSettings.EXTENSION_NAME
+ ).apply {
+
+ // find all Gradle settings files
+ val settingsFiles = projectDir.asFileTree
+ .matching {
+ include(
+ "**/*dokkatoo*/**/settings.gradle.kts",
+ "**/*dokkatoo*/**/settings.gradle",
+ )
+ }.files
+
+ // for each settings file, create a DokkatooExampleProjectSpec
+ settingsFiles.forEach {
+ val destinationDir = it.parentFile
+ val name = destinationDir.toRelativeString(projectDir.asFile).toAlphaNumericCamelCase()
+ exampleProjects.register(name) {
+ this.exampleProjectDir.set(destinationDir)
+ }
+ }
+
+ exampleProjects.configureEach {
+ gradlePropertiesContent.add(
+ mavenPublishTestExtension.testMavenRepoPath.map { testMavenRepoPath ->
+ "testMavenRepo=$testMavenRepoPath"
+ }
+ )
+ }
+ }
+}
+
+val dokkatooExampleProjectsSettings = createDokkatooExampleProjectsSettings()
+
+val updateDokkatooExamplesGradleProperties by tasks.registering(
+ UpdateDokkatooExampleProjects::class
+) {
+ group = DokkatooExampleProjectsSettings.TASK_GROUP
+
+ mustRunAfter(tasks.withType<SetupDokkaProjects>())
+
+ exampleProjects.addAllLater(providers.provider {
+ dokkatooExampleProjectsSettings.exampleProjects
+ })
+}
+
+val dokkatooVersion = provider { project.version.toString() }
+
+val updateDokkatooExamplesBuildFiles by tasks.registering {
+ group = DokkatooExampleProjectsSettings.TASK_GROUP
+ description = "Update the Gradle build files in the Dokkatoo examples"
+
+ outputs.upToDateWhen { false }
+
+ mustRunAfter(tasks.withType<SetupDokkaProjects>())
+ shouldRunAfter(updateDokkatooExamplesGradleProperties)
+
+ val dokkatooVersion = dokkatooVersion
+
+ val dokkatooDependencyVersionMatcher = """
+ \"dev\.adamko\.dokkatoo\:dokkatoo\-plugin\:([^"]+?)\"
+ """.trimIndent().toRegex()
+
+ val dokkatooPluginVersionMatcher = """
+ id[^"]+?"dev\.adamko\.dokkatoo".+?version "([^"]+?)"
+ """.trimIndent().toRegex()
+
+ val gradleBuildFiles =
+ layout.projectDirectory.asFileTree
+ .matching {
+ include(
+ "**/*dokkatoo*/**/build.gradle.kts",
+ "**/*dokkatoo*/**/build.gradle",
+ )
+ }.elements
+ outputs.files(gradleBuildFiles)
+
+ doLast {
+ gradleBuildFiles.get().forEach { fileLocation ->
+ val file = fileLocation.asFile
+ if (file.exists()) {
+ file.writeText(
+ file.readText()
+ .replace(dokkatooPluginVersionMatcher) {
+ val oldVersion = it.groupValues[1]
+ it.value.replace(oldVersion, dokkatooVersion.get())
+ }
+ .replace(dokkatooDependencyVersionMatcher) {
+ val oldVersion = it.groupValues[1]
+ it.value.replace(oldVersion, dokkatooVersion.get())
+ }
+ )
+ }
+ }
+ }
+}
+
+
+val updateDokkatooExamples by tasks.registering {
+ group = DokkatooExampleProjectsSettings.TASK_GROUP
+ description = "lifecycle task for all '${DokkatooExampleProjectsSettings.TASK_GROUP}' tasks"
+ dependsOn(
+ setupDokkaTemplateProjects,
+ updateDokkatooExamplesGradleProperties,
+ updateDokkatooExamplesBuildFiles,
+ )
+}
+
+tasks.assemble {
+ dependsOn(updateDokkatooExamples)
+ dependsOn(setupDokkaTemplateProjects)
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/gradle-plugin-variants.gradle.kts b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/gradle-plugin-variants.gradle.kts
new file mode 100644
index 00000000..1d9fc43b
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/gradle-plugin-variants.gradle.kts
@@ -0,0 +1,44 @@
+package buildsrc.conventions
+
+import org.gradle.api.attributes.plugin.GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE
+
+plugins {
+ id("buildsrc.conventions.base")
+ `java-gradle-plugin`
+}
+
+fun registerGradleVariant(name: String, gradleVersion: String) {
+ val variantSources = sourceSets.create(name)
+
+ java {
+ registerFeature(variantSources.name) {
+ usingSourceSet(variantSources)
+ capability("${project.group}", "${project.name}", "${project.version}")
+
+ withJavadocJar()
+ withSourcesJar()
+ }
+ }
+
+ configurations
+ .matching { it.isCanBeConsumed && it.name.startsWith(variantSources.name) }
+ .configureEach {
+ attributes {
+ attribute(GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, objects.named(gradleVersion))
+ }
+ }
+
+ tasks.named<Copy>(variantSources.processResourcesTaskName) {
+ val copyPluginDescriptors = rootSpec.addChild()
+ copyPluginDescriptors.into("META-INF/gradle-plugins")
+// copyPluginDescriptors.into(tasks.pluginDescriptors.flatMap { it.outputDirectory })
+ copyPluginDescriptors.from(tasks.pluginDescriptors)
+ }
+
+ dependencies {
+ add(variantSources.compileOnlyConfigurationName, gradleApi())
+ }
+}
+
+registerGradleVariant("gradle7", "7.6")
+registerGradleVariant("gradle8", "8.0")
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/java-base.gradle.kts b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/java-base.gradle.kts
new file mode 100644
index 00000000..203b80f2
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/java-base.gradle.kts
@@ -0,0 +1,19 @@
+package buildsrc.conventions
+
+import org.gradle.api.JavaVersion
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.jvm.toolchain.JavaLanguageVersion
+import org.gradle.kotlin.dsl.getByType
+import org.gradle.kotlin.dsl.`java-base`
+
+plugins {
+ id("buildsrc.conventions.base")
+ `java`
+}
+
+extensions.getByType<JavaPluginExtension>().apply {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(11))
+ }
+ withSourcesJar()
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/kotlin-gradle-plugin.gradle.kts b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/kotlin-gradle-plugin.gradle.kts
new file mode 100644
index 00000000..4174088a
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/kotlin-gradle-plugin.gradle.kts
@@ -0,0 +1,37 @@
+package buildsrc.conventions
+
+plugins {
+ id("buildsrc.conventions.base")
+ id("buildsrc.conventions.java-base")
+ id("org.gradle.kotlin.kotlin-dsl")
+ id("com.gradle.plugin-publish")
+}
+
+tasks.validatePlugins {
+ enableStricterValidation.set(true)
+}
+
+val createJavadocJarReadme by tasks.registering(Sync::class) {
+ description = "generate a readme.txt for the Javadoc JAR"
+ from(
+ resources.text.fromString(
+ """
+ This Javadoc JAR is intentionally empty.
+
+ For documentation, see the sources JAR or https://github.com/adamko-dev/dokkatoo/
+
+ """.trimIndent()
+ )
+ ) {
+ rename { "readme.txt" }
+ }
+ into(temporaryDir)
+}
+
+
+// The Gradle Publish Plugin enables the Javadoc JAR in afterEvaluate, so find it lazily
+tasks.withType<Jar>()
+ .matching { it.name == "javadocJar" }
+ .configureEach {
+ from(createJavadocJarReadme)
+ }
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publish-test.gradle.kts b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publish-test.gradle.kts
new file mode 100644
index 00000000..38678b5b
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publish-test.gradle.kts
@@ -0,0 +1,93 @@
+package buildsrc.conventions
+
+import buildsrc.settings.MavenPublishTestSettings
+import buildsrc.utils.*
+
+
+/** Utility for publishing a project to a local Maven directory for use in integration tests. */
+
+plugins {
+ base
+}
+
+val Gradle.rootGradle: Gradle get() = generateSequence(gradle) { it.parent }.last()
+
+val mavenPublishTestExtension = extensions.create<MavenPublishTestSettings>(
+ "mavenPublishTest",
+ gradle.rootGradle.rootProject.layout.buildDirectory.dir("test-maven-repo"),
+)
+
+
+val publishToTestMavenRepo by tasks.registering {
+ group = PublishingPlugin.PUBLISH_TASK_GROUP
+ description = "Publishes all Maven publications to the test Maven repository."
+}
+
+
+plugins.withType<MavenPublishPlugin>().all {
+ extensions
+ .getByType<PublishingExtension>()
+ .publications
+ .withType<MavenPublication>().all publication@{
+ val publicationName = this@publication.name
+ val installTaskName = "publish${publicationName.uppercaseFirstChar()}PublicationToTestMavenRepo"
+
+ // Register a publication task for each publication.
+ // Use PublishToMavenLocal, because the PublishToMavenRepository task will *always* create
+ // a new jar, even if nothing has changed, and append a timestamp, which results in a large
+ // directory and tasks are never up-to-date.
+ // PublishToMavenLocal does not append a timestamp, so the target directory is smaller, and
+ // up-to-date checks work.
+ val installTask = tasks.register<PublishToMavenLocal>(installTaskName) {
+ description = "Publishes Maven publication '$publicationName' to the test Maven repository."
+ group = PublishingPlugin.PUBLISH_TASK_GROUP
+ outputs.cacheIf { true }
+ publication = this@publication
+ val destinationDir = mavenPublishTestExtension.testMavenRepo.get().asFile
+ inputs.property("testMavenRepoTempDir", destinationDir.invariantSeparatorsPath)
+ doFirst {
+ /**
+ * `maven.repo.local` will set the destination directory for this [PublishToMavenLocal] task.
+ *
+ * @see org.gradle.api.internal.artifacts.mvnsettings.DefaultLocalMavenRepositoryLocator.getLocalMavenRepository
+ */
+ System.setProperty("maven.repo.local", destinationDir.absolutePath)
+ }
+ }
+
+ publishToTestMavenRepo.configure {
+ dependsOn(installTask)
+ }
+
+ tasks.check {
+ mustRunAfter(installTask)
+ }
+ }
+}
+
+
+val testMavenPublication by configurations.registering {
+ asConsumer()
+ attributes {
+ attribute(MavenPublishTestSettings.attribute, "testMavenRepo")
+ }
+}
+
+val testMavenPublicationElements by configurations.registering {
+ asProvider()
+ extendsFrom(testMavenPublication.get())
+ attributes {
+ attribute(MavenPublishTestSettings.attribute, "testMavenRepo")
+ }
+ outgoing {
+ artifact(mavenPublishTestExtension.testMavenRepo) {
+ builtBy(publishToTestMavenRepo)
+ }
+ }
+}
+
+dependencies {
+ attributesSchema {
+ attribute(MavenPublishTestSettings.attribute)
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts
new file mode 100644
index 00000000..7af7b69f
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts
@@ -0,0 +1,137 @@
+package buildsrc.conventions
+
+import buildsrc.settings.MavenPublishingSettings
+
+plugins {
+ `maven-publish`
+ signing
+}
+
+val mavenPublishing =
+ extensions.create<MavenPublishingSettings>(MavenPublishingSettings.EXTENSION_NAME, project)
+
+
+//region POM convention
+publishing {
+ publications.withType<MavenPublication>().configureEach {
+ pom {
+ name.convention("Dokkatoo")
+ description.convention("Dokkatoo is a Gradle plugin that generates documentation for your Kotlin projects")
+ url.convention("https://github.com/adamko-dev/dokkatoo")
+
+ scm {
+ connection.convention("scm:git:https://github.com/adamko-dev/dokkatoo")
+ developerConnection.convention("scm:git:https://github.com/adamko-dev/dokkatoo")
+ url.convention("https://github.com/adamko-dev/dokkatoo")
+ }
+
+ licenses {
+ license {
+ name.convention("Apache-2.0")
+ url.convention("https://www.apache.org/licenses/LICENSE-2.0.txt")
+ }
+ }
+
+ developers {
+ developer {
+ email.set("adam@adamko.dev")
+ }
+ }
+ }
+ }
+}
+//endregion
+
+
+//region GitHub branch publishing
+publishing {
+ repositories {
+ maven(mavenPublishing.githubPublishDir) {
+ name = "GitHubPublish"
+ }
+ }
+}
+//endregion
+
+
+//region Maven Central publishing/signing
+publishing {
+ repositories {
+ val mavenCentralUsername = mavenPublishing.mavenCentralUsername.orNull
+ val mavenCentralPassword = mavenPublishing.mavenCentralPassword.orNull
+ if (!mavenCentralUsername.isNullOrBlank() && !mavenCentralPassword.isNullOrBlank()) {
+ maven(mavenPublishing.sonatypeReleaseUrl) {
+ name = "SonatypeRelease"
+ credentials {
+ username = mavenCentralUsername
+ password = mavenCentralPassword
+ }
+ }
+ }
+ }
+
+ // com.gradle.plugin-publish automatically adds a Javadoc jar
+}
+
+signing {
+ logger.info("maven-publishing.gradle.kts enabled signing for ${project.path}")
+
+ val keyId = mavenPublishing.signingKeyId.orNull
+ val key = mavenPublishing.signingKey.orNull
+ val password = mavenPublishing.signingPassword.orNull
+
+ if (!keyId.isNullOrBlank() && !key.isNullOrBlank() && !password.isNullOrBlank()) {
+ useInMemoryPgpKeys(keyId, key, password)
+ }
+
+ setRequired({
+ gradle.taskGraph.allTasks.filterIsInstance<PublishToMavenRepository>().any {
+ it.repository.name == "SonatypeRelease"
+ }
+ })
+}
+
+//afterEvaluate {
+// com.gradle.plugin-publish automatically signs tasks in a weird way, that stops this from working:
+// signing {
+// sign(publishing.publications)
+// }
+//}
+//endregion
+
+
+//region Fix Gradle warning about signing tasks using publishing task outputs without explicit dependencies
+// https://youtrack.jetbrains.com/issue/KT-46466 https://github.com/gradle/gradle/issues/26091
+tasks.withType<AbstractPublishToMaven>().configureEach {
+ val signingTasks = tasks.withType<Sign>()
+ mustRunAfter(signingTasks)
+}
+//endregion
+
+
+//region publishing logging
+tasks.withType<AbstractPublishToMaven>().configureEach {
+ val publicationGAV = provider { publication?.run { "$group:$artifactId:$version" } }
+ doLast("log publication GAV") {
+ if (publicationGAV.isPresent) {
+ logger.lifecycle("[task: ${path}] ${publicationGAV.get()}")
+ }
+ }
+}
+//endregion
+
+
+//region IJ workarounds
+// manually define the Kotlin DSL accessors because IntelliJ _still_ doesn't load them properly
+fun Project.publishing(configure: PublishingExtension.() -> Unit): Unit =
+ extensions.configure(configure)
+
+val Project.publishing: PublishingExtension
+ get() = extensions.getByType()
+
+fun Project.signing(configure: SigningExtension.() -> Unit): Unit =
+ extensions.configure(configure)
+
+val Project.signing: SigningExtension
+ get() = extensions.getByType()
+//endregion
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkaSourceDownloaderSettings.kt b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkaSourceDownloaderSettings.kt
new file mode 100644
index 00000000..c3f9906c
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkaSourceDownloaderSettings.kt
@@ -0,0 +1,13 @@
+package buildsrc.settings
+
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.provider.Property
+
+abstract class DokkaSourceDownloaderSettings : ExtensionAware {
+
+ abstract val dokkaVersion: Property<String>
+
+ companion object {
+ const val EXTENSION_NAME = "dokkaSourceDownload"
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkaTemplateProjectSettings.kt b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkaTemplateProjectSettings.kt
new file mode 100644
index 00000000..7bacafb9
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkaTemplateProjectSettings.kt
@@ -0,0 +1,96 @@
+package buildsrc.settings
+
+import buildsrc.utils.adding
+import buildsrc.utils.domainObjectContainer
+import buildsrc.utils.toAlphaNumericCamelCase
+import javax.inject.Inject
+import org.gradle.api.Named
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.CopySpec
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.SetProperty
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.Optional
+import org.gradle.kotlin.dsl.*
+
+private typealias TemplateProjectsContainer = NamedDomainObjectContainer<DokkaTemplateProjectSettings.DokkaTemplateProjectSpec>
+
+abstract class DokkaTemplateProjectSettings @Inject constructor(
+ private val objects: ObjectFactory,
+ private val copySpecs: () -> CopySpec
+) : ExtensionAware {
+
+ /** Directory that will contain the projects downloaded from the Dokka source code. */
+ abstract val dokkaSourceDir: DirectoryProperty
+
+ abstract val destinationBaseDir: DirectoryProperty
+
+ internal val templateProjects: TemplateProjectsContainer =
+ // create an extension so Gradle will generate DSL accessors
+ extensions.adding("templateProjects", objects.domainObjectContainer { name ->
+ objects.newInstance<DokkaTemplateProjectSpec>(name, copySpecs())
+ })
+
+ /**
+ * Copy a directory from the Dokka source project into a local directory.
+ *
+ * @param[source] Source dir, relative to [templateProjectsDir]
+ * @param[destination] Destination dir, relative to [destinationBaseDir]
+ */
+ fun register(
+ source: String,
+ destination: String,
+ configure: DokkaTemplateProjectSpec.() -> Unit = {},
+ ) {
+ val name = source.toAlphaNumericCamelCase()
+ templateProjects.register(name) {
+ this.sourcePath.set(source)
+ this.destinationPath.set(destination)
+ configure()
+ }
+ }
+
+ fun configureEach(configure: DokkaTemplateProjectSpec.() -> Unit) {
+ templateProjects.configureEach(configure)
+ }
+
+ /**
+ * Details for how to copy a Dokka template project from the Dokka project to a local directory.
+ */
+ abstract class DokkaTemplateProjectSpec @Inject constructor(
+ private val named: String,
+ @get:Internal
+ internal val copySpec: CopySpec,
+ ) : Named {
+
+ @get:Input
+ abstract val sourcePath: Property<String>
+
+ @get:Input
+ @get:Optional
+ abstract val destinationPath: Property<String>
+
+ @get:Input
+ abstract val additionalPaths: SetProperty<String>
+
+ @get:InputFiles
+ abstract val additionalFiles: ConfigurableFileCollection
+
+ fun configureCopy(configure: CopySpec.() -> Unit) {
+ copySpec.configure()
+ }
+
+ @Input
+ override fun getName(): String = named
+ }
+
+ companion object {
+ const val EXTENSION_NAME = "dokkaTemplateProjects"
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkatooExampleProjectsSettings.kt b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkatooExampleProjectsSettings.kt
new file mode 100644
index 00000000..a3124904
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/DokkatooExampleProjectsSettings.kt
@@ -0,0 +1,62 @@
+package buildsrc.settings
+
+import buildsrc.utils.adding
+import buildsrc.utils.domainObjectContainer
+import javax.inject.Inject
+import org.gradle.api.Named
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFile
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputFile
+
+/**
+ * Settings for the [buildsrc.conventions.Dokkatoo_example_projects_gradle] convention plugin
+ */
+abstract class DokkatooExampleProjectsSettings @Inject constructor(
+ objects: ObjectFactory,
+) : ExtensionAware {
+
+ val exampleProjects: NamedDomainObjectContainer<DokkatooExampleProjectSpec> =
+ // create an extension so Gradle will generate DSL accessors
+ extensions.adding("exampleProjects", objects.domainObjectContainer())
+
+ abstract class DokkatooExampleProjectSpec(
+ private val name: String
+ ): Named {
+
+ /** The `gradle.properties` file of the example project */
+ @get:OutputFile
+ val gradlePropertiesFile: Provider<RegularFile>
+ get() = exampleProjectDir.file("gradle.properties")
+
+ /** The directory that contains the example project */
+ @get:Internal
+ abstract val exampleProjectDir: DirectoryProperty
+
+ /**
+ * Content to add to the `gradle.properties` file.
+ *
+ * Elements may span multiple lines.
+ *
+ * Elements will be sorted before appending to the file (to improve caching & reproducibility).
+ */
+ @get:Input
+ @get:Optional
+ abstract val gradlePropertiesContent: ListProperty<String>
+
+ @Input
+ override fun getName(): String = name
+ }
+
+ companion object {
+ const val TASK_GROUP = "dokkatoo examples"
+ const val EXTENSION_NAME = "dokkatooExampleProjects"
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishTestSettings.kt b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishTestSettings.kt
new file mode 100644
index 00000000..0a701986
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishTestSettings.kt
@@ -0,0 +1,19 @@
+package buildsrc.settings
+
+import org.gradle.api.attributes.Attribute
+import org.gradle.api.file.Directory
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.provider.Provider
+
+/**
+ * Settings for the [buildsrc.conventions.Maven_publish_test_gradle] convention plugin.
+ */
+abstract class MavenPublishTestSettings(
+ val testMavenRepo: Provider<Directory>
+) : ExtensionAware {
+ val testMavenRepoPath: Provider<String> = testMavenRepo.map { it.asFile.invariantSeparatorsPath }
+
+ companion object {
+ val attribute = Attribute.of("maven-publish-test", String::class.java)
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishingSettings.kt b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishingSettings.kt
new file mode 100644
index 00000000..9ec28faa
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishingSettings.kt
@@ -0,0 +1,68 @@
+package buildsrc.settings
+
+import java.io.File
+import javax.inject.Inject
+import org.gradle.api.Project
+import org.gradle.api.provider.Provider
+import org.gradle.api.provider.ProviderFactory
+import org.gradle.kotlin.dsl.*
+
+
+/**
+ * Settings for the [buildsrc.conventions.Maven_publish_test_gradle] convention plugin.
+ */
+abstract class MavenPublishingSettings @Inject constructor(
+ private val project: Project,
+ private val providers: ProviderFactory,
+) {
+
+ private val isReleaseVersion: Provider<Boolean> =
+ providers.provider { !project.version.toString().endsWith("-SNAPSHOT") }
+
+ val sonatypeReleaseUrl: Provider<String> =
+ isReleaseVersion.map { isRelease ->
+ if (isRelease) {
+ "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
+ } else {
+ "https://s01.oss.sonatype.org/content/repositories/snapshots/"
+ }
+ }
+
+ val mavenCentralUsername: Provider<String> =
+ d2Prop("mavenCentralUsername")
+ .orElse(providers.environmentVariable("MAVEN_SONATYPE_USERNAME"))
+ val mavenCentralPassword: Provider<String> =
+ d2Prop("mavenCentralPassword")
+ .orElse(providers.environmentVariable("MAVEN_SONATYPE_PASSWORD"))
+
+ val signingKeyId: Provider<String> =
+ d2Prop("signing.keyId")
+ .orElse(providers.environmentVariable("MAVEN_SONATYPE_SIGNING_KEY_ID"))
+ val signingKey: Provider<String> =
+ d2Prop("signing.key")
+ .orElse(providers.environmentVariable("MAVEN_SONATYPE_SIGNING_KEY"))
+ val signingPassword: Provider<String> =
+ d2Prop("signing.password")
+ .orElse(providers.environmentVariable("MAVEN_SONATYPE_SIGNING_PASSWORD"))
+
+ val githubPublishDir: Provider<File> =
+ providers.environmentVariable("GITHUB_PUBLISH_DIR").map { File(it) }
+
+ private fun d2Prop(name: String): Provider<String> =
+ providers.gradleProperty("org.jetbrains.dokka.dokkatoo.$name")
+
+ private fun <T : Any> d2Prop(name: String, convert: (String) -> T): Provider<T> =
+ d2Prop(name).map(convert)
+
+ companion object {
+ const val EXTENSION_NAME = "mavenPublishing"
+
+ /** Retrieve the [KayrayBuildProperties] extension. */
+ internal val Project.mavenPublishing: MavenPublishingSettings
+ get() = extensions.getByType()
+
+ /** Configure the [KayrayBuildProperties] extension. */
+ internal fun Project.mavenPublishing(configure: MavenPublishingSettings.() -> Unit) =
+ extensions.configure(configure)
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/tasks/SetupDokkaProjects.kt b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/tasks/SetupDokkaProjects.kt
new file mode 100644
index 00000000..d473d287
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/tasks/SetupDokkaProjects.kt
@@ -0,0 +1,73 @@
+package buildsrc.tasks
+
+import buildsrc.settings.DokkaTemplateProjectSettings.DokkaTemplateProjectSpec
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.FileSystemOperations
+import org.gradle.api.file.ProjectLayout
+import org.gradle.api.provider.ProviderFactory
+import org.gradle.api.tasks.*
+
+abstract class SetupDokkaProjects @Inject constructor(
+ private val fs: FileSystemOperations,
+ private val layout: ProjectLayout,
+ private val providers: ProviderFactory,
+) : DefaultTask() {
+
+ @get:OutputDirectories
+ val destinationDirs: FileCollection
+ get() = layout.files(
+ destinationBaseDir.map { base ->
+ templateProjects.map { spec -> base.dir(spec.destinationPath) }
+ }
+ )
+
+ @get:Internal // tracked by destinationDirs
+ abstract val destinationBaseDir: DirectoryProperty
+
+ @get:Nested
+ abstract val templateProjects: NamedDomainObjectContainer<DokkaTemplateProjectSpec>
+
+ @get:InputDirectory
+ abstract val dokkaSourceDir: DirectoryProperty
+
+ @get:InputFiles
+ val additionalFiles: FileCollection
+ get() = layout.files(
+ providers.provider {
+ templateProjects.map { it.additionalFiles }
+ }
+ )
+
+ init {
+ group = "dokka examples"
+ }
+
+ @TaskAction
+ internal fun action() {
+ val dokkaSourceDir = dokkaSourceDir.get()
+ val destinationBaseDir = destinationBaseDir.get()
+ val templateProjects = templateProjects.filter { it.destinationPath.isPresent }
+
+ templateProjects.forEach { spec ->
+ fs.sync {
+ with(spec.copySpec)
+
+ from(dokkaSourceDir.dir(spec.sourcePath))
+
+ from(
+ spec.additionalPaths.get().map { additionalPath ->
+ dokkaSourceDir.asFile.resolve(additionalPath)
+ }
+ )
+
+ from(spec.additionalFiles)
+
+ into(destinationBaseDir.dir(spec.destinationPath))
+ }
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/tasks/UpdateDokkatooExampleProjects.kt b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/tasks/UpdateDokkatooExampleProjects.kt
new file mode 100644
index 00000000..7737e098
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/tasks/UpdateDokkatooExampleProjects.kt
@@ -0,0 +1,49 @@
+package buildsrc.tasks
+
+import buildsrc.settings.DokkatooExampleProjectsSettings.DokkatooExampleProjectSpec
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.Nested
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Utility for updating the `gradle.properties` of projects used in automated tests.
+ */
+@CacheableTask
+abstract class UpdateDokkatooExampleProjects @Inject constructor(
+ @get:Internal
+ val objects: ObjectFactory
+) : DefaultTask() {
+
+ @get:Nested
+ abstract val exampleProjects: NamedDomainObjectContainer<DokkatooExampleProjectSpec>
+
+ private val taskPath: String = path // renamed for clarity
+
+ @TaskAction
+ fun update() {
+ exampleProjects.forEach { exampleProject ->
+ updateGradleProperties(exampleProject)
+ }
+ }
+
+ private fun updateGradleProperties(exampleProject: DokkatooExampleProjectSpec) {
+
+ val gradlePropertiesContent = exampleProject.gradlePropertiesContent.orNull?.sorted() ?: return
+
+ val content = buildString {
+ appendLine("# DO NOT EDIT - Generated by $taskPath")
+ appendLine()
+
+ gradlePropertiesContent.forEach {
+ appendLine(it)
+ }
+ }
+
+ exampleProject.gradlePropertiesFile.get().asFile.writeText(content)
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt
new file mode 100644
index 00000000..0af662d4
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt
@@ -0,0 +1,118 @@
+package buildsrc.utils
+
+import java.io.File
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.component.AdhocComponentWithVariants
+import org.gradle.api.file.RelativePath
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.plugins.ExtensionContainer
+import org.gradle.kotlin.dsl.*
+
+/**
+ * Mark this [Configuration] as one that will be consumed by other subprojects.
+ *
+ * ```
+ * isCanBeResolved = false
+ * isCanBeConsumed = true
+ * ```
+ */
+fun Configuration.asProvider(
+ visible: Boolean = true
+) {
+ isVisible = visible
+ isCanBeResolved = false
+ isCanBeConsumed = true
+}
+
+/**
+ * Mark this [Configuration] as one that will consume artifacts from other subprojects (also known as 'resolving')
+ *
+ * ```
+ * isCanBeResolved = true
+ * isCanBeConsumed = false
+ * ```
+ * */
+fun Configuration.asConsumer(
+ visible: Boolean = false
+) {
+ isVisible = visible
+ isCanBeResolved = true
+ isCanBeConsumed = false
+}
+
+
+/** Drop the first [count] directories from the path */
+fun RelativePath.dropDirectories(count: Int): RelativePath =
+ RelativePath(true, *segments.drop(count).toTypedArray())
+
+
+/** Drop the first directory from the path */
+fun RelativePath.dropDirectory(): RelativePath =
+ dropDirectories(1)
+
+
+/** Drop the first directory from the path */
+fun RelativePath.dropDirectoriesWhile(
+ segmentPrediate: (segment: String) -> Boolean
+): RelativePath =
+ RelativePath(
+ true,
+ *segments.dropWhile(segmentPrediate).toTypedArray(),
+ )
+
+
+/**
+ * Don't publish test fixtures (which causes warnings when publishing)
+ *
+ * https://docs.gradle.org/current/userguide/java_testing.html#publishing_test_fixtures
+ */
+fun Project.skipTestFixturesPublications() {
+ val javaComponent = components["java"] as AdhocComponentWithVariants
+ javaComponent.withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() }
+ javaComponent.withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() }
+}
+
+
+/**
+ * Add an extension to the [ExtensionContainer], and return the value.
+ *
+ * Adding an extension is especially useful for improving the DSL in build scripts when [T] is a
+ * [NamedDomainObjectContainer].
+ * Using an extension will allow Gradle to generate
+ * [type-safe model accessors](https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:accessor_applicability)
+ * for added types.
+ *
+ * ([name] should match the property name. This has to be done manually. I tried using a
+ * delegated-property provider but then Gradle can't introspect the types properly, so it fails to
+ * create accessors).
+ */
+internal inline fun <reified T : Any> ExtensionContainer.adding(
+ name: String,
+ value: T,
+): T {
+ add<T>(name, value)
+ return value
+}
+
+/**
+ * Create a new [NamedDomainObjectContainer], using
+ * [org.gradle.kotlin.dsl.domainObjectContainer]
+ * (but [T] is `reified`).
+ *
+ * @param[factory] an optional factory for creating elements
+ * @see org.gradle.kotlin.dsl.domainObjectContainer
+ */
+internal inline fun <reified T : Any> ObjectFactory.domainObjectContainer(
+ factory: NamedDomainObjectFactory<T>? = null
+): NamedDomainObjectContainer<T> =
+ if (factory == null) {
+ domainObjectContainer(T::class)
+ } else {
+ domainObjectContainer(T::class, factory)
+ }
+
+/** workaround for the overly verbose replacement for the deprecated [Project.getBuildDir] property */
+val Project.buildDir_: File get() = layout.buildDirectory.get().asFile
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/intellij.kt b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/intellij.kt
new file mode 100644
index 00000000..f93e7683
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/intellij.kt
@@ -0,0 +1,45 @@
+package buildsrc.utils
+
+import org.gradle.api.Project
+import org.gradle.api.file.ProjectLayout
+import org.gradle.plugins.ide.idea.model.IdeaModule
+
+
+/** exclude generated Gradle code, so it doesn't clog up search results */
+fun IdeaModule.excludeGeneratedGradleDsl(layout: ProjectLayout) {
+
+ val generatedSrcDirs = listOf(
+ "kotlin-dsl-accessors",
+ "kotlin-dsl-external-plugin-spec-builders",
+ "kotlin-dsl-plugins",
+ )
+
+ excludeDirs.addAll(
+ layout.projectDirectory.asFile.walk()
+ .filter { it.isDirectory && it.parentFile.name in generatedSrcDirs }
+ .flatMap { file ->
+ file.walk().maxDepth(1).filter { it.isDirectory }.toList()
+ }
+ )
+}
+
+
+/** Sets a logo for project IDEs */
+fun Project.initIdeProjectLogo(
+ svgLogoPath: String
+) {
+ val logoSvg = rootProject.layout.projectDirectory.file(svgLogoPath)
+ val ideaDir = rootProject.layout.projectDirectory.dir(".idea")
+
+ if (
+ logoSvg.asFile.exists()
+ && ideaDir.asFile.exists()
+ && !ideaDir.file("icon.png").asFile.exists()
+ && !ideaDir.file("icon.svg").asFile.exists()
+ ) {
+ copy {
+ from(logoSvg) { rename { "icon.svg" } }
+ into(ideaDir)
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/strings.kt b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/strings.kt
new file mode 100644
index 00000000..6a0749ce
--- /dev/null
+++ b/dokka-runners/dokkatoo/buildSrc/src/main/kotlin/buildsrc/utils/strings.kt
@@ -0,0 +1,26 @@
+package buildsrc.utils
+
+
+/** Title case the first char of a string */
+internal fun String.uppercaseFirstChar(): String = mapFirstChar(Character::toTitleCase)
+
+
+/** Lowercase the first char of a string */
+internal fun String.lowercaseFirstChar(): String = mapFirstChar(Character::toLowerCase)
+
+
+private inline fun String.mapFirstChar(
+ transform: (Char) -> Char
+): String = if (isNotEmpty()) transform(this[0]) + substring(1) else this
+
+
+/**
+ * Exclude all non-alphanumeric characters and converts the result into a camelCase string.
+ */
+internal fun String.toAlphaNumericCamelCase(): String =
+ map { if (it.isLetterOrDigit()) it else ' ' }
+ .joinToString("")
+ .split(" ")
+ .filter { it.isNotBlank() }
+ .joinToString("") { it.uppercaseFirstChar() }
+ .lowercaseFirstChar()
diff --git a/dokka-runners/dokkatoo/devOps/release.main.kts b/dokka-runners/dokkatoo/devOps/release.main.kts
new file mode 100644
index 00000000..a7555719
--- /dev/null
+++ b/dokka-runners/dokkatoo/devOps/release.main.kts
@@ -0,0 +1,415 @@
+#!/usr/bin/env kotlin
+@file:DependsOn("com.github.ajalt.clikt:clikt-jvm:3.5.2")
+@file:DependsOn("me.alllex.parsus:parsus-jvm:0.4.0")
+@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3")
+
+import Release_main.SemVer.Companion.SemVer
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.parameters.options.flag
+import com.github.ajalt.clikt.parameters.options.option
+import java.io.File
+import java.util.concurrent.TimeUnit.MINUTES
+import kotlin.system.exitProcess
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import me.alllex.parsus.parser.*
+import me.alllex.parsus.token.literalToken
+import me.alllex.parsus.token.regexToken
+
+try {
+ Release.main(args)
+ exitProcess(0)
+} catch (ex: Exception) {
+ println("${ex::class.simpleName}: ${ex.message}")
+ exitProcess(1)
+}
+
+/**
+ * Release a new version.
+ *
+ * Requires:
+ * * [gh cli](https://cli.github.com/manual/gh)
+ * * [kotlin](https://kotlinlang.org/docs/command-line.html)
+ * * [git](https://git-scm.com/)
+ */
+// based on https://github.com/apollographql/apollo-kotlin/blob/v4.0.0-dev.2/scripts/release.main.kts
+object Release : CliktCommand() {
+ private val skipGitValidation by option(
+ "--skip-git-validation",
+ help = "skips git status validation"
+ ).flag(default = false)
+
+ override fun run() {
+ echo("Current Dokkatoo version is $dokkatooVersion")
+ echo("git dir is ${Git.rootDir}")
+
+ val startBranch = Git.currentBranch()
+
+ validateGitStatus(startBranch)
+
+ val releaseVersion = semverPrompt(
+ text = "version to release?",
+ default = dokkatooVersion.copy(snapshot = false),
+ ) {
+ if (it.snapshot) {
+ echo("versionToRelease must not be a snapshot version, but was $it")
+ }
+ !it.snapshot
+ }
+ val nextVersion = semverPrompt(
+ text = "post-release version?",
+ default = releaseVersion.incrementMinor(snapshot = true),
+ )
+ updateVersionCreatePR(releaseVersion)
+
+ // switch back to the main branch
+ Git.switch(startBranch)
+ Git.pull(startBranch)
+
+ // Tag the release
+ createAndPushTag(releaseVersion)
+
+ confirm("Publish plugins to Gradle Plugin Portal?", abort = true)
+ Gradle.publishPlugins()
+
+ // Bump the version to the next snapshot
+ updateVersionCreatePR(nextVersion)
+
+ // Go back and pull the changes
+ Git.switch(startBranch)
+ Git.pull(startBranch)
+
+ echo("Released version $releaseVersion")
+ }
+
+ private fun validateGitStatus(startBranch: String) {
+ if (skipGitValidation) {
+ echo("skipping git status validation")
+ return
+ }
+ check(Git.status().isEmpty()) {
+ "git repo is not clean. Stash or commit changes before making a release."
+ }
+ check(dokkatooVersion.snapshot) {
+ "Current version must be a SNAPSHOT, but was $dokkatooVersion"
+ }
+ check(startBranch == "main") {
+ "Must be on the main branch to make a release, but current branch is $startBranch"
+ }
+ }
+
+ /**
+ * @param[validate] returns `null` if the provided SemVer is valid, or else an error message
+ * explaining why it is invalid.
+ */
+ private tailrec fun semverPrompt(
+ text: String,
+ default: SemVer,
+ validate: (candidate: SemVer) -> Boolean = { true },
+ ): SemVer {
+ val response = prompt(
+ text = text,
+ default = default.toString(),
+ requireConfirmation = true,
+ ) {
+ SemVer.of(it)
+ }
+
+ return if (response == null || !validate(response)) {
+ if (response == null) echo("invalid SemVer")
+ semverPrompt(text, default, validate)
+ } else {
+ response
+ }
+ }
+
+ private fun updateVersionCreatePR(version: SemVer) {
+ // checkout a release branch
+ val releaseBranch = "release/v$version"
+ echo("checkout out new branch...")
+ Git.switch(releaseBranch, create = true)
+
+ // update the version & run tests
+ dokkatooVersion = version
+ echo("running Gradle check...")
+ Gradle.check()
+
+ // commit and push
+ echo("committing...")
+ Git.commit("release $version")
+ echo("pushing...")
+ Git.push(releaseBranch)
+
+ // create a new PR
+ echo("creating PR...")
+ GitHub.createPr(releaseBranch)
+
+ confirm("Merge the PR for branch $releaseBranch?", abort = true)
+ mergeAndWait(releaseBranch)
+ echo("$releaseBranch PR merged")
+ }
+
+ private fun createAndPushTag(version: SemVer) {
+ // Tag the release
+ require(dokkatooVersion == version) {
+ "tried to create a tag, but project version does not match provided version. Expected $version but got $dokkatooVersion"
+ }
+ val tagName = "v$version"
+ Git.tag(tagName)
+ confirm("Push tag $tagName?", abort = true)
+ Git.push(tagName)
+ echo("Tag pushed")
+
+ confirm("Publish plugins to Gradle Plugin Portal?", abort = true)
+ Gradle.publishPlugins()
+ }
+
+ private val buildGradleKts: File by lazy {
+ val rootDir = Git.rootDir
+ File("$rootDir/build.gradle.kts").apply {
+ require(exists()) { "could not find build.gradle.kts in $rootDir" }
+ }
+ }
+
+ /** Read/write the version set in the root `build.gradle.kts` file */
+ private var dokkatooVersion: SemVer
+ get() {
+ val rawVersion = Gradle.dokkatooVersion()
+ return SemVer(rawVersion)
+ }
+ set(value) {
+ val updatedFile = buildGradleKts.useLines { lines ->
+ lines.joinToString(separator = "\n", postfix = "\n") { line ->
+ if (line.startsWith("version = ")) {
+ "version = \"${value}\""
+ } else {
+ line
+ }
+ }
+ }
+ buildGradleKts.writeText(updatedFile)
+ }
+
+ private fun mergeAndWait(branchName: String): Unit = runBlocking {
+ GitHub.autoMergePr(branchName)
+ echo("Waiting for the PR to be merged...")
+ while (GitHub.prState(branchName) != "MERGED") {
+ delay(1.seconds)
+ echo(".", trailingNewline = false)
+ }
+ }
+}
+
+private abstract class CliTool {
+
+ protected fun runCommand(
+ cmd: String,
+ dir: File? = Git.rootDir,
+ logOutput: Boolean = true,
+ ): String {
+ val args = parseSpaceSeparatedArgs(cmd)
+
+ val process = ProcessBuilder(args).apply {
+ redirectOutput(ProcessBuilder.Redirect.PIPE)
+ redirectInput(ProcessBuilder.Redirect.PIPE)
+ redirectErrorStream(true)
+ if (dir != null) directory(dir)
+ }.start()
+
+ val processOutput = process.inputStream
+ .bufferedReader()
+ .lineSequence()
+ .onEach { if (logOutput) println("\t$it") }
+ .joinToString("\n")
+ .trim()
+
+ process.waitFor(10, MINUTES)
+
+ val exitCode = process.exitValue()
+
+ if (exitCode != 0) {
+ error("command '$cmd' failed:\n${processOutput}")
+ }
+
+ return processOutput
+ }
+
+ private data class ProcessResult(
+ val exitCode: Int,
+ val output: String,
+ )
+
+ companion object {
+ private fun parseSpaceSeparatedArgs(argsString: String): List<String> {
+ val parsedArgs = mutableListOf<String>()
+ var inQuotes = false
+ var currentCharSequence = StringBuilder()
+ fun saveArg(wasInQuotes: Boolean) {
+ if (wasInQuotes || currentCharSequence.isNotBlank()) {
+ parsedArgs.add(currentCharSequence.toString())
+ currentCharSequence = StringBuilder()
+ }
+ }
+ argsString.forEach { char ->
+ if (char == '"') {
+ inQuotes = !inQuotes
+ // Save value which was in quotes.
+ if (!inQuotes) {
+ saveArg(true)
+ }
+ } else if (char.isWhitespace() && !inQuotes) {
+ // Space is separator
+ saveArg(false)
+ } else {
+ currentCharSequence.append(char)
+ }
+ }
+ if (inQuotes) {
+ error("No close-quote was found in $currentCharSequence.")
+ }
+ saveArg(false)
+ return parsedArgs
+ }
+ }
+}
+
+/** git commands */
+private object Git : CliTool() {
+ val rootDir = File(runCommand("git rev-parse --show-toplevel", dir = null))
+
+ init {
+ require(rootDir.exists()) { "could not determine root git directory" }
+ }
+
+ fun switch(branch: String, create: Boolean = false): String {
+ return runCommand(
+ buildString {
+ append("git switch ")
+ if (create) append("--create ")
+ append(branch)
+ }
+ )
+ }
+
+ fun commit(message: String): String = runCommand("git commit -a -m \"$message\"")
+ fun currentBranch(): String = runCommand("git symbolic-ref --short HEAD")
+ fun pull(ref: String): String = runCommand("git pull origin $ref")
+ fun push(ref: String): String = runCommand("git push origin $ref")
+ fun status(): String {
+ runCommand("git fetch --all")
+ return runCommand("git status --porcelain=v2")
+ }
+
+ fun tag(tag: String): String {
+ return runCommand("git tag $tag")
+ }
+}
+
+/** GitHub commands */
+private object GitHub : CliTool() {
+
+ init {
+ setRepo("adamko-dev/dokkatoo")
+ }
+
+ fun setRepo(repo: String): String =
+ runCommand("gh repo set-default $repo")
+
+ fun prState(branchName: String): String =
+ runCommand("gh pr view $branchName --json state --jq .state", logOutput = false)
+
+ fun createPr(branch: String): String =
+ runCommand("gh pr create --head $branch --fill")
+
+ fun autoMergePr(branch: String): String =
+ runCommand("gh pr merge $branch --squash --auto --delete-branch")
+
+ fun waitForPrChecks(branch: String): String =
+ runCommand("gh pr checks $branch --watch --interval 30")
+}
+
+/** GitHub commands */
+private object Gradle : CliTool() {
+
+ val gradlew: String
+
+ init {
+ val osName = System.getProperty("os.name").lowercase()
+ gradlew = if ("win" in osName) "./gradlew.bat" else "./gradlew"
+ }
+
+ fun stopDaemons(): String = runCommand("$gradlew --stop")
+
+ fun dokkatooVersion(): String {
+ stopDaemons()
+ return runCommand("$gradlew :dokkatooVersion --quiet --no-daemon")
+ }
+
+ fun check(): String {
+ stopDaemons()
+ return runCommand("$gradlew check --no-daemon")
+ }
+
+ fun publishPlugins(): String {
+ stopDaemons()
+ return runCommand("$gradlew publishPlugins --no-daemon --no-configuration-cache")
+ }
+}
+
+private data class SemVer(
+ val major: Int,
+ val minor: Int,
+ val patch: Int,
+ val snapshot: Boolean,
+) {
+
+ fun incrementMinor(snapshot: Boolean): SemVer =
+ copy(minor = minor + 1, snapshot = snapshot)
+
+ override fun toString(): String =
+ "$major.$minor.$patch" + if (snapshot) "-SNAPSHOT" else ""
+
+ companion object {
+ fun SemVer(input: String): SemVer =
+ SemVerParser.parseEntire(input).getOrElse { error ->
+ error("provided version to release must be SemVer X.Y.Z, but got error while parsing: $error")
+ }
+
+ fun of(input: String): SemVer? =
+ SemVerParser.parseEntire(input).getOrElse { return null }
+
+ fun isValid(input: String): Boolean =
+ try {
+ SemVerParser.parseEntireOrThrow(input)
+ true
+ } catch (ex: ParseException) {
+ false
+ }
+ }
+
+ private object SemVerParser : Grammar<SemVer>() {
+ private val dotSeparator by literalToken(".")
+ private val dashSeparator by literalToken("-")
+
+ /** Non-negative number that is either 0, or does not start with 0 */
+ private val number: Parser<Int> by regexToken("""0|[1-9]\d*""").map { it.text.toInt() }
+
+ private val snapshot by -dashSeparator * literalToken("SNAPSHOT")
+
+ override val root: Parser<SemVer> by parser {
+ val major = number()
+ dotSeparator()
+ val minor = number()
+ dotSeparator()
+ val patch = number()
+ val snapshot = checkPresent(snapshot)
+ SemVer(
+ major = major,
+ minor = minor,
+ patch = patch,
+ snapshot = snapshot,
+ )
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/.gitignore b/dokka-runners/dokkatoo/examples/.gitignore
new file mode 100644
index 00000000..4a1da3f8
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/.gitignore
@@ -0,0 +1,10 @@
+.idea
+**/gradle/wrapper/**
+gradlew.bat
+gradlew
+gradle.properties
+
+versioning-multimodule-example/dokka/previousDocVersions
+
+# these are test projects, so don't commit the lock file
+**/kotlin-js-store/yarn.lock
diff --git a/dokka-runners/dokkatoo/examples/README.md b/dokka-runners/dokkatoo/examples/README.md
new file mode 100644
index 00000000..f39abb5b
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/README.md
@@ -0,0 +1,18 @@
+# Dokkatoo Examples
+
+Examples of how to use Dokkatoo.
+
+The examples are copied from the Dokka project.
+
+### Set up
+
+The Dokka examples are synced automatically from the Dokka source code.
+
+### Tests
+
+The projects are tested in the
+[`:modules:dokkatoo-plugin-integration-tests`](./../modules/dokkatoo-plugin-integration-tests/)
+project.
+
+The Dokka Publications generated by the Dokka examples are compared against the Dokka
+Publications generated by the Dokkatoo projects.
diff --git a/dokka-runners/dokkatoo/examples/build.gradle.kts b/dokka-runners/dokkatoo/examples/build.gradle.kts
new file mode 100644
index 00000000..da366c88
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/build.gradle.kts
@@ -0,0 +1,48 @@
+plugins {
+ buildsrc.conventions.`maven-publish-test`
+ buildsrc.conventions.`dokkatoo-example-projects`
+}
+
+dokkaTemplateProjects {
+ register(
+ source = "examples/gradle/dokka-customFormat-example",
+ destination = "custom-format-example/dokka"
+ )
+ register(
+ source = "examples/gradle/dokka-gradle-example",
+ destination = "gradle-example/dokka"
+ )
+ register(
+ source = "examples/gradle/dokka-kotlinAsJava-example",
+ destination = "kotlin-as-java-example/dokka"
+ )
+ register(
+ source = "examples/gradle/dokka-library-publishing-example",
+ destination = "library-publishing-example/dokka"
+ )
+ register(
+ source = "examples/gradle/dokka-multimodule-example",
+ destination = "multimodule-example/dokka"
+ )
+ register(
+ source = "examples/gradle/dokka-multiplatform-example",
+ destination = "multiplatform-example/dokka"
+ )
+ register(
+ source = "examples/gradle/dokka-versioning-multimodule-example",
+ destination = "versioning-multimodule-example/dokka"
+ )
+}
+
+configurations.exampleProjectsElements.configure {
+ outgoing {
+ artifact(projectDir) {
+ builtBy(tasks.updateDokkatooExamples)
+ type = "directory"
+ }
+ }
+}
+
+dokkaSourceDownload {
+ dokkaVersion.set(libs.versions.kotlin.dokka)
+}
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokka/README.md b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/README.md
new file mode 100644
index 00000000..a25cd80e
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/README.md
@@ -0,0 +1,17 @@
+## Dokka custom format example
+
+This example demonstrates how to override `.css` styles and add custom images as assets, allowing
+you to change the logo used in the header.
+
+You can see up-to-date documentation generated for this example on
+[GitHub Pages](https://kotlin.github.io/dokka/examples/dokka-customFormat-example/html/index.html).
+
+![screenshot demonstration of output](demo.png)
+
+### Running
+
+Run `dokkaHtml` task to generate documentation with the custom logo in place:
+
+```bash
+./gradlew dokkaHtml
+```
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokka/build.gradle.kts b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/build.gradle.kts
new file mode 100644
index 00000000..27540ee6
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/build.gradle.kts
@@ -0,0 +1,35 @@
+import org.jetbrains.dokka.gradle.DokkaTask
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.base.DokkaBaseConfiguration
+
+plugins {
+ kotlin("jvm") version "1.9.0"
+ id("org.jetbrains.dokka") version "1.9.0"
+}
+
+buildscript {
+ dependencies {
+ classpath("org.jetbrains.dokka:dokka-base:1.9.0")
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+tasks.dokkaHtml {
+ pluginConfiguration<DokkaBase, DokkaBaseConfiguration> {
+ // Dokka's stylesheets and assets with conflicting names will be overriden.
+ // In this particular case, logo-styles.css will be overriden and ktor-logo.png will
+ // be added as an additional image asset
+ customStyleSheets = listOf(file("logo-styles.css"))
+ customAssets = listOf(file("ktor-logo.png"))
+
+ // Text used in the footer
+ footerMessage = "(c) Custom Format Dokka example"
+ }
+}
+
+dependencies {
+ testImplementation(kotlin("test-junit"))
+}
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokka/demo.png b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/demo.png
new file mode 100644
index 00000000..8f9b88b0
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/demo.png
Binary files differ
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokka/ktor-logo.png b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/ktor-logo.png
new file mode 100644
index 00000000..ef943896
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/ktor-logo.png
Binary files differ
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokka/logo-styles.css b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/logo-styles.css
new file mode 100644
index 00000000..ffe4d503
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/logo-styles.css
@@ -0,0 +1,20 @@
+/*
+ * All Margins and sizes are custom for the ktor-logo.png file.
+ * You may need to override it and find what works best for your case.
+ */
+:root {
+ --dokka-logo-image-url: url('../images/ktor-logo.png');
+ --dokka-logo-height: 125px;
+ --dokka-logo-width: 50px;
+}
+
+/* link custom rules styles */
+.library-name--link {
+ /* ... */
+}
+
+/* logo custom rules styles */
+.library-name--link::before {
+ background-position: left;
+ width: 52px;
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/settings.gradle.kts
new file mode 100644
index 00000000..9855e823
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "dokka-customFormat-example"
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokka/src/main/kotlin/demo/HelloWorld.kt b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/src/main/kotlin/demo/HelloWorld.kt
new file mode 100644
index 00000000..172e18f7
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokka/src/main/kotlin/demo/HelloWorld.kt
@@ -0,0 +1,20 @@
+package demo
+
+/**
+ * This class supports greeting people by name.
+ *
+ * @property name The name of the person to be greeted.
+ */
+class Greeter(val name: String) {
+
+ /**
+ * Prints the greeting to the standard output.
+ */
+ fun greet() {
+ println("Hello $name!")
+ }
+}
+
+fun main(args: Array<String>) {
+ Greeter(args[0]).greet()
+}
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/build.gradle.kts b/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/build.gradle.kts
new file mode 100644
index 00000000..7832a8f9
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/build.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ kotlin("jvm") version "1.9.0"
+ id("org.jetbrains.dokka.dokkatoo") version "2.1.0-SNAPSHOT"
+}
+
+dokkatoo {
+ moduleName.set("customFormat-example")
+ pluginsConfiguration.html {
+ // Dokka's stylesheets and assets with conflicting names will be overridden.
+ // In this particular case, logo-styles.css will be overridden
+ // and ktor-logo.png will be added as an additional image asset
+ customStyleSheets.from("logo-styles.css")
+ customAssets.from("ktor-logo.png")
+
+ // Text used in the footer
+ footerMessage.set("(c) Custom Format Dokka example")
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/ktor-logo.png b/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/ktor-logo.png
new file mode 100644
index 00000000..ef943896
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/ktor-logo.png
Binary files differ
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/logo-styles.css b/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/logo-styles.css
new file mode 100644
index 00000000..ffe4d503
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/logo-styles.css
@@ -0,0 +1,20 @@
+/*
+ * All Margins and sizes are custom for the ktor-logo.png file.
+ * You may need to override it and find what works best for your case.
+ */
+:root {
+ --dokka-logo-image-url: url('../images/ktor-logo.png');
+ --dokka-logo-height: 125px;
+ --dokka-logo-width: 50px;
+}
+
+/* link custom rules styles */
+.library-name--link {
+ /* ... */
+}
+
+/* logo custom rules styles */
+.library-name--link::before {
+ background-position: left;
+ width: 52px;
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..31ad8c91
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "custom-format-example"
+
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/src/main/kotlin/demo/HelloWorld.kt b/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/src/main/kotlin/demo/HelloWorld.kt
new file mode 100644
index 00000000..172e18f7
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/custom-format-example/dokkatoo/src/main/kotlin/demo/HelloWorld.kt
@@ -0,0 +1,20 @@
+package demo
+
+/**
+ * This class supports greeting people by name.
+ *
+ * @property name The name of the person to be greeted.
+ */
+class Greeter(val name: String) {
+
+ /**
+ * Prints the greeting to the standard output.
+ */
+ fun greet() {
+ println("Hello $name!")
+ }
+}
+
+fun main(args: Array<String>) {
+ Greeter(args[0]).greet()
+}
diff --git a/dokka-runners/dokkatoo/examples/gradle-example/dokka/Module.md b/dokka-runners/dokkatoo/examples/gradle-example/dokka/Module.md
new file mode 100644
index 00000000..0d051cb1
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/gradle-example/dokka/Module.md
@@ -0,0 +1,7 @@
+# Module Dokka Gradle Example
+
+This is an example of how you can write module documentation with Dokka.
+
+# Package demo
+
+This package contains a few examples of Dokka usage.
diff --git a/dokka-runners/dokkatoo/examples/gradle-example/dokka/README.md b/dokka-runners/dokkatoo/examples/gradle-example/dokka/README.md
new file mode 100644
index 00000000..3401e8e9
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/gradle-example/dokka/README.md
@@ -0,0 +1,22 @@
+# Dokka Gradle example
+
+This example demonstrates how to apply Dokka in a simple single-project Gradle build, as well as how to configure it.
+
+Configuration changes:
+
+* Custom project name used in the header, `Dokka Gradle Example`.
+* Description for the project and the packages taken from [Module.md](Module.md).
+* Documentation contains source links that lead to declarations in this GitHub repository.
+
+You can see up-to-date documentation generated for this example on
+[GitHub Pages](https://kotlin.github.io/dokka/examples/dokka-gradle-example/html/index.html).
+
+![screenshot demonstration of output](demo.png)
+
+### Running
+
+Run `dokkaHtml` task to generate documentation for this example:
+
+```bash
+./gradlew dokkaHtml
+```
diff --git a/dokka-runners/dokkatoo/examples/gradle-example/dokka/build.gradle.kts b/dokka-runners/dokkatoo/examples/gradle-example/dokka/build.gradle.kts
new file mode 100644
index 00000000..5f54ad9d
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/gradle-example/dokka/build.gradle.kts
@@ -0,0 +1,37 @@
+import org.jetbrains.dokka.gradle.DokkaTask
+import java.net.URL
+
+plugins {
+ kotlin("jvm") version "1.9.0"
+ id("org.jetbrains.dokka") version "1.9.0"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation(kotlin("test-junit"))
+}
+
+tasks.withType<DokkaTask>().configureEach {
+ dokkaSourceSets {
+ named("main") {
+ // used as project name in the header
+ moduleName.set("Dokka Gradle Example")
+
+ // contains descriptions for the module and the packages
+ includes.from("Module.md")
+
+ // adds source links that lead to this repository, allowing readers
+ // to easily find source code for inspected declarations
+ sourceLink {
+ localDirectory.set(file("src/main/kotlin"))
+ remoteUrl.set(URL("https://github.com/Kotlin/dokka/tree/master/" +
+ "examples/gradle/dokka-gradle-example/src/main/kotlin"
+ ))
+ remoteLineSuffix.set("#L")
+ }
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/gradle-example/dokka/demo.png b/dokka-runners/dokkatoo/examples/gradle-example/dokka/demo.png
new file mode 100644
index 00000000..4462f3b5
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/gradle-example/dokka/demo.png
Binary files differ
diff --git a/dokka-runners/dokkatoo/examples/gradle-example/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/examples/gradle-example/dokka/settings.gradle.kts
new file mode 100644
index 00000000..5b8c3c92
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/gradle-example/dokka/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "dokka-gradle-example"
diff --git a/dokka-runners/dokkatoo/examples/gradle-example/dokka/src/main/kotlin/demo/HelloWorld.kt b/dokka-runners/dokkatoo/examples/gradle-example/dokka/src/main/kotlin/demo/HelloWorld.kt
new file mode 100644
index 00000000..172e18f7
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/gradle-example/dokka/src/main/kotlin/demo/HelloWorld.kt
@@ -0,0 +1,20 @@
+package demo
+
+/**
+ * This class supports greeting people by name.
+ *
+ * @property name The name of the person to be greeted.
+ */
+class Greeter(val name: String) {
+
+ /**
+ * Prints the greeting to the standard output.
+ */
+ fun greet() {
+ println("Hello $name!")
+ }
+}
+
+fun main(args: Array<String>) {
+ Greeter(args[0]).greet()
+}
diff --git a/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/Module.md b/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/Module.md
new file mode 100644
index 00000000..0d051cb1
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/Module.md
@@ -0,0 +1,7 @@
+# Module Dokka Gradle Example
+
+This is an example of how you can write module documentation with Dokka.
+
+# Package demo
+
+This package contains a few examples of Dokka usage.
diff --git a/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/build.gradle.kts b/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/build.gradle.kts
new file mode 100644
index 00000000..2cfc30bd
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/build.gradle.kts
@@ -0,0 +1,23 @@
+plugins {
+ kotlin("jvm") version "1.9.0"
+ id("org.jetbrains.dokka.dokkatoo") version "2.1.0-SNAPSHOT"
+}
+
+dokkatoo {
+ // used as project name in the header
+ moduleName.set("Dokka Gradle Example")
+
+ dokkatooSourceSets.main {
+
+ // contains descriptions for the module and the packages
+ includes.from("Module.md")
+
+ // adds source links that lead to this repository, allowing readers
+ // to easily find source code for inspected declarations
+ sourceLink {
+ localDirectory.set(file("src/main/kotlin"))
+ remoteUrl("https://github.com/Kotlin/dokka/tree/master/examples/gradle/dokka-gradle-example/src/main/kotlin")
+ remoteLineSuffix.set("#L")
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..02c75bfb
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "gradle-example"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/src/main/kotlin/demo/HelloWorld.kt b/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/src/main/kotlin/demo/HelloWorld.kt
new file mode 100644
index 00000000..172e18f7
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/gradle-example/dokkatoo/src/main/kotlin/demo/HelloWorld.kt
@@ -0,0 +1,20 @@
+package demo
+
+/**
+ * This class supports greeting people by name.
+ *
+ * @property name The name of the person to be greeted.
+ */
+class Greeter(val name: String) {
+
+ /**
+ * Prints the greeting to the standard output.
+ */
+ fun greet() {
+ println("Hello $name!")
+ }
+}
+
+fun main(args: Array<String>) {
+ Greeter(args[0]).greet()
+}
diff --git a/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/README.md b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/README.md
new file mode 100644
index 00000000..647b0e6d
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/README.md
@@ -0,0 +1,19 @@
+# Dokka Kotlin-as-Java plugin example
+
+This example demonstrates how you can apply a Dokka plugin in a simple Gradle project.
+
+In particular, it applies [Kotlin as Java](../../../plugins/kotlin-as-java) Dokka plugin that renders all
+Kotlin signatures as Java signatures.
+
+You can see up-to-date documentation generated for this example on
+[GitHub Pages](https://kotlin.github.io/dokka/examples/dokka-kotlinAsJava-example/html/index.html).
+
+![screenshot demonstration of output](demo.png)
+
+### Running
+
+Run `dokkaHtml` task to generate documentation for this example:
+
+```bash
+./gradlew dokkaHtml
+```
diff --git a/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/build.gradle.kts b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/build.gradle.kts
new file mode 100644
index 00000000..b339cec3
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ kotlin("jvm") version "1.9.0"
+ id("org.jetbrains.dokka") version "1.9.0"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation(kotlin("test-junit"))
+
+ // Will apply the plugin to all Dokka tasks
+ dokkaPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:1.9.0")
+
+ // Will apply the plugin only to the `:dokkaHtml` task
+ //dokkaHtmlPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:1.9.0")
+
+ // Will apply the plugin only to the `:dokkaGfm` task
+ //dokkaGfmPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:1.9.0")
+}
diff --git a/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/demo.png b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/demo.png
new file mode 100644
index 00000000..92f1170f
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/demo.png
Binary files differ
diff --git a/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/settings.gradle.kts
new file mode 100644
index 00000000..0a0b8c0e
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "dokka-kotlinAsJava-example"
diff --git a/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/src/main/kotlin/demo/HelloWorld.kt b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/src/main/kotlin/demo/HelloWorld.kt
new file mode 100644
index 00000000..172e18f7
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokka/src/main/kotlin/demo/HelloWorld.kt
@@ -0,0 +1,20 @@
+package demo
+
+/**
+ * This class supports greeting people by name.
+ *
+ * @property name The name of the person to be greeted.
+ */
+class Greeter(val name: String) {
+
+ /**
+ * Prints the greeting to the standard output.
+ */
+ fun greet() {
+ println("Hello $name!")
+ }
+}
+
+fun main(args: Array<String>) {
+ Greeter(args[0]).greet()
+}
diff --git a/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..019ac938
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/kotlin-as-java-example/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "kotlin-as-java-example"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/README.md b/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/README.md
new file mode 100644
index 00000000..60d967b8
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/README.md
@@ -0,0 +1,41 @@
+# Dokka Library publishing example
+
+This example demonstrates how you can integrate Dokka into the publishing process of your library, adding
+documentation generated by Dokka as artifacts.
+
+This is useful because some repositories, like Maven Central, require documentation (`javadoc.jar`) to be published
+alongside library artifacts.
+
+You can also use services like [javadoc.io](https://javadoc.io/) to host of your library's API documentation for free
+and without any additional setup - it will take documentation pages straight from the published artifact. It works with
+both HTML and Javadoc formats as demonstrated by
+[com.trib3's Javadocs](https://javadoc.io/doc/com.trib3/server/latest/index.html).
+
+## Running
+
+Run `dokkaHtml` task to generate documentation for this example:
+
+```bash
+./gradlew dokkaHtml
+```
+
+### Javadoc jar
+
+Run `dokkaJavadocJar` task to create a jar file that contains documentation generated in Dokka's Javadoc format.
+
+```Bash
+./gradlew dokkaJavadocJar
+```
+
+After that, you can find the jar under `build/libs/dokka-library-publishing-example-javadoc.jar`
+
+### HTML jar
+
+
+Run `dokkaHtmlJar` task to create a jar file that contains documentation generated in Dokka's HTML format.
+
+```Bash
+./gradlew dokkaHtmlJar
+```
+
+After that, you can find the jar under `build/libs/dokka-library-publishing-example-html.jar`
diff --git a/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/build.gradle.kts b/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/build.gradle.kts
new file mode 100644
index 00000000..731de85b
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/build.gradle.kts
@@ -0,0 +1,39 @@
+plugins {
+ kotlin("jvm") version "1.9.0"
+ id("org.jetbrains.dokka") version "1.9.0"
+ `java-library`
+ `maven-publish`
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation(kotlin("test-junit"))
+}
+
+val dokkaJavadocJar by tasks.register<Jar>("dokkaJavadocJar") {
+ dependsOn(tasks.dokkaJavadoc)
+ from(tasks.dokkaJavadoc.flatMap { it.outputDirectory })
+ archiveClassifier.set("javadoc")
+}
+
+val dokkaHtmlJar by tasks.register<Jar>("dokkaHtmlJar") {
+ dependsOn(tasks.dokkaHtml)
+ from(tasks.dokkaHtml.flatMap { it.outputDirectory })
+ archiveClassifier.set("html-doc")
+}
+
+publishing {
+ publications {
+ register<MavenPublication>("library") {
+ from(components["java"])
+ version = "1.0.0"
+ groupId = "demo"
+ artifactId = "dokka-library-publishing-example"
+ artifact(dokkaJavadocJar)
+ artifact(dokkaHtmlJar)
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/settings.gradle.kts
new file mode 100644
index 00000000..e0847ac9
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "dokka-library-publishing-example"
diff --git a/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/src/main/kotlin/demo/HelloWorld.kt b/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/src/main/kotlin/demo/HelloWorld.kt
new file mode 100644
index 00000000..172e18f7
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/library-publishing-example/dokka/src/main/kotlin/demo/HelloWorld.kt
@@ -0,0 +1,20 @@
+package demo
+
+/**
+ * This class supports greeting people by name.
+ *
+ * @property name The name of the person to be greeted.
+ */
+class Greeter(val name: String) {
+
+ /**
+ * Prints the greeting to the standard output.
+ */
+ fun greet() {
+ println("Hello $name!")
+ }
+}
+
+fun main(args: Array<String>) {
+ Greeter(args[0]).greet()
+}
diff --git a/dokka-runners/dokkatoo/examples/library-publishing-example/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/examples/library-publishing-example/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..198cfbdf
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/library-publishing-example/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "library-publishing-example"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/README.md b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/README.md
new file mode 100644
index 00000000..c8b224ec
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/README.md
@@ -0,0 +1,25 @@
+# Dokka MultiModule example
+
+This example demonstrates how to apply and configure Dokka in a
+[multi-project build](https://docs.gradle.org/current/userguide/multi_project_builds.html).
+
+You can also learn how to set Dokka's version in [gradle.properties](gradle.properties) using `pluginManagement`
+configuration block in [settings.gradle.kts](settings.gradle.kts).
+
+____
+
+You can see up-to-date documentation generated for this example on
+[GitHub Pages](https://kotlin.github.io/dokka/examples/dokka-multimodule-example/htmlMultiModule/index.html).
+
+![screenshot demonstration of output](demo.png)
+
+### Running
+
+Run `dokkaHtmlMultiModule` task to generate documentation for this example:
+
+```bash
+./gradlew dokkaHtmlMultiModule
+```
+
+It will generate complete documentation for the root project and its subprojects, with a common
+Table of Contents.
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/build.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/build.gradle.kts
new file mode 100644
index 00000000..6b416abc
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/build.gradle.kts
@@ -0,0 +1,5 @@
+subprojects {
+ repositories {
+ mavenCentral()
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/demo.png b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/demo.png
new file mode 100644
index 00000000..d25576b8
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/demo.png
Binary files differ
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/build.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/build.gradle.kts
new file mode 100644
index 00000000..3563ecca
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/build.gradle.kts
@@ -0,0 +1,38 @@
+import org.jetbrains.dokka.DokkaConfiguration.Visibility
+import org.jetbrains.dokka.gradle.DokkaTaskPartial
+import java.net.URL
+
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
+
+// You can apply and configure Dokka in each subproject
+// individially or configure all subprojects at once
+subprojects {
+ apply(plugin = "org.jetbrains.dokka")
+
+ tasks.withType<DokkaTaskPartial>().configureEach {
+ dokkaSourceSets.configureEach {
+ documentedVisibilities.set(setOf(
+ Visibility.PUBLIC,
+ Visibility.PROTECTED
+ ))
+
+ // Read docs for more details: https://kotlinlang.org/docs/dokka-gradle.html#source-link-configuration
+ sourceLink {
+ val exampleDir = "https://github.com/Kotlin/dokka/tree/master/examples/gradle/dokka-multimodule-example"
+
+ localDirectory.set(rootProject.projectDir)
+ remoteUrl.set(URL("$exampleDir"))
+ remoteLineSuffix.set("#L")
+ }
+ }
+ }
+}
+
+// Configures only the parent MultiModule task,
+// this will not affect subprojects
+tasks.dokkaHtmlMultiModule {
+ moduleName.set("Dokka MultiModule Example")
+}
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/ModuleA.md b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/ModuleA.md
new file mode 100644
index 00000000..12712d97
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/ModuleA.md
@@ -0,0 +1,5 @@
+# Module childProjectA
+This is the child module A
+
+# Package demo
+This package contains a few examples of Dokka usage.
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/build.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/build.gradle.kts
new file mode 100644
index 00000000..7b3b1e23
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/build.gradle.kts
@@ -0,0 +1,16 @@
+import org.jetbrains.dokka.gradle.DokkaTaskPartial
+
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
+
+// configuration specific to this subproject.
+// notice the use of Partial task
+tasks.withType<DokkaTaskPartial>().configureEach {
+ dokkaSourceSets {
+ configureEach {
+ includes.from("ModuleA.md")
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt
new file mode 100644
index 00000000..533b305c
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt
@@ -0,0 +1,8 @@
+@file:Suppress("unused")
+
+package demo
+
+/**
+ * Class defined in child project a
+ */
+class ChildProjectAClass
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/ModuleB.md b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/ModuleB.md
new file mode 100644
index 00000000..18a92a33
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/ModuleB.md
@@ -0,0 +1,5 @@
+# Module childProjectB
+This is the child module B
+
+# Package demo
+This package contains a few examples of Dokka usage.
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/build.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/build.gradle.kts
new file mode 100644
index 00000000..e8b40d4a
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/build.gradle.kts
@@ -0,0 +1,16 @@
+import org.jetbrains.dokka.gradle.DokkaTaskPartial
+
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
+
+// configuration specific to this subproject.
+// notice the use of Partial task
+tasks.withType<DokkaTaskPartial>().configureEach {
+ dokkaSourceSets {
+ configureEach {
+ includes.from("ModuleB.md")
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt
new file mode 100644
index 00000000..6bfd22eb
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt
@@ -0,0 +1,8 @@
+@file:Suppress("unused")
+
+package demo
+
+/**
+ * Class defined in child module b
+ */
+class ChildProjectBClass
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/settings.gradle.kts
new file mode 100644
index 00000000..9844b3cc
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokka/settings.gradle.kts
@@ -0,0 +1,15 @@
+pluginManagement {
+ val kotlinVersion: String by settings
+ val dokkaVersion: String by settings
+
+ plugins {
+ kotlin("jvm") version kotlinVersion
+ id("org.jetbrains.dokka") version dokkaVersion
+ }
+}
+
+include(":parentProject")
+include(":parentProject:childProjectA")
+include(":parentProject:childProjectB")
+
+rootProject.name = "dokka-multimodule-example"
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/build.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/build.gradle.kts
new file mode 100644
index 00000000..4ee5b079
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/build.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ `kotlin-dsl`
+}
+
+dependencies {
+ implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
+ implementation("org.jetbrains.dokka.dokkatoo:dokkatoo-plugin:2.1.0-SNAPSHOT")
+}
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/settings.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/settings.gradle.kts
new file mode 100644
index 00000000..b7510ccf
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/settings.gradle.kts
@@ -0,0 +1,21 @@
+rootProject.name = "buildSrc"
+
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+
+ repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
+
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/src/main/kotlin/dokka-convention.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/src/main/kotlin/dokka-convention.gradle.kts
new file mode 100644
index 00000000..04970e8a
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/buildSrc/src/main/kotlin/dokka-convention.gradle.kts
@@ -0,0 +1,17 @@
+/**
+ * Common conventions for generating documentation with Dokkatoo.
+ */
+
+plugins {
+ id("org.jetbrains.dokka.dokkatoo")
+}
+
+dokkatoo {
+ dokkatooSourceSets.configureEach {
+ sourceLink {
+ // Read docs for more details: https://kotlinlang.org/docs/dokka-gradle.html#source-link-configuration
+ remoteUrl("https://github.com/Kotlin/dokka/tree/master/examples/gradle/dokka-multimodule-example")
+ localDirectory.set(rootDir)
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/build.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/build.gradle.kts
new file mode 100644
index 00000000..2a698a7b
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/build.gradle.kts
@@ -0,0 +1,23 @@
+plugins {
+ kotlin("jvm") apply false
+ `dokka-convention`
+}
+
+dependencies {
+ dokkatoo(project(":parentProject:childProjectA"))
+ dokkatoo(project(":parentProject:childProjectB"))
+ dokkatooPluginHtml(
+ dokkatoo.versions.jetbrainsDokka.map { dokkaVersion ->
+ "org.jetbrains.dokka:all-modules-page-plugin:$dokkaVersion"
+ }
+ )
+ dokkatooPluginHtml(
+ dokkatoo.versions.jetbrainsDokka.map { dokkaVersion ->
+ "org.jetbrains.dokka:templating-plugin:$dokkaVersion"
+ }
+ )
+}
+
+dokkatoo {
+ moduleName.set("Dokka MultiModule Example")
+}
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/ModuleA.md b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/ModuleA.md
new file mode 100644
index 00000000..12712d97
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/ModuleA.md
@@ -0,0 +1,5 @@
+# Module childProjectA
+This is the child module A
+
+# Package demo
+This package contains a few examples of Dokka usage.
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/build.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/build.gradle.kts
new file mode 100644
index 00000000..51c17118
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ kotlin("jvm")
+ `dokka-convention`
+}
+
+dokkatoo {
+ dokkatooSourceSets.configureEach {
+ includes.from("ModuleA.md")
+ }
+}
+
+//region DON'T COPY - this is only needed for internal Dokkatoo integration tests
+dokkatoo {
+ modulePath.set("childProjectA") // match the original dokka default
+}
+tasks.withType<org.jetbrains.dokka.dokkatoo.tasks.DokkatooGenerateTask>().configureEach {
+ generator.dokkaSourceSets.configureEach {
+ sourceSetScope.set(":parentProject:childProjectA:dokkaHtmlPartial")
+ }
+}
+//endregion
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt
new file mode 100644
index 00000000..533b305c
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt
@@ -0,0 +1,8 @@
+@file:Suppress("unused")
+
+package demo
+
+/**
+ * Class defined in child project a
+ */
+class ChildProjectAClass
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/ModuleB.md b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/ModuleB.md
new file mode 100644
index 00000000..18a92a33
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/ModuleB.md
@@ -0,0 +1,5 @@
+# Module childProjectB
+This is the child module B
+
+# Package demo
+This package contains a few examples of Dokka usage.
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/build.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/build.gradle.kts
new file mode 100644
index 00000000..69e066bb
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ kotlin("jvm")
+ `dokka-convention`
+}
+
+dokkatoo {
+ dokkatooSourceSets.configureEach {
+ includes.from("ModuleB.md")
+ }
+}
+
+//region DON'T COPY - this is only needed for internal Dokkatoo integration tests
+dokkatoo {
+ modulePath.set("childProjectB") // match the original dokka default
+}
+tasks.withType<org.jetbrains.dokka.dokkatoo.tasks.DokkatooGenerateTask>().configureEach {
+ generator.dokkaSourceSets.configureEach {
+ sourceSetScope.set(":parentProject:childProjectB:dokkaHtmlPartial")
+ }
+}
+//endregion
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt
new file mode 100644
index 00000000..6bfd22eb
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt
@@ -0,0 +1,8 @@
+@file:Suppress("unused")
+
+package demo
+
+/**
+ * Class defined in child module b
+ */
+class ChildProjectBClass
diff --git a/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..a091e8cc
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multimodule-example/dokkatoo/settings.gradle.kts
@@ -0,0 +1,21 @@
+rootProject.name = "dokkatoo-multimodule-example"
+
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+include(":parentProject")
+include(":parentProject:childProjectA")
+include(":parentProject:childProjectB")
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/README.md b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/README.md
new file mode 100644
index 00000000..9b8a85e6
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/README.md
@@ -0,0 +1,29 @@
+# Dokka Multiplatform example
+
+This example demonstrates Dokka's configuration and output for a simple
+[Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) project.
+
+It contains [Kotlin source sets](https://kotlinlang.org/docs/multiplatform-discover-project.html#source-sets) for different
+platforms that are automatically picked up by Dokka from the Kotlin Gradle Plugin, and an additional custom source
+set known to Dokka only.
+
+The example demonstrates the following things:
+
+* Documentation for common code
+* Documentation for expect/actual declarations available via tabs
+* Documentation for platform-specific declarations, including functions from different source sets, but
+ with clashing names
+* Use of Platform-specific API, such as `CPointer` from `kotlinx.cinterop`
+
+You can see up-to-date documentation generated for this example on
+[GitHub Pages](https://kotlin.github.io/dokka/examples/dokka-multiplatform-example/html/index.html).
+
+![screenshot demonstration of output](demo.png)
+
+### Running
+
+Run `dokkaHtml` task in order to generate documentation for this example:
+
+```bash
+./gradlew dokkaHtml
+```
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/build.gradle.kts b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/build.gradle.kts
new file mode 100644
index 00000000..7c5f11cf
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/build.gradle.kts
@@ -0,0 +1,42 @@
+@file:Suppress("UNUSED_VARIABLE")
+
+import org.jetbrains.dokka.gradle.DokkaTask
+import org.jetbrains.dokka.Platform
+
+plugins {
+ kotlin("multiplatform") version "1.9.0"
+ id("org.jetbrains.dokka") version "1.9.0"
+}
+
+repositories {
+ mavenCentral()
+}
+
+group = "org.dokka.example"
+version = "1.0-SNAPSHOT"
+
+kotlin {
+ jvm() // Creates a JVM target with the default name "jvm"
+ linuxX64("linux")
+ macosX64("macos")
+ js()
+ sourceSets {
+ val commonMain by getting {
+ dependencies {
+ implementation(kotlin("stdlib-common"))
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
+ }
+ }
+ }
+}
+
+tasks.withType<DokkaTask>().configureEach {
+ dokkaSourceSets {
+ // Create a custom source set not known to the Kotlin Gradle Plugin
+ register("customSourceSet") {
+ this.jdkVersion.set(9)
+ this.displayName.set("custom")
+ this.sourceRoots.from(file("src/customJdk9/kotlin"))
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/demo.png b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/demo.png
new file mode 100644
index 00000000..58798ccf
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/demo.png
Binary files differ
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/settings.gradle.kts
new file mode 100644
index 00000000..e9daf094
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/settings.gradle.kts
@@ -0,0 +1,2 @@
+rootProject.name = "dokka-multiplatform-example"
+
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/CommonCoroutineExtensions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/CommonCoroutineExtensions.kt
new file mode 100644
index 00000000..30bea657
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/CommonCoroutineExtensions.kt
@@ -0,0 +1,15 @@
+package org.kotlintestmpp.coroutines
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Deferred
+
+/**
+ * Common `expect` declaration
+ */
+expect fun <T> CoroutineScope.asyncWithDealy(delay: Long, block: suspend () -> T): Deferred<T>
+
+/**
+ * Common coroutine extension
+ */
+fun CoroutineDispatcher.name(): String = TODO("Not implemented")
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/CommonDateUtils.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/CommonDateUtils.kt
new file mode 100644
index 00000000..b241f5ea
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/CommonDateUtils.kt
@@ -0,0 +1,14 @@
+package org.kotlintestmpp.date
+
+/**
+ * Common `expect` declaration
+ */
+expect fun getCurrentDate(): String
+
+/**
+ * Common date util function
+ */
+fun getDate(): String {
+ return "Today's Date is ${getCurrentDate()}"
+}
+
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/common/Foo.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/common/Foo.kt
new file mode 100644
index 00000000..96c825c5
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/commonMain/kotlin/org/kotlintestmpp/common/Foo.kt
@@ -0,0 +1,7 @@
+package org.kotlintestmpp.common
+
+/**
+ * Common Foo class
+ */
+class Foo {}
+
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/customJdk9/kotlin/org/kotlintest/jdk9/CustomSourceSetFile.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/customJdk9/kotlin/org/kotlintest/jdk9/CustomSourceSetFile.kt
new file mode 100644
index 00000000..d77b959b
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/customJdk9/kotlin/org/kotlintest/jdk9/CustomSourceSetFile.kt
@@ -0,0 +1,11 @@
+package org.kotlintest.jdk9
+
+/**
+ * This class demonstrates custom dokka source sets
+ */
+class CustomSourceSetFile {
+ /**
+ * This function will not do anything
+ */
+ fun thisIsAFunction() {}
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsCoroutineExtensions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsCoroutineExtensions.kt
new file mode 100644
index 00000000..85d6beb0
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsCoroutineExtensions.kt
@@ -0,0 +1,11 @@
+package org.kotlintestmpp.coroutines
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+
+/**
+ * JS actual implementation for `asyncWithDelay`
+ */
+actual fun <T> CoroutineScope.asyncWithDealy(delay: Long, block: suspend () -> T): Deferred<T> {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsDateUtils.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsDateUtils.kt
new file mode 100644
index 00000000..2f4f3c45
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsDateUtils.kt
@@ -0,0 +1,8 @@
+package org.kotlintestmpp.date
+
+/**
+ * JS actual implementation for `getCurrentDate`
+ */
+actual fun getCurrentDate(): String {
+ return "test"
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsFunctions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsFunctions.kt
new file mode 100644
index 00000000..76757359
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jsMain/kotlin/org/kotlintestmpp/JsFunctions.kt
@@ -0,0 +1,18 @@
+package org.kotlintestmpp
+
+/**
+ * Function declares in JS source set
+ */
+fun js() {}
+
+/**
+ * Function declared in JS source set.
+ *
+ * Function with the same name exists in another source set as well.
+ */
+fun shared() {}
+
+/**
+ * Extension declared in JS source set
+ */
+fun String.myExtension() = println("test")
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JavaAnnotation.java b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JavaAnnotation.java
new file mode 100644
index 00000000..8b11ca09
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JavaAnnotation.java
@@ -0,0 +1,19 @@
+package org.kotlintestmpp;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This is a Java annotation
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JavaAnnotation {
+ String usage();
+
+ String[] aliases();
+
+ String description();
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmCoroutineExtensions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmCoroutineExtensions.kt
new file mode 100644
index 00000000..8f7fda49
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmCoroutineExtensions.kt
@@ -0,0 +1,11 @@
+package org.kotlintestmpp.coroutines
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+
+/**
+ * JVM actual implementation for `asyncWithDelay`
+ */
+actual fun <T> CoroutineScope.asyncWithDealy(delay: Long, block: suspend () -> T): Deferred<T> {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmDateUtils.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmDateUtils.kt
new file mode 100644
index 00000000..db7f2d74
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmDateUtils.kt
@@ -0,0 +1,8 @@
+package org.kotlintestmpp.date
+
+/**
+ * JVM actual implementation for `getCurrentDate`
+ */
+actual fun getCurrentDate(): String {
+ return "test"
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmFunctions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmFunctions.kt
new file mode 100644
index 00000000..0ef8a99d
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/jvmMain/kotlin/org/kotlintestmpp/JvmFunctions.kt
@@ -0,0 +1,35 @@
+package org.kotlintestmpp
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import org.kotlintestmpp.common.Foo
+
+/**
+ * Function declared in JVM source set
+ *
+ * also see the [Foo] class
+ * @see org.kotlintestmpp.common.Foo
+ */
+fun jvm() {}
+
+/**
+ * Function declared in JVM source set
+ *
+ * Function with the same name exists in another source set as well.
+ */
+fun shared() {}
+
+/**
+ * Extension declared in JVM source set
+ */
+fun CoroutineScope.startConnectionPipeline(
+ input: String
+): Job = launch { TODO() }
+
+/**
+ * Extension declared in JVM source set
+ */
+fun String.myExtension() = println("test2")
+
+
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/CInterop.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/CInterop.kt
new file mode 100644
index 00000000..5c84780b
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/CInterop.kt
@@ -0,0 +1,15 @@
+@file:Suppress("unused")
+
+package org.kotlintestmpp
+
+import kotlinx.cinterop.CPointed
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.ExperimentalForeignApi
+
+/**
+ * Low-level Linux function
+ */
+@OptIn(ExperimentalForeignApi::class)
+fun <T : CPointed> printPointerRawValue(pointer: CPointer<T>) {
+ println(pointer.rawValue)
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/LinuxCoroutineExtensions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/LinuxCoroutineExtensions.kt
new file mode 100644
index 00000000..b561272d
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/LinuxCoroutineExtensions.kt
@@ -0,0 +1,11 @@
+package org.kotlintestmpp.coroutines
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+
+/**
+ * Linux actual implementation for `asyncWithDelay`
+ */
+actual fun <T> CoroutineScope.asyncWithDealy(delay: Long, block: suspend () -> T): Deferred<T> {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/LinuxDateUtils.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/LinuxDateUtils.kt
new file mode 100644
index 00000000..8900f87b
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/linuxMain/kotlin/org/kotlintestmpp/LinuxDateUtils.kt
@@ -0,0 +1,8 @@
+package org.kotlintestmpp.date
+
+/**
+ * Linux actual implementation for `getCurrentDate`
+ */
+actual fun getCurrentDate(): String {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/macosMain/kotlin/org/kotlintestmpp/MacOsCoroutineExtensions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/macosMain/kotlin/org/kotlintestmpp/MacOsCoroutineExtensions.kt
new file mode 100644
index 00000000..8576982c
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/macosMain/kotlin/org/kotlintestmpp/MacOsCoroutineExtensions.kt
@@ -0,0 +1,11 @@
+package org.kotlintestmpp.coroutines
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+
+/**
+ * MacOS actual implementation for `asyncWithDelay`
+ */
+actual fun <T> CoroutineScope.asyncWithDealy(delay: Long, block: suspend () -> T): Deferred<T> {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/macosMain/kotlin/org/kotlintestmpp/MacOsDateUtils.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/macosMain/kotlin/org/kotlintestmpp/MacOsDateUtils.kt
new file mode 100644
index 00000000..accf98a9
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokka/src/macosMain/kotlin/org/kotlintestmpp/MacOsDateUtils.kt
@@ -0,0 +1,8 @@
+package org.kotlintestmpp.date
+
+/**
+ * MacOS actual implementation for `getCurrentDate`
+ */
+actual fun getCurrentDate(): String {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/build.gradle.kts b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/build.gradle.kts
new file mode 100644
index 00000000..3d5a9e0d
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/build.gradle.kts
@@ -0,0 +1,39 @@
+plugins {
+ kotlin("multiplatform") version "1.9.0"
+ id("org.jetbrains.dokka.dokkatoo") version "2.1.0-SNAPSHOT"
+}
+
+group = "org.dokka.example"
+version = "1.0-SNAPSHOT"
+
+kotlin {
+ jvm() // Creates a JVM target with the default name "jvm"
+ linuxX64("linux")
+ macosX64("macos")
+ js(IR) {
+ browser()
+ }
+ sourceSets {
+ commonMain {
+ dependencies {
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
+ }
+ }
+ }
+}
+
+dokkatoo {
+ // Create a custom source set not known to the Kotlin Gradle Plugin
+ dokkatooSourceSets.register("customSourceSet") {
+ jdkVersion.set(9)
+ displayName.set("custom")
+ sourceRoots.from("src/customJdk9/kotlin")
+ }
+}
+
+
+//region DON'T COPY - this is only needed for internal Dokkatoo integration tests
+dokkatoo {
+ sourceSetScopeDefault.set(":dokkaHtml")
+}
+//endregion
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..014a7584
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "dokka-multiplatform-example"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/CommonCoroutineExtensions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/CommonCoroutineExtensions.kt
new file mode 100644
index 00000000..30bea657
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/CommonCoroutineExtensions.kt
@@ -0,0 +1,15 @@
+package org.kotlintestmpp.coroutines
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Deferred
+
+/**
+ * Common `expect` declaration
+ */
+expect fun <T> CoroutineScope.asyncWithDealy(delay: Long, block: suspend () -> T): Deferred<T>
+
+/**
+ * Common coroutine extension
+ */
+fun CoroutineDispatcher.name(): String = TODO("Not implemented")
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/CommonDateUtils.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/CommonDateUtils.kt
new file mode 100644
index 00000000..b241f5ea
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/CommonDateUtils.kt
@@ -0,0 +1,14 @@
+package org.kotlintestmpp.date
+
+/**
+ * Common `expect` declaration
+ */
+expect fun getCurrentDate(): String
+
+/**
+ * Common date util function
+ */
+fun getDate(): String {
+ return "Today's Date is ${getCurrentDate()}"
+}
+
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/common/Foo.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/common/Foo.kt
new file mode 100644
index 00000000..96c825c5
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/commonMain/kotlin/org/kotlintestmpp/common/Foo.kt
@@ -0,0 +1,7 @@
+package org.kotlintestmpp.common
+
+/**
+ * Common Foo class
+ */
+class Foo {}
+
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/customJdk9/kotlin/org/kotlintest/jdk9/CustomSourceSetFile.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/customJdk9/kotlin/org/kotlintest/jdk9/CustomSourceSetFile.kt
new file mode 100644
index 00000000..d77b959b
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/customJdk9/kotlin/org/kotlintest/jdk9/CustomSourceSetFile.kt
@@ -0,0 +1,11 @@
+package org.kotlintest.jdk9
+
+/**
+ * This class demonstrates custom dokka source sets
+ */
+class CustomSourceSetFile {
+ /**
+ * This function will not do anything
+ */
+ fun thisIsAFunction() {}
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsCoroutineExtensions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsCoroutineExtensions.kt
new file mode 100644
index 00000000..85d6beb0
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsCoroutineExtensions.kt
@@ -0,0 +1,11 @@
+package org.kotlintestmpp.coroutines
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+
+/**
+ * JS actual implementation for `asyncWithDelay`
+ */
+actual fun <T> CoroutineScope.asyncWithDealy(delay: Long, block: suspend () -> T): Deferred<T> {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsDateUtils.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsDateUtils.kt
new file mode 100644
index 00000000..2f4f3c45
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsDateUtils.kt
@@ -0,0 +1,8 @@
+package org.kotlintestmpp.date
+
+/**
+ * JS actual implementation for `getCurrentDate`
+ */
+actual fun getCurrentDate(): String {
+ return "test"
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsFunctions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsFunctions.kt
new file mode 100644
index 00000000..76757359
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jsMain/kotlin/org/kotlintestmpp/JsFunctions.kt
@@ -0,0 +1,18 @@
+package org.kotlintestmpp
+
+/**
+ * Function declares in JS source set
+ */
+fun js() {}
+
+/**
+ * Function declared in JS source set.
+ *
+ * Function with the same name exists in another source set as well.
+ */
+fun shared() {}
+
+/**
+ * Extension declared in JS source set
+ */
+fun String.myExtension() = println("test")
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JavaAnnotation.java b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JavaAnnotation.java
new file mode 100644
index 00000000..8b11ca09
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JavaAnnotation.java
@@ -0,0 +1,19 @@
+package org.kotlintestmpp;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This is a Java annotation
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JavaAnnotation {
+ String usage();
+
+ String[] aliases();
+
+ String description();
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmCoroutineExtensions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmCoroutineExtensions.kt
new file mode 100644
index 00000000..8f7fda49
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmCoroutineExtensions.kt
@@ -0,0 +1,11 @@
+package org.kotlintestmpp.coroutines
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+
+/**
+ * JVM actual implementation for `asyncWithDelay`
+ */
+actual fun <T> CoroutineScope.asyncWithDealy(delay: Long, block: suspend () -> T): Deferred<T> {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmDateUtils.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmDateUtils.kt
new file mode 100644
index 00000000..db7f2d74
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmDateUtils.kt
@@ -0,0 +1,8 @@
+package org.kotlintestmpp.date
+
+/**
+ * JVM actual implementation for `getCurrentDate`
+ */
+actual fun getCurrentDate(): String {
+ return "test"
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmFunctions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmFunctions.kt
new file mode 100644
index 00000000..0ef8a99d
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/jvmMain/kotlin/org/kotlintestmpp/JvmFunctions.kt
@@ -0,0 +1,35 @@
+package org.kotlintestmpp
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import org.kotlintestmpp.common.Foo
+
+/**
+ * Function declared in JVM source set
+ *
+ * also see the [Foo] class
+ * @see org.kotlintestmpp.common.Foo
+ */
+fun jvm() {}
+
+/**
+ * Function declared in JVM source set
+ *
+ * Function with the same name exists in another source set as well.
+ */
+fun shared() {}
+
+/**
+ * Extension declared in JVM source set
+ */
+fun CoroutineScope.startConnectionPipeline(
+ input: String
+): Job = launch { TODO() }
+
+/**
+ * Extension declared in JVM source set
+ */
+fun String.myExtension() = println("test2")
+
+
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/CInterop.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/CInterop.kt
new file mode 100644
index 00000000..5c84780b
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/CInterop.kt
@@ -0,0 +1,15 @@
+@file:Suppress("unused")
+
+package org.kotlintestmpp
+
+import kotlinx.cinterop.CPointed
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.ExperimentalForeignApi
+
+/**
+ * Low-level Linux function
+ */
+@OptIn(ExperimentalForeignApi::class)
+fun <T : CPointed> printPointerRawValue(pointer: CPointer<T>) {
+ println(pointer.rawValue)
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/LinuxCoroutineExtensions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/LinuxCoroutineExtensions.kt
new file mode 100644
index 00000000..b561272d
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/LinuxCoroutineExtensions.kt
@@ -0,0 +1,11 @@
+package org.kotlintestmpp.coroutines
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+
+/**
+ * Linux actual implementation for `asyncWithDelay`
+ */
+actual fun <T> CoroutineScope.asyncWithDealy(delay: Long, block: suspend () -> T): Deferred<T> {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/LinuxDateUtils.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/LinuxDateUtils.kt
new file mode 100644
index 00000000..8900f87b
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/linuxMain/kotlin/org/kotlintestmpp/LinuxDateUtils.kt
@@ -0,0 +1,8 @@
+package org.kotlintestmpp.date
+
+/**
+ * Linux actual implementation for `getCurrentDate`
+ */
+actual fun getCurrentDate(): String {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/macosMain/kotlin/org/kotlintestmpp/MacOsCoroutineExtensions.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/macosMain/kotlin/org/kotlintestmpp/MacOsCoroutineExtensions.kt
new file mode 100644
index 00000000..8576982c
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/macosMain/kotlin/org/kotlintestmpp/MacOsCoroutineExtensions.kt
@@ -0,0 +1,11 @@
+package org.kotlintestmpp.coroutines
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+
+/**
+ * MacOS actual implementation for `asyncWithDelay`
+ */
+actual fun <T> CoroutineScope.asyncWithDealy(delay: Long, block: suspend () -> T): Deferred<T> {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/macosMain/kotlin/org/kotlintestmpp/MacOsDateUtils.kt b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/macosMain/kotlin/org/kotlintestmpp/MacOsDateUtils.kt
new file mode 100644
index 00000000..accf98a9
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/multiplatform-example/dokkatoo/src/macosMain/kotlin/org/kotlintestmpp/MacOsDateUtils.kt
@@ -0,0 +1,8 @@
+package org.kotlintestmpp.date
+
+/**
+ * MacOS actual implementation for `getCurrentDate`
+ */
+actual fun getCurrentDate(): String {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/README.md b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/README.md
new file mode 100644
index 00000000..dd012003
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/README.md
@@ -0,0 +1,25 @@
+# Dokka Versioning MultiModule example
+
+This example demonstrates configuration of Dokka's [versioning plugin](../../../plugins/versioning), which
+allows readers to navigate through different versions of your API reference documentation.
+
+The example contains some code that exists only in the current documentation version `1.0`. You will not see
+this code in the previous version `0.9`, which is located in the [previousDocVersions](previousDocVersions) directory.
+
+___
+
+You can see up-to-date documentation generated for this example on
+[GitHub Pages](https://kotlin.github.io/dokka/examples/dokka-versioning-multimodule-example/htmlMultiModule/index.html).
+
+![screenshot demonstration of output](demo.png)
+
+### Running
+
+Run `dokkaHtmlMultiModule` task to generate documentation for this example:
+
+```bash
+./gradlew dokkaHtmlMultiModule
+```
+
+It will generate complete documentation for the root project and its subprojects, with the version navigation
+dropdown menu.
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/build.gradle.kts b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/build.gradle.kts
new file mode 100644
index 00000000..14aea2b4
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/build.gradle.kts
@@ -0,0 +1,19 @@
+plugins {
+ kotlin("jvm") version "1.9.0"
+ id("org.jetbrains.dokka") version "1.9.0" apply false
+}
+
+// The versioning plugin must be applied in all submodules
+subprojects {
+ repositories {
+ mavenCentral()
+ }
+ apply {
+ plugin("org.jetbrains.kotlin.jvm")
+ plugin("org.jetbrains.dokka")
+ }
+ val dokkaPlugin by configurations
+ dependencies {
+ dokkaPlugin("org.jetbrains.dokka:versioning-plugin:1.9.0")
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/demo.png b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/demo.png
new file mode 100644
index 00000000..9ac1d382
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/demo.png
Binary files differ
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/build.gradle.kts b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/build.gradle.kts
new file mode 100644
index 00000000..6d830079
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/build.gradle.kts
@@ -0,0 +1,27 @@
+import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
+import org.jetbrains.dokka.versioning.VersioningPlugin
+import org.jetbrains.dokka.versioning.VersioningConfiguration
+
+buildscript {
+ dependencies {
+ classpath("org.jetbrains.dokka:versioning-plugin:1.9.0")
+ }
+
+ repositories {
+ mavenCentral()
+ }
+}
+
+val currentVersion = "1.0"
+val previousVersionsDirectory = project.rootProject.projectDir.resolve("previousDocVersions").invariantSeparatorsPath
+
+// Main configuration for the versioning plugin. It will generate documentation for
+// the current version of the application, and look for previous versions of docs
+// in the directory defined in previousVersionsDirectory, allowing it to create
+// the version navigation dropdown menu.
+tasks.dokkaHtmlMultiModule {
+ pluginConfiguration<VersioningPlugin, VersioningConfiguration> {
+ version = currentVersion
+ olderVersionsDir = file(previousVersionsDirectory)
+ }
+}
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/build.gradle.kts b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/build.gradle.kts
new file mode 100644
index 00000000..bf1513f8
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/build.gradle.kts
@@ -0,0 +1 @@
+// intentionally empty - build config is set in the root build.gradle.kts
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt
new file mode 100644
index 00000000..e0f9e86a
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/ChildProjectAClass.kt
@@ -0,0 +1,18 @@
+@file:Suppress("unused")
+
+package demo
+
+/**
+ * Class defined in child project A
+ *
+ * @since 0.9
+ */
+class ChildProjectAClass {
+
+ /**
+ * Function that extends [ChildProjectAClass]
+ *
+ * @since 1.0
+ */
+ fun extend() {}
+}
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/FancyAPI.kt b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/FancyAPI.kt
new file mode 100644
index 00000000..e691be03
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectA/src/main/kotlin/demo/FancyAPI.kt
@@ -0,0 +1,10 @@
+package demo
+
+/**
+ * New shiny fancy API
+ *
+ * @since 1.0
+ */
+class FancyAPI {
+ fun doSomething() {}
+}
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/build.gradle.kts b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/build.gradle.kts
new file mode 100644
index 00000000..bf1513f8
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/build.gradle.kts
@@ -0,0 +1 @@
+// intentionally empty - build config is set in the root build.gradle.kts
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt
new file mode 100644
index 00000000..6978a176
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/ChildProjectBClass.kt
@@ -0,0 +1,10 @@
+@file:Suppress("unused")
+
+package demo
+
+/**
+ * Class defined in child module B
+ *
+ * @since 0.9
+ */
+class ChildProjectBClass
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/Functions.kt b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/Functions.kt
new file mode 100644
index 00000000..35a9275b
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/parentProject/childProjectB/src/main/kotlin/demo/Functions.kt
@@ -0,0 +1,8 @@
+package demo
+
+/**
+ * New super function that does everything
+ *
+ * @since 1.0
+ */
+fun superFunction42() {}
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/settings.gradle.kts
new file mode 100644
index 00000000..ec6614f0
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokka/settings.gradle.kts
@@ -0,0 +1,5 @@
+include(":parentProject")
+include(":parentProject:childProjectA")
+include(":parentProject:childProjectB")
+
+rootProject.name = "dokka-versioning-multimodule-example" \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..850a82bf
--- /dev/null
+++ b/dokka-runners/dokkatoo/examples/versioning-multimodule-example/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "versioning-multimodule-example"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/gradle.properties b/dokka-runners/dokkatoo/gradle.properties
new file mode 100644
index 00000000..cb5238c6
--- /dev/null
+++ b/dokka-runners/dokkatoo/gradle.properties
@@ -0,0 +1,12 @@
+# memory is set quite high because the tests launch a lot of processes
+org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx4g -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError -XX:+AlwaysPreTouch
+
+org.gradle.caching=true
+
+org.gradle.unsafe.configuration-cache=true
+org.gradle.unsafe.configuration-cache-problems=warn
+
+org.gradle.parallel=true
+org.gradle.welcome=never
+
+kotlin.mpp.import.enableKgpDependencyResolution=true
diff --git a/dokka-runners/dokkatoo/gradle/libs.versions.toml b/dokka-runners/dokkatoo/gradle/libs.versions.toml
new file mode 100644
index 00000000..4a6fa4ff
--- /dev/null
+++ b/dokka-runners/dokkatoo/gradle/libs.versions.toml
@@ -0,0 +1,48 @@
+[versions]
+
+kotlin = "1.9.0" # should match Gradle's embedded Kotlin version https://docs.gradle.org/current/userguide/compatibility.html#kotlin
+kotlin-dokka = "1.9.0"
+kotlinx-serialization = "1.6.0"
+
+kotest = "5.6.2"
+
+gradlePlugin-android = "8.0.2"
+gradlePlugin-dokkatoo = "1.6.0"
+gradlePlugin-gradlePublishPlugin = "1.2.1"
+gradlePlugin-bcvMu = "0.0.4"
+
+
+[libraries]
+
+## Dokka
+kotlin-dokkaCore = { module = "org.jetbrains.dokka:dokka-core", version.ref = "kotlin-dokka" }
+kotlin-dokkaPlugin-allModulesPage = { module = "org.jetbrains.dokka:all-modules-page-plugin", version.ref = "kotlin-dokka" }
+kotlin-dokkaPlugin-templating = { module = "org.jetbrains.dokka:templating-plugin", version.ref = "kotlin-dokka" }
+
+## Kotlinx Serialization
+kotlinxSerialization-bom = { module = "org.jetbrains.kotlinx:kotlinx-serialization-bom", version.ref = "kotlinx-serialization" }
+kotlinxSerialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json" }
+#kotlinxSerialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+
+
+### Test libraries ###
+
+kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
+
+kotest-bom = { module = "io.kotest:kotest-bom", version.ref = "kotest" }
+kotest-datatest = { module = "io.kotest:kotest-framework-datatest" }
+kotest-junit5Runner = { module = "io.kotest:kotest-runner-junit5" }
+kotest-assertionsCore = { module = "io.kotest:kotest-assertions-core" }
+kotest-assertionsJson = { module = "io.kotest:kotest-assertions-json" }
+
+### Gradle plugins ###
+
+gradlePlugin-android = { module = "com.android.tools.build:gradle", version.ref = "gradlePlugin-android" }
+gradlePlugin-androidApi = { module = "com.android.tools.build:gradle-api", version.ref = "gradlePlugin-android" }
+gradlePlugin-dokkatoo = { module = "dev.adamko.dokkatoo:dokkatoo-plugin", version.ref = "gradlePlugin-dokkatoo" }
+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/dokka-runners/dokkatoo/modules/docs/build.gradle.kts b/dokka-runners/dokkatoo/modules/docs/build.gradle.kts
new file mode 100644
index 00000000..a27b177c
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/docs/build.gradle.kts
@@ -0,0 +1,58 @@
+import dev.adamko.dokkatoo.dokka.plugins.DokkaHtmlPluginParameters
+
+plugins {
+ buildsrc.conventions.base
+ dev.adamko.`dokkatoo-html`
+}
+
+dependencies {
+ dokkatoo(projects.modules.dokkatooPlugin)
+ dokkatooPluginHtml(libs.kotlin.dokkaPlugin.allModulesPage)
+ dokkatooPluginHtml(libs.kotlin.dokkaPlugin.templating)
+}
+
+dokkatoo {
+ moduleName.set("Dokkatoo Gradle Plugin")
+
+ pluginsConfiguration.named<DokkaHtmlPluginParameters>("html") {
+ customStyleSheets.from(
+ "./style/logo-styles.css",
+ )
+ customAssets.from(
+ "./images/logo-icon.svg",
+ )
+ }
+}
+
+tasks.dokkatooGeneratePublicationHtml {
+ doLast {
+ outputDirectory.get().asFile.walk()
+ .filter { it.isFile && it.extension == "html" }
+ .forEach { file ->
+ file.writeText(
+ file.readText()
+ .replace(
+ """<html>""",
+ """<html lang="en">""",
+ )
+ .replace(
+ """
+ <button id="theme-toggle-button">
+ """.trimIndent(),
+ """
+ <div id="github-link"><a href="https://github.com/adamko-dev/dokkatoo/"></a></div>
+ <button id="theme-toggle-button">
+ """.trimIndent(),
+ )
+ .replace(
+ """
+ href="https://github.com/Kotlin/dokka"><span>dokka</span>
+ """.trimIndent(),
+ """
+ href="https://github.com/adamko-dev/dokkatoo/"><span>Dokkatoo</span>
+ """.trimIndent(),
+ )
+ )
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/docs/images/banner.svg b/dokka-runners/dokkatoo/modules/docs/images/banner.svg
new file mode 100644
index 00000000..ee9c57e6
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/docs/images/banner.svg
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="0 0 128 32"
+ width="1920"
+ height="480"
+ xmlns="http://www.w3.org/2000/svg"
+ style="background-color: black;"
+>
+ <defs>
+ <style>
+ /*
+ Specifically only embed the font required for 'Dokkatoo'
+ // NOTE replace &amp; with plain ampersand in URL, because XML can't handle it even though it's in a comment
+ https://fonts.googleapis.com/icon?family=JetBrains+Mono&amp;text=Dokkatoo
+ https://amio.github.io/embedded-google-fonts/
+ */
+ @font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400;
+ src: url(data:application/font-woff2;charset=utf-8;base64,d09GMgABAAAAAAP0AA8AAAAAB3QAAAObAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhwbHhwoBmA/U1RBVF4AXBEICoM0gn8LEAABNgIkAxAEIAWFAAcgGywGyAQeKK/6V5U0JrWweuRkIPSAPu3uB3ECeFTjw871+62KWCXStRIqNcGi7y+i1sSSaUziSTQxRM5L6vcAzzDqRKNLEcAyDCJUdBzUN3C9Btu4UaSm55biRv/HadtC+EEOMEosXtPTONLHCq1Fsl2X1eJCHCDPyko05JykRD2hIjkuIYS2cyPSXsWf3wBueJ1sC3iG5f/b2r//+0D7VxzbhbjhgyDIA/JtMO5qKuXOQ8A6NNq+2Hh7x0jdnuwcaewRKxEcYANONARSrGo4CBul0MWFIJ+4NT3yRL1dPCdiSygYxYmvk4krKtzvZBepNNJNPyOdbX3ca39uP2nfZF9pX2ZflLElKBYI22blBV2fYOCIInJaMz4oQh1zGhLZdCjgz3VNW2ZsV8vXrFirHMv1puWOlu5lyqHO8fuRCz6fUa7OCQptMWAYZWcDYjGolFh83Tjr22YEg+ocP3yOIYyNC77yc4JCGGdfZeE15VBSVajKKqPcoPFsICDEoqzghwWfZlkM4D/EBwv+5WcDQjOEEgqFIReUovrtW9+u8xyLS7KPxh6j9u0nM9oiohtT9qZEN7VFZDQVNFvZWdZeMzvLauafkIrmyty85oqyu6PCXor2mIesanf6cEmFt7exdKqyc+X38bVpkenJRUmh+Xce9xw98Snet6uv6Lrh4/ZHHI/v7Lzh1+7fOOqyvJ7wevNiK7zO67Esrye8zrzEDK/3esyCONNMvjjZNONiLTP54mTLpCMjPyct5Zria66pvubd4ndTkuIvLwbg3zE5JiN1c+UTM96V8b+5Ne3Lj/F3Xr/96tO2t1/9+a+Y/336xdq/hM7ZoJo96jk5h37PXzH/XaBfPLYXZCXV1GLipYyoGTebSzxCHw/glKJWpG8Zl88xSl8PwSfEiZtIsE2sEUlWCF2kEc8rIp1jXCtysIUlkZMjVApXEMkeSlRj3IBDenFlYiXLMJCs0g5BMf300kgfWYxosj100kwFrfl2OhiJ60cYYJhYTmqHhmlmiE4GGGGYE+jtpIff0c8Q7ZykgHRyyaaVESyGaKSTPobJox8U+MhximmlnVF6aGSIclp1KAB+ZXcRygkmHq1DdvMuXd6upgwkTkfr0JfK2zx0XWFxCv0MMNl19cBAU/FUvJxdlNJBK7uavK+QIfrpopXmrH6T0QGGoQPR+EOD7W+n82fxKE2coHmyJp2sYNqT3cXDe5zSTNqthC7U1gAAAA==) format('woff2');
+ }
+ </style>
+
+ <linearGradient
+ id="upper-beak-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="16"
+ y2="31">
+ <stop offset="0" stop-color="#2d2d2d"/>
+ <stop offset="1" stop-color="#3d3d3d"/>
+ </linearGradient>
+ <linearGradient
+ id="lower-beak-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="16"
+ y2="31">
+ <stop offset="1" stop-color="#1d1d1d"/>
+ <stop offset="0" stop-color="#2d2d2d"/>
+ </linearGradient>
+ <linearGradient
+ id="blue-purple-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="31"
+ y2="0">
+ <stop offset="0.3" stop-color="#0095D5"/>
+ <stop offset="0.5" stop-color="#3C83DC"/>
+ <stop offset="0.6" stop-color="#6D74E1"/>
+ <stop offset="0.7" stop-color="#806EE3"/>
+ </linearGradient>
+ <linearGradient
+ id="purple-orange-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="31"
+ y2="0">
+ <stop offset="0.5" stop-color="#C757BC"/>
+ <stop offset="0.65" stop-color="#D0609A"/>
+ <stop offset="0.9" stop-color="#F88909"/>
+ </linearGradient>
+ </defs>
+
+ <rect id="black-background" width="100%" height="100%"/>
+
+ <g id="bird">
+ <path id="blue-feathers" fill="url(#blue-purple-gradient)"
+ d="
+ M5,19 v-10 l7,-7 h10
+ M20,16 l8,-8 v10 l-3,3
+ "/>
+ <path id="orange-feather" fill="url(#purple-orange-gradient)"
+ d="M11,13 l11,-11 h6 v6 l-8,8 l-3,-3"/>
+ <path id="white-head" fill="#fff"
+ d="M5,19 l6,-6 h6 l8,8 v3 l2,6 h-18 l2,-4"/>
+ <g id="beak" stroke-width="0.2" stroke="#666" >
+ <path id="lower-beak" fill="url(#lower-beak-gradient)"
+ d="M5,26 l4,3 l4,-3 Z"/>
+ <path id="upper-beak" fill="url(#upper-beak-gradient)"
+ d="M4,26 l5,-4 l4,4 Z"/>
+ </g>
+ <g id="eye">
+ <circle id="eye-base" fill="#000" cx="15" cy="21" r="1.5"/>
+ <path id="eye-twinkle" fill="none"
+ stroke="#f5f5f5"
+ stroke-linecap="round"
+ stroke-width=".2"
+ d="M15.1,20.1 q0.9,-0.1 1,0.9"/>
+ </g>
+ </g>
+
+ <text x="36" y="28" font-family="JetBrains Mono" font-size="18" fill="#fff">Dokkatoo</text>
+</svg>
diff --git a/dokka-runners/dokkatoo/modules/docs/images/logo-icon.svg b/dokka-runners/dokkatoo/modules/docs/images/logo-icon.svg
new file mode 100644
index 00000000..5b343c11
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/docs/images/logo-icon.svg
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="0 0 32 32"
+ height="480"
+ width="480"
+ style="background-color: black;"
+ xml:space="preserve"
+ xmlns="http://www.w3.org/2000/svg">
+ <defs>
+
+ <linearGradient
+ id="upper-beak-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="16"
+ y2="31">
+ <stop offset="0" stop-color="#2d2d2d"/>
+ <stop offset="1" stop-color="#3d3d3d"/>
+ </linearGradient>
+ <linearGradient
+ id="lower-beak-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="16"
+ y2="31">
+ <stop offset="1" stop-color="#1d1d1d"/>
+ <stop offset="0" stop-color="#2d2d2d"/>
+ </linearGradient>
+ <linearGradient
+ id="blue-purple-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="31"
+ y2="0">
+ <stop offset="0.3" stop-color="#0095D5"/>
+ <stop offset="0.5" stop-color="#3C83DC"/>
+ <stop offset="0.6" stop-color="#6D74E1"/>
+ <stop offset="0.7" stop-color="#806EE3"/>
+ </linearGradient>
+ <linearGradient
+ id="purple-orange-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="31"
+ y2="0">
+ <stop offset="0.5" stop-color="#C757BC"/>
+ <stop offset="0.65" stop-color="#D0609A"/>
+ <stop offset="0.9" stop-color="#F88909"/>
+ </linearGradient>
+</defs>
+
+ <rect id="black-background" width="100%" height="100%"/>
+
+ <g id="bird">
+ <path id="blue-feathers" fill="url(#blue-purple-gradient)"
+ d="
+ M5,19 v-10 l7,-7 h10
+ M20,16 l8,-8 v10 l-3,3
+ "/>
+ <path id="orange-feather" fill="url(#purple-orange-gradient)"
+ d="M11,13 l11,-11 h6 v6 l-8,8 l-3,-3"/>
+ <path id="white-head" fill="#fff"
+ d="M5,19 l6,-6 h6 l8,8 v3 l2,6 h-18 l2,-4"/>
+ <g id="beak" stroke-width="0.2" stroke="#666" >
+ <path id="lower-beak" fill="url(#lower-beak-gradient)"
+ d="M5,26 l4,3 l4,-3 Z"/>
+ <path id="upper-beak" fill="url(#upper-beak-gradient)"
+ d="M4,26 l5,-4 l4,4 Z"/>
+ </g>
+ <g id="eye">
+ <circle id="eye-base" fill="#000" cx="15" cy="21" r="1.5"/>
+ <path id="eye-twinkle" fill="none"
+ stroke="#f5f5f5"
+ stroke-linecap="round"
+ stroke-width=".2"
+ d="M15.1,20.1 q0.9,-0.1 1,0.9"/>
+ </g>
+ </g>
+</svg>
diff --git a/dokka-runners/dokkatoo/modules/docs/images/social_preview_banner.png b/dokka-runners/dokkatoo/modules/docs/images/social_preview_banner.png
new file mode 100644
index 00000000..37d731bd
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/docs/images/social_preview_banner.png
Binary files differ
diff --git a/dokka-runners/dokkatoo/modules/docs/images/social_preview_banner.svg b/dokka-runners/dokkatoo/modules/docs/images/social_preview_banner.svg
new file mode 100644
index 00000000..46878cce
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/docs/images/social_preview_banner.svg
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="0 0 128 64"
+ width="1280"
+ height="640"
+ xmlns="http://www.w3.org/2000/svg"
+ style="background-color: black;"
+>
+ <defs>
+ <style>
+ /*
+ Specifically only embed the font required for 'Dokkatoo'
+ // NOTE replace &amp; with plain ampersand in URL, because XML can't handle it even though it's in a comment
+ https://fonts.googleapis.com/icon?family=JetBrains+Mono&amp;text=Dokkatoo https://github.com/adamko-dev/dokkatoo Generate Kotlin documentation
+ https://amio.github.io/embedded-google-fonts/
+ */
+ @font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400;
+ src: url(data:font/woff2;base64,d09GMgABAAAAAA9AAA8AAAAAHpAAAA7kAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGkAbHhyPaAZgP1NUQVReAIECEQgKnnSYJQt8AAE2AiQDgXQEIAWFAAcgG8AZI1K0ekimjtSFP3/+/fw+3Kpf1dVZ6E6AMc+aOjNrYrDnzJ66Z+DEhTeZLzf7CfVM66mYpBpKN8wkpCZACB1KsBorYsf2ZnRVleN/vf49fROD6LQJ3rqkOhVQXGibKp8iTaEHhORB5anm8n9zpZ0JvDe5ArFkPa/y1FaSEjs/OcjO/t0DzjEmx5ArkAJ2fVWnC6iuVQSKQFbYalQVTlfJWgynOYhWjNaXGmNfIqO/jIo3BwFwkdoUybIJAJH0Wwyy0c9kpUE1qjcbM25KEhFIHsLQGmjWDMBRcPtWG5ft3IwYyuFJzprswxipHE8BcXdesWzjThRv3LJiI2IaOtKuwD9boQWicJFAEUpQhkrUoA4t0QYd0Ald0B290Rf9MRCDMRTDMapFxPnP+cE56Gx2fnB+cJLOOP2T/kS/pZfrpH5Lv6XLtZG8fCJvyQNyk+yV9ZKXPtJO8uKof9RH6g11WG1Vy/k3/kItV8v5BX6Ir+CzeAT3kjw9RfdIXvJ0ER2j7bSSJtAg8qkQ/+A7vIGncAMuwokg1N8bAcEBM5ZpsdlCHUYMTi5RwXAyuJU53wiT3Z3zUG0vF3gyuGGxyUJ7EHAa+CG79kEhLdTb8YMCXRT3ZP2d/g6kmhN+QGAAN5v0EwKf63D9YuNj9gXAw+D6LFFnge+ssRhTMABtkIAKuqiDYHVQHTSTvegkmabWqhIAPWs5a4QeoSexB616BJQaMTR9ySHVrUtYJH6ULIubKoieglHoBR+xOE4RhLpJJu9N45/CXfXd+AMwf8AfWM/kV2T/zkV8DxIMGHKWft34zkzxBZeyrJf1CddxH6gPDKPaJVRLtUp7Jm3SyCgBpd1CSUhiGv7S2jJ8JgCxKVLxRpfyuDdwIe5Dq9xpb0CUuXScI4NQWjN1yTQc1VoxbBUIb0UmlqI/dsMElDGVnIZBnH8xfFSZ0C56IXBxIqGSvTk+KlIYV6aNFZjznE+jhBLSro5zFH81LAPhPyv+qtLyPkApXa1tSfwACK8ouIaBzZnX8DXpeIDKl590CUpqa/B+/TR/qUngTlRWMc/iWYm+mi+Wk5yMz51qCMrdSynnsrGLcoBCYfYE9FJZwNQAK/hF8w7uawyvLcFreE2JJ9M9Ah9SEp6wiukKuiLe39GjZZ8FJSG2ml6mupTHp7onrkbr3OnnIuJrOEYGodgQOYe9oBvp84A+ogkFOJFQ4RtYeHbFNQvDR9NI+MiZfAePFRrMCRAojoGL8AkFxkIswok4Ub9FF3qBvGBoQZ7YtQwHiMCNqMPFV9ogAidgAE5S4AVCGCj0D7jDg5eMmJvJaiA8n86ABjjDcxGePHPRYvSiEYd2EMWgyxXgMIF/vmdKYOakUej4BLzmUOWbA7kwT138rpd/nAO4Hb93HktdQhCSPRpq424D3rNm7c5KNlm3c9lGikO2O+OgbgVMiScPMOzKJHQGwI84mZBntDiVnjLSGSxb540j2HymQYM9mgMf3TEIYzAek7EK67AVu59uCnx03bXpMqzBBmy/lwXCT8JHw4fDB8Krw6tUnMUhetW1qECNayVhiHh95VA93U8mMZmUrG8H3DXk1p2onh4kzzSGZyZQcbG1+0CUv5ihwkEqZBKFDmwg16ZAQ0jvDqKUa9p4NbYsVlsWqzR1sTLjxTuUFsbKY6Vdiwo913VLYkNk8G24brV4XiQi8agTj7siTiwq1RJ1CnzXak+We3rlBtdq29jUBE4FgWmwjTlS6awxyzLZLKl0LrfnDiadNZkAnGra24EMWaSCQL4C2wV27jwz33B9JkvpnLWUfstkgmoTdAUbMmWpoIHAMm/c981lmSx1NDDaImk7pA0QWqsEArNkjfLrzVXb3LYx19oGwc/DRPqOO9kGsmLIdEao6eEafmq8XhdZ8I2NvHqyjY9PMMe0TwXmgMntbHhxKjDz03qrLVDbPDbqeNzFkvUwgKYiNBAcC0UpsdAggjsvTUUEbr4Nd1cDmLTlIy23GhbN4QDkjP8UTJP+9tUPg7lxvM2f3PTLBNCAvHwxLQOl8unQbID0LrC70YcMuP01bP6W2nKn3vKlCR91/HM2veMGTY/dApuv18mfP4PNn+iIsV+UDKnUDWjNZgjBH5EeAL0C5n/zE1Rh/+7zyTsvZtrggGvsDrXlLhy/DTbfGsl4a4pC0cDjhDQLKf1kAOa92XD8dPTwtGJRdt69m5hw5MqTAosiH75+PT5xx4ED1L/wje3blxQ/c/NmoSKbDz26lMdM376dLJstO86cSaG3nTqVJK+Dq21UBFAYPaNrKRxWdWpWKzQFxRRongJoRisfyAYVWqE8R68m+q4UuCkXWOSmPMB56BIzfTnf5QyBmigQqREk8gWC3crJk4m+EyUWKEeBSPNaG4FyDIg2h0dP0G91Exs0T00xMSB6ejpeXvt1wTqw9s84doyZPl4su6koEEHFgdWEgsDKjmlSHQHC5XV7R1L10MJvuuJAbH84OXcdWVlae3KIHO48D662jL24c+dGs8O/BHyu5Pnt29dPlAmjQPipT/OO039jiP+Q386W6fT/lzaWgQH/7ZbhOs4l5oqce7iuxd3jEdrbhFzc3iZ4QGWY4GRrVdwu1NqdrCA4qmvsmPBI0OkEzHfwgqjTIZEnU8Yfp1CN6qgGA36inb+fc19S76wdOd7nNYK/wxSPtbPLo8gnGmqecCwuEkho8zKT4ljgMq+1eqN+NA1I9c2oT1/dfaycLa2YBR1hhr5WPRPrJX6yj1rPMLyxTTR5Fjj2q54Fi7u0WEQ+hEVtA5J4H48kUBkmuNk6FfuEOrubFRzLhjQKyUMcp5MEfoaXRB2HtLlEoywbAjDMOLhA3e/wLDAZ+tp4hqHW7yN+4o1l9H2t+V2cXuJ9vF7itFhEPoRFUBqGnTW1qt6Hau3OGoydNbV25LLUYWyYU/vLIAk1LO8tFPIxkvg5WMLpQWDfRePZkqGiZsoTG9nUI4mya6G6XHUtkMUeqSkyxtvf2E7kvrw8Ti/xXl4vcVpeRF7Ei2DATy554/JwV7Nkcg1Zl5JRHY/qOSQhFcjGh1tPGAM/XTcCh5/MzN3708i1oJuZ3r2/zPsVrKpqyS+UKn++bozT51iyzhqBUNAjxH6tCCqpYEX+cElhRX5JLdeFDpgO5jxOS53NOdR6MHc2Le1SziEwUEC8cXm4q1kyuYasS8mo7kc9h0SkgtIQwcHWqnhCqLU7WEFwsLV2PIlrVQeLe3QYowmEsa5RwGgCCRjYLvqJN5bRG1sNJvd8x359ma2X5URTG88w1Pp9q3/XIhH5EBK1DVjifTyWwKICqzc+DfV3kk5leL49myRj3tD4oUiFVlEEql6Enm6WYixQ+niFRrHM29tJjUiT9ERdciEp4jNMSUuNHQVPPvgVTw9bm9TI0ISkJKYW55agKiE9RatepF+0yLjoof6hOj5mrh5eDUw9upuLjEVx8ZrwCIUmrshYZO7VHZ/3cQUDeiyXByjk0/v2yN/HwbsBf4Fzv8Tzl9y5C/BewN2l6bscb6VpK7eKu0vTdzneStNWXL+4O0iRrIuOTtYph+QM0VWERJhUkf7G7NCe1THXqBYU1YJydnpKUS0p7C5MCAxNOxmieMC7/Wioy3Nx0ue56KwL36tV23u3MxlM7Xpv16v/T7TqDC5OeoNLWMMfbuHvFeyOsLSEwLC0HV18M+tl+nY5geQubYM3m9oGb8YkN6Ig8HEjy8h8soygqRs5QkwAOUJMZD+B9xb2IMceYtlDLHeQ8WYPcexBlj3IcodCyUjWkqM0M5YSZVfWomMty1mLjrXkKM2MpUTZlbXoWMty1hJQfzljWY5sRV7oPI/GPUbI64fON265XQaxYsvtsi2380LneTTuMUJeP3Q+DNlq4+BVZFU/bMlWGwefJWf71Vcb1fhOH/nEL1JrjiYesNVYhDPkjFVAUHPGxtncayzCN/LNKsyxcTb3mtf8Gx4c/Zp/w1cf+cRUMoPIwOCBIQdsCH/KvObf8NVHPjGVTJErUoqKoVmce8CGwmLmNf+Grz7yia8JCZ6adMAGgbiR6eQYMXZPaT2xf03/1hP9HlAtKKoF5ez0lKJaUigsZl4X8OM6L1STqnNC8HEHCSYVIprlIzWTrk2X88LelIOXJXO/vMo77p073Lt4dfdoXbbltjF0bv1DZvOh+hdfyT7/WV+n9IHn4InRnWUu5a4xOTE+vELL+HlNnujdmXLNc67Tklxz1bLwHu+W6eSsd3PTOztloi7/ofHiuohutvJI3dHB/i+Z0mG78X8v7NA2KIsdyv4HktLPof5/mUXZ17vqgXIUI5kjE5oe02GbiKEubyS333dsFzMnFPNw2kOZ42T/7jj9t9pSnYrrwml8PNNHCCHiWRTCfb4SHuVnDivWkJxysbekwew2wg3HL5E/FtdOC4jMEK6t9BcH1J5jeMKEOUtEfuL7JO0WdrtjpriJJE6IC5jopFHbiszZQZgAX9EwcWU3x3uHY/i0Q19e1iYJkzZNMrQ2NYv5WV8ieRvU4MH1tQtxt/tXv/2NFG5KZ0pqXYiIX4Ss1yuXdPvfSPu0D037waAqVmyAMaMFaJjpSn8ODmTYpCbq7FftBond7PnDgfcUHUcZmkImMyEk+f9DkBtG7TR6jZ4Lz4Fht5MLTIhjta2OAspmwrrk2tXCbizIBPZ/V7L9O8KL8ml/1cKE7YXqFgBe0U0GrixWtB9dsRt1+ZAS3pzGmPuxDF5f8w6L/FbrRJCLWq4To6lIuk6CGFjXSeGDxevqgMLIdXXhBYPTFGGfvARConKwLXxSe/DIY31EEtJuHk8G6NENXWBGVyShd+ztjGqUIu/FtKYSVeitOvZGd/RCFPzjml4oRc/X1qre6AW/7uusRmf4oRt6ohL+yIAOqUhGOXpDhZ4woxpd0Qtp6IauwYP4Qo9yVKIPOsOMnshFeQIWQFfliiD4STswHoIedU125tqPYEcnUjUZldIHyQ27RK8a3dAdA1y3mIG6ygB1fVyRjSqUw7XOdyPoiW6oQTlKqY4SfRgG7sBo/x5sO5Wo1nr7oAR+KG2sA/mXcLD+7krPTxIiy3SUf7AaOAAAAA==) format('woff2');
+ }
+ </style>
+
+ <linearGradient
+ id="upper-beak-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="16"
+ y2="31">
+ <stop offset="0" stop-color="#2d2d2d"/>
+ <stop offset="1" stop-color="#3d3d3d"/>
+ </linearGradient>
+ <linearGradient
+ id="lower-beak-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="16"
+ y2="31">
+ <stop offset="1" stop-color="#1d1d1d"/>
+ <stop offset="0" stop-color="#2d2d2d"/>
+ </linearGradient>
+ <linearGradient
+ id="blue-purple-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="31"
+ y2="0">
+ <stop offset="0.3" stop-color="#0095D5"/>
+ <stop offset="0.5" stop-color="#3C83DC"/>
+ <stop offset="0.6" stop-color="#6D74E1"/>
+ <stop offset="0.7" stop-color="#806EE3"/>
+ </linearGradient>
+ <linearGradient
+ id="purple-orange-gradient"
+ gradientUnits="userSpaceOnUse"
+ x1="0"
+ y1="31"
+ x2="31"
+ y2="0">
+ <stop offset="0.5" stop-color="#C757BC"/>
+ <stop offset="0.65" stop-color="#D0609A"/>
+ <stop offset="0.9" stop-color="#F88909"/>
+ </linearGradient>
+ </defs>
+
+ <rect id="black-background" width="100%" height="100%"/>
+
+ <g transform="translate(2,16)" id="bird">
+ <path id="blue-feathers" fill="url(#blue-purple-gradient)"
+ d="
+ M5,19 v-10 l7,-7 h10
+ M20,16 l8,-8 v10 l-3,3
+ "/>
+ <path id="orange-feather" fill="url(#purple-orange-gradient)"
+ d="M11,13 l11,-11 h6 v6 l-8,8 l-3,-3"/>
+ <path id="white-head" fill="#fff"
+ d="M5,19 l6,-6 h6 l8,8 v3 l2,6 h-18 l2,-4"/>
+ <g id="beak" stroke-width="0.2" stroke="#666" >
+ <path id="lower-beak" fill="url(#lower-beak-gradient)"
+ d="M5,26 l4,3 l4,-3 Z"/>
+ <path id="upper-beak" fill="url(#upper-beak-gradient)"
+ d="M4,26 l5,-4 l4,4 Z"/>
+ </g>
+ <g id="eye">
+ <circle id="eye-base" fill="#000" cx="15" cy="21" r="1.5"/>
+ <path id="eye-twinkle" fill="none"
+ stroke="#f5f5f5"
+ stroke-linecap="round"
+ stroke-width=".2"
+ d="M15.1,20.1 q0.9,-0.1 1,0.9"/>
+ </g>
+ </g>
+
+ <text x="33" y="32" font-family="JetBrains Mono" font-size="18" fill="#fff">Dokkatoo</text>
+ <text x="34.2" y="39" font-family="JetBrains Mono" font-size="5" fill="#ddd">
+ Generate Kotlin documentation
+ </text>
+ <text x="34.3" y="45" font-family="JetBrains Mono" font-size="3" fill="#999">
+ https://github.com/adamko-dev/dokkatoo
+ </text>
+</svg>
diff --git a/dokka-runners/dokkatoo/modules/docs/style/logo-styles.css b/dokka-runners/dokkatoo/modules/docs/style/logo-styles.css
new file mode 100644
index 00000000..73c59666
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/docs/style/logo-styles.css
@@ -0,0 +1,44 @@
+.library-name a {
+ position: center;
+ display: flex;
+ margin-left: 0.2em;
+ margin-right: 0.2em;
+ align-items: center;
+ justify-content: center;
+}
+
+.library-name a::before {
+ content: "";
+ background-image: url('../images/logo-icon.svg');
+ background-repeat: no-repeat;
+ background-size: 100% 100%;
+ position: center;
+ display: flex;
+ width: 3.5em;
+ height: 3.5em;
+ margin-right: 1em;
+}
+
+.navigation-wrapper {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+}
+
+div#github-link {
+ width: 36px;
+ height: 36px;
+ display: inline-flex;
+ border-radius: 24px;
+ align-items: center;
+ justify-content: center;
+ border: none;
+}
+
+div#github-link a {
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-github' viewBox='0 0 16 16'><path d='M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z'/></svg>");
+ width: 24px;
+ height: 24px;
+ border-radius: 24px;
+ background-size: 100% 100%;
+ margin-left: 16px;
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/build.gradle.kts
new file mode 100644
index 00000000..3af8ad71
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/build.gradle.kts
@@ -0,0 +1,281 @@
+@file:Suppress("UnstableApiUsage") // jvm test suites & test report aggregation are incubating
+
+import buildsrc.tasks.SetupDokkaProjects
+import buildsrc.utils.buildDir_
+import buildsrc.utils.skipTestFixturesPublications
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+
+plugins {
+ kotlin("jvm")
+ kotlin("plugin.serialization")
+ `java-test-fixtures`
+
+ `jvm-test-suite`
+ `test-report-aggregation`
+
+ buildsrc.conventions.`java-base`
+ buildsrc.conventions.`maven-publish-test`
+ buildsrc.conventions.`dokkatoo-example-projects`
+ buildsrc.conventions.`android-setup`
+}
+
+description = """
+ Integration tests for Dokkatoo Gradle Plugin.
+ The tests use Gradle TestKit to run the template projects that are committed in the repo.
+ """.trimIndent()
+
+dependencies {
+ testMavenPublication(projects.modules.dokkatooPlugin)
+ exampleProjects(projects.examples)
+
+ testFixturesApi(testFixtures(projects.modules.dokkatooPlugin))
+
+ testFixturesImplementation(gradleTestKit())
+
+ testFixturesImplementation(platform(libs.kotlinxSerialization.bom))
+ testFixturesImplementation(libs.kotlinxSerialization.json)
+
+ testFixturesCompileOnly(libs.kotlin.dokkaCore)
+
+ // don't define test dependencies here, instead define them in the testing.suites {} configuration below
+}
+
+kotlin {
+ target {
+ compilations.configureEach {
+ // TODO Dokkatoo uses Gradle 8, while Dokka uses Gradle 7, which has an older version of Kotlin that
+ // doesn't include these options - so update them or update Gradle.
+// compilerOptions.configure {
+// freeCompilerArgs.addAll(
+// "-opt-in=org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi",
+// )
+// }
+ }
+ }
+}
+
+//region Test suites and task configuration
+testing.suites {
+ withType<JvmTestSuite>().configureEach {
+ useJUnitJupiter()
+
+ dependencies {
+ implementation(project.dependencies.gradleTestKit())
+ implementation(project.dependencies.testFixtures(project()))
+
+ compileOnly(libs.kotlin.dokkaCore)
+
+ implementation(project.dependencies.platform(libs.kotlinxSerialization.bom))
+ implementation(libs.kotlinxSerialization.json)
+ }
+
+ targets.configureEach {
+ testTask.configure {
+ javaLauncher.set(javaToolchains.launcherFor {
+ // Android test project requires Java 17
+ languageVersion.set(JavaLanguageVersion.of(17))
+ })
+
+ val projectTestTempDirPath = "$buildDir_/test-temp-dir"
+ inputs.property("projectTestTempDir", projectTestTempDirPath)
+ systemProperty("projectTestTempDir", projectTestTempDirPath)
+
+ // depend on the test-publication configuration, but not the test-maven repo dir
+ // (otherwise this task will never be up-to-date)
+ dependsOn(configurations.testMavenPublication)
+
+ // depend on example & integration-test projects setup
+ dependsOn(configurations.exampleProjects)
+ dependsOn(tasks.updateDokkatooExamples)
+
+ val dokkatooExamplesDir = configurations.exampleProjects.map {
+ it.incoming.files.singleFile.absolutePath
+ }
+
+ systemProperty("integrationTestProjectsDir", "$projectDir/projects")
+ systemProperty("testMavenRepoDir", file(mavenPublishTest.testMavenRepo).canonicalPath)
+ doFirst {
+ // workaround for lazy-properties not working https://github.com/gradle/gradle/issues/12247
+ systemProperty("exampleProjectsDir", dokkatooExamplesDir.get())
+ }
+ }
+ }
+ }
+
+ /** Examples tests suite */
+ val testExamples by registering(JvmTestSuite::class) {
+ description = "Test the example projects, from the 'examples' directory in the project root"
+ }
+
+ /** Integration tests suite */
+ val testIntegration by registering(JvmTestSuite::class) {
+ description =
+ "Test the integration template projects, in the dokkatoo-plugin-integration-tests/projects directory"
+ }
+
+ tasks.check { dependsOn(testExamples, testIntegration) }
+}
+
+
+tasks.withType<Test>().configureEach {
+ // this seems to help OOM errors in the Worker Daemons
+ //setForkEvery(1)
+ jvmArgs(
+ "-Xmx1g",
+ "-XX:MaxMetaspaceSize=512m",
+ )
+
+ mustRunAfter(tasks.withType<AbstractPublishToMaven>())
+
+ testLogging {
+ events = setOf(
+ TestLogEvent.STARTED,
+ TestLogEvent.PASSED,
+ TestLogEvent.SKIPPED,
+ TestLogEvent.FAILED,
+ TestLogEvent.STANDARD_OUT,
+ TestLogEvent.STANDARD_ERROR,
+ )
+ showStandardStreams = true
+ showExceptions = true
+ showCauses = true
+ showStackTraces = true
+ }
+}
+//endregion
+
+//region Example & Template projects setup
+dokkatooExampleProjects {
+ exampleProjects {
+ projectsItAndroid0Dokkatoo {
+ gradlePropertiesContent.add("android.useAndroidX=true")
+ }
+ }
+}
+
+dokkaTemplateProjects {
+
+ val androidLocalPropertiesFile = tasks.createAndroidLocalPropertiesFile.map {
+ it.outputs.files
+ }
+
+ register(
+ source = "integration-tests/gradle/projects/it-android-0",
+ destination = "projects/it-android-0/dokka",
+ ) {
+ additionalFiles.from(androidLocalPropertiesFile)
+ }
+ register(
+ source = "integration-tests/gradle/projects/it-basic",
+ destination = "projects/it-basic/dokka",
+ )
+ register(
+ source = "integration-tests/gradle/projects/it-basic-groovy",
+ destination = "projects/it-basic-groovy/dokka",
+ )
+ register(
+ source = "integration-tests/gradle/projects/it-collector-0",
+ destination = "projects/it-collector-0/dokka",
+ )
+ register(
+ source = "integration-tests/gradle/projects/it-js-ir-0",
+ destination = "projects/it-js-ir-0/dokka",
+ )
+ register(
+ source = "integration-tests/gradle/projects/it-multimodule-0",
+ destination = "projects/it-multimodule-0/dokka",
+ )
+ register(
+ source = "integration-tests/gradle/projects/it-multimodule-1",
+ destination = "projects/it-multimodule-1/dokka",
+ )
+ register(
+ source = "integration-tests/gradle/projects/it-multimodule-versioning-0",
+ destination = "projects/it-multimodule-versioning-0/dokka",
+ )
+ register(
+ source = "integration-tests/gradle/projects/it-multiplatform-0",
+ destination = "projects/it-multiplatform-0/dokka",
+ )
+
+// register("projects/coroutines/dokka") { }
+// register("projects/serialization/dokka") { }
+// register("projects/stdlib/dokka") { }
+
+ configureEach {
+ additionalPaths.addAll(
+ "integration-tests/gradle/projects/template.root.gradle.kts",
+ "integration-tests/gradle/projects/template.settings.gradle.kts",
+ )
+ }
+}
+
+tasks.setupDokkaTemplateProjects.configure {
+
+ val kotlinDokkaVersion = libs.versions.kotlin.dokka
+ inputs.property("kotlinDokkaVersion", kotlinDokkaVersion)
+
+ doLast {
+ outputs.files.asFileTree.files.forEach { file ->
+ when (file.name) {
+ "build.gradle.kts" -> {
+ file.writeText(
+ file.readText()
+ .replace(
+ """../template.root.gradle.kts""",
+ """./template.root.gradle.kts""",
+ ).replace(
+ """${'$'}{System.getenv("DOKKA_VERSION")}""",
+ kotlinDokkaVersion.get(),
+ )
+ )
+ }
+
+ "settings.gradle.kts" -> {
+ file.writeText(
+ file.readText()
+ .replace(
+ """../template.settings.gradle.kts""",
+ """./template.settings.gradle.kts""",
+ )
+ )
+ }
+
+ "template.settings.gradle.kts" -> {
+ file.writeText(
+ file.readText()
+ .replace(
+ """for-integration-tests-SNAPSHOT""",
+ kotlinDokkaVersion.get(),
+ )
+ )
+ }
+ }
+ }
+ }
+}
+
+tasks.withType<Test>().configureEach {
+ // this seems to help OOM errors in the Worker Daemons
+ //setForkEvery(1)
+ jvmArgs(
+ "-Xmx1g",
+ "-XX:MaxMetaspaceSize=512m",
+ "-XX:+AlwaysPreTouch", // https://github.com/gradle/gradle/issues/3093#issuecomment-387259298
+ )
+}
+
+dokkaSourceDownload {
+ dokkaVersion.set(libs.versions.kotlin.dokka)
+}
+
+tasks.updateAndroidLocalProperties {
+ mustRunAfter(tasks.withType<SetupDokkaProjects>())
+}
+
+tasks.updateDokkatooExamples {
+ dependsOn(tasks.updateAndroidLocalProperties)
+}
+//endregion
+
+skipTestFixturesPublications()
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/.gitignore b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/.gitignore
new file mode 100644
index 00000000..c0a92be3
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/.gitignore
@@ -0,0 +1,15 @@
+# These projects are run using Gradle TestKit, which does not require Gradle or IDE files.
+# However, it is convenient to open up the project in an IDEs, which will automatically
+# download Gradle files - but since they're not nessessary, don't commit them.
+.idea
+**/gradle/wrapper/**
+gradlew.bat
+gradlew
+
+# gradle.properties will be auto-updated by Gradle tasks
+gradle.properties
+
+ANDROID_SDK/*
+**/local.properties
+# Don't ignore licenses - they're required by Android to auto-download the SDK
+!ANDROID_SDK/licenses/
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-googletv-license b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-googletv-license
new file mode 100644
index 00000000..c34f9a12
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-googletv-license
@@ -0,0 +1,2 @@
+
+601085b94cd77f0b54ff86406957099ebe79c4d6
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-arm-dbt-license b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-arm-dbt-license
new file mode 100644
index 00000000..5cbbb66e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-arm-dbt-license
@@ -0,0 +1,2 @@
+
+859f317696f67ef3d7f30a50a5560e7834b43903
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-license b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-license
new file mode 100644
index 00000000..ee55294f
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-license
@@ -0,0 +1,2 @@
+
+24333f8a63b6825ea9c5514f83c2829b004d1fee
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-preview-license b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-preview-license
new file mode 100644
index 00000000..74069f8f
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/android-sdk-preview-license
@@ -0,0 +1,2 @@
+
+84831b9409646a918e30573bab4c9c91346d8abd
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/google-gdk-license b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/google-gdk-license
new file mode 100644
index 00000000..5c5a0a6f
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/google-gdk-license
@@ -0,0 +1,2 @@
+
+33b6a2b64607f11b759f320ef9dff4ae5c47d97a
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/intel-android-extra-license b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/intel-android-extra-license
new file mode 100644
index 00000000..3ba49310
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/intel-android-extra-license
@@ -0,0 +1,2 @@
+
+d975f751698a77b662f1254ddbeed3901e976f5a
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/mips-android-sysimage-license b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/mips-android-sysimage-license
new file mode 100644
index 00000000..5547e8de
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/ANDROID_SDK/licenses/mips-android-sysimage-license
@@ -0,0 +1,2 @@
+
+e9acab5b5fbb560a72cfaecce8946896ff6aab9d
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/build.gradle.kts
new file mode 100644
index 00000000..860666eb
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/build.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.dokka")
+ kotlin("android")
+}
+
+apply(from = "./template.root.gradle.kts")
+
+android {
+ defaultConfig {
+ minSdkVersion(21)
+ setCompileSdkVersion(29)
+ }
+}
+
+dependencies {
+ implementation("androidx.appcompat:appcompat:1.1.0")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/settings.gradle.kts
new file mode 100644
index 00000000..74641446
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/settings.gradle.kts
@@ -0,0 +1,5 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+apply(from = "./template.settings.gradle.kts")
+rootProject.name = "it-android-0"
+
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/AndroidManifest.xml b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a35f86be
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest package="org.jetbrains.dokka.it.android"/>
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/java/it/android/AndroidSpecificClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/java/it/android/AndroidSpecificClass.kt
new file mode 100644
index 00000000..cb9046b1
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/java/it/android/AndroidSpecificClass.kt
@@ -0,0 +1,16 @@
+@file:Suppress("unused")
+
+package it.android
+
+import android.content.Context
+import android.util.SparseIntArray
+import android.view.View
+
+/**
+ * This class is specific to android and uses android classes like:
+ * [Context], [SparseIntArray] or [View]
+ */
+class AndroidSpecificClass {
+ fun sparseIntArray() = SparseIntArray()
+ fun createView(context: Context): View = View(context)
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/java/it/android/IntegrationTestActivity.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/java/it/android/IntegrationTestActivity.kt
new file mode 100644
index 00000000..1792818b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/src/main/java/it/android/IntegrationTestActivity.kt
@@ -0,0 +1,22 @@
+package it.android
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+/**
+ * Some Activity implementing [AppCompatActivity] from android x
+ */
+class IntegrationTestActivity : AppCompatActivity() {
+ /**
+ * Will show a small happy text
+ */
+ @SuppressLint("SetTextI18n")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val textView = TextView(this)
+ textView.text = "I am so happy :)"
+ setContentView(textView)
+ }
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/template.root.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/template.root.gradle.kts
new file mode 100644
index 00000000..f63c28e7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/template.root.gradle.kts
@@ -0,0 +1,23 @@
+allprojects {
+ repositories {
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenLocal()
+ mavenCentral()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") {
+ content {
+ includeGroup("org.jetbrains.kotlinx")
+ }
+ }
+ }
+}
+
+afterEvaluate {
+ logger.quiet("Gradle version: ${gradle.gradleVersion}")
+ logger.quiet("Kotlin version: ${properties["dokka_it_kotlin_version"]}")
+ properties["dokka_it_android_gradle_plugin_version"]?.let { androidVersion ->
+ logger.quiet("Android version: $androidVersion")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/template.settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/template.settings.gradle.kts
new file mode 100644
index 00000000..b7e3195e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokka/template.settings.gradle.kts
@@ -0,0 +1,38 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+pluginManagement {
+ val dokka_it_kotlin_version: String by settings
+ val dokka_it_android_gradle_plugin_version: String? by settings
+
+ plugins {
+ id("org.jetbrains.kotlin.js") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.jvm") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.android") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.multiplatform") version dokka_it_kotlin_version
+ }
+
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "org.jetbrains.dokka") {
+ useModule("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0")
+ }
+
+ if (requested.id.id == "com.android.library") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+
+ if (requested.id.id == "com.android.application") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+ }
+ }
+ repositories {
+ mavenLocal()
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/build.gradle.kts
new file mode 100644
index 00000000..ebcfc7a4
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/build.gradle.kts
@@ -0,0 +1,32 @@
+plugins {
+ id("com.android.library") version "8.0.2"
+ kotlin("android") version "1.9.0"
+ id("org.jetbrains.dokka.dokkatoo") version "2.1.0-SNAPSHOT"
+}
+
+android {
+ namespace = "org.jetbrains.dokka.it.android"
+ defaultConfig {
+ minSdkVersion(21)
+ setCompileSdkVersion(29)
+ }
+}
+
+dependencies {
+ implementation("androidx.appcompat:appcompat:1.1.0")
+}
+
+tasks.withType<org.jetbrains.dokka.dokkatoo.tasks.DokkatooGenerateTask>().configureEach {
+ // Dokka and Dokkatoo fetch Source Set information in different ways. This results in the
+ // 'SourceSetId' for each DokkatooSourceSet being slightly different.
+ // The SourceSetId is not visible, and just because it's different in Dokka vs Dokkatoo, it
+ // doesn't have any impact. But for the purposes of automated testing, they need to be the same.
+ // So, forcibly rename the SourceSetId.
+
+ generator.dokkaSourceSets.configureEach {
+ // sourceSetScope renaming is fine, I'm not worried about it. The default comes from the
+ // Gradle Task name, so a name difference doesn't matter.
+ // We can just manually force the Dokkatoo name to match Dokka.
+ sourceSetScope.set(":dokkaHtml")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..5bea52c9
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/settings.gradle.kts
@@ -0,0 +1,19 @@
+rootProject.name = "it-android-0"
+
+pluginManagement {
+ repositories {
+ maven(providers.gradleProperty("testMavenRepo"))
+ mavenCentral()
+ google()
+ gradlePluginPortal()
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ maven(providers.gradleProperty("testMavenRepo"))
+ mavenCentral()
+ google()
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/AndroidManifest.xml b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a35f86be
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest package="org.jetbrains.dokka.it.android"/>
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/java/it/android/AndroidSpecificClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/java/it/android/AndroidSpecificClass.kt
new file mode 100644
index 00000000..cb9046b1
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/java/it/android/AndroidSpecificClass.kt
@@ -0,0 +1,16 @@
+@file:Suppress("unused")
+
+package it.android
+
+import android.content.Context
+import android.util.SparseIntArray
+import android.view.View
+
+/**
+ * This class is specific to android and uses android classes like:
+ * [Context], [SparseIntArray] or [View]
+ */
+class AndroidSpecificClass {
+ fun sparseIntArray() = SparseIntArray()
+ fun createView(context: Context): View = View(context)
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/java/it/android/IntegrationTestActivity.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/java/it/android/IntegrationTestActivity.kt
new file mode 100644
index 00000000..1792818b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo/src/main/java/it/android/IntegrationTestActivity.kt
@@ -0,0 +1,22 @@
+package it.android
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+/**
+ * Some Activity implementing [AppCompatActivity] from android x
+ */
+class IntegrationTestActivity : AppCompatActivity() {
+ /**
+ * Will show a small happy text
+ */
+ @SuppressLint("SetTextI18n")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val textView = TextView(this)
+ textView.text = "I am so happy :)"
+ setContentView(textView)
+ }
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/build.gradle b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/build.gradle
new file mode 100644
index 00000000..f368ed10
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/build.gradle
@@ -0,0 +1,54 @@
+plugins {
+ id 'org.jetbrains.kotlin.jvm'
+ id("org.jetbrains.dokka")
+}
+
+apply from: '../template.root.gradle.kts'
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib"
+}
+
+dokkaHtml {
+ outputDirectory = new File(buildDir, "/dokka/customHtml")
+ failOnWarning = false
+ dokkaSourceSets {
+ customSourceSet {
+ sourceRoot(file("src/main/java"))
+ sourceRoot(file("src/main/kotlin"))
+ displayName.set("custom")
+ reportUndocumented.set(true)
+ }
+
+ configureEach {
+ perPackageOption { // testing closures
+ matchingRegex.set(".*internal.*")
+ suppress.set(true)
+ }
+
+ sourceLink { // testing closures
+ localDirectory.set(file("src/main"))
+ remoteUrl.set(
+ new URL(
+ "https://github.com/Kotlin/dokka/tree/master/" +
+ "integration-tests/gradle/projects/it-basic-groovy/src/main"
+ )
+ )
+ }
+ }
+ }
+
+}
+
+dokkaJavadoc {
+ outputDirectory = new File(buildDir, "dokka/customJavadoc")
+}
+
+dokkaGfm {
+ outputDirectory = new File(buildDir, "dokka/customGfm")
+}
+
+dokkaJekyll {
+ outputDirectory = new File(buildDir, "dokka/customJekyll")
+}
+
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/settings.gradle.kts
new file mode 100644
index 00000000..87cf173c
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/settings.gradle.kts
@@ -0,0 +1,5 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+apply(from = "./template.settings.gradle.kts")
+rootProject.name = "it-basic-groovy"
+
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/src/main/java/it/basic/java/SampleJavaClass.java b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/src/main/java/it/basic/java/SampleJavaClass.java
new file mode 100644
index 00000000..23b0202c
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/src/main/java/it/basic/java/SampleJavaClass.java
@@ -0,0 +1,17 @@
+package it.basic.java;
+
+import it.basic.PublicClass;
+
+/**
+ * This class is, unlike {@link PublicClass}, written in Java
+ */
+@SuppressWarnings("unused")
+public class SampleJavaClass {
+
+ /**
+ * @return Empty instance of {@link PublicClass}
+ */
+ public PublicClass publicDocumentedFunction() {
+ return new PublicClass();
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/src/main/kotlin/it/basic/PublicClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/src/main/kotlin/it/basic/PublicClass.kt
new file mode 100644
index 00000000..71bc7e63
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/src/main/kotlin/it/basic/PublicClass.kt
@@ -0,0 +1,48 @@
+@file:Suppress("unused")
+
+package it.basic
+
+class PublicClass {
+ /**
+ * This function is public and documented
+ */
+ fun publicDocumentedFunction(): String = ""
+
+ fun publicUndocumentedFunction(): String = ""
+
+ /**
+ * This function is internal and documented
+ */
+ internal fun internalDocumentedFunction(): String = ""
+
+ internal fun internalUndocumentedFunction(): String = ""
+
+ /**
+ * This function is private and documented
+ */
+ private fun privateDocumentedFunction(): String = ""
+
+ private fun privateUndocumentedFunction(): String = ""
+
+
+ /**
+ * This property is public and documented
+ */
+ val publicDocumentedProperty: Int = 0
+
+ val publicUndocumentedProperty: Int = 0
+
+ /**
+ * This property internal and documented
+ */
+ val internalDocumentedProperty: Int = 0
+
+ val internalUndocumentedProperty: Int = 0
+
+ /**
+ * This property private and documented
+ */
+ private val privateDocumentedProperty: Int = 0
+
+ private val privateUndocumentedProperty: Int = 0
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/template.root.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/template.root.gradle.kts
new file mode 100644
index 00000000..f63c28e7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/template.root.gradle.kts
@@ -0,0 +1,23 @@
+allprojects {
+ repositories {
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenLocal()
+ mavenCentral()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") {
+ content {
+ includeGroup("org.jetbrains.kotlinx")
+ }
+ }
+ }
+}
+
+afterEvaluate {
+ logger.quiet("Gradle version: ${gradle.gradleVersion}")
+ logger.quiet("Kotlin version: ${properties["dokka_it_kotlin_version"]}")
+ properties["dokka_it_android_gradle_plugin_version"]?.let { androidVersion ->
+ logger.quiet("Android version: $androidVersion")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/template.settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/template.settings.gradle.kts
new file mode 100644
index 00000000..b7e3195e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokka/template.settings.gradle.kts
@@ -0,0 +1,38 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+pluginManagement {
+ val dokka_it_kotlin_version: String by settings
+ val dokka_it_android_gradle_plugin_version: String? by settings
+
+ plugins {
+ id("org.jetbrains.kotlin.js") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.jvm") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.android") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.multiplatform") version dokka_it_kotlin_version
+ }
+
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "org.jetbrains.dokka") {
+ useModule("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0")
+ }
+
+ if (requested.id.id == "com.android.library") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+
+ if (requested.id.id == "com.android.application") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+ }
+ }
+ repositories {
+ mavenLocal()
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokkatoo/settings.gradle b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokkatoo/settings.gradle
new file mode 100644
index 00000000..fd5763e1
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokkatoo/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven { url = providers.gradleProperty("testMavenRepo") }
+ }
+}
+
+rootProject.name = "it-basic-groovy"
+
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven { url = providers.gradleProperty("testMavenRepo") }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/build.gradle.kts
new file mode 100644
index 00000000..e81e35fd
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/build.gradle.kts
@@ -0,0 +1,63 @@
+import org.jetbrains.dokka.gradle.DokkaTask
+import org.jetbrains.dokka.gradle.kotlinSourceSet
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.base.DokkaBaseConfiguration
+import org.jetbrains.dokka.DokkaConfiguration
+import java.net.URL
+
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
+
+buildscript {
+ dependencies {
+ classpath("org.jetbrains.dokka:dokka-base:1.9.0")
+ }
+}
+
+version = "1.9.0-SNAPSHOT"
+
+apply(from = "./template.root.gradle.kts")
+
+dependencies {
+ testImplementation(kotlin("test-junit"))
+}
+
+tasks.withType<DokkaTask> {
+ moduleName.set("Basic Project")
+ dokkaSourceSets {
+ configureEach {
+ documentedVisibilities.set(
+ setOf(DokkaConfiguration.Visibility.PUBLIC, DokkaConfiguration.Visibility.PROTECTED)
+ )
+ suppressedFiles.from(file("src/main/kotlin/it/suppressedByPath"))
+ perPackageOption {
+ matchingRegex.set("it.suppressedByPackage.*")
+ suppress.set(true)
+ }
+ perPackageOption {
+ matchingRegex.set("it.overriddenVisibility.*")
+ documentedVisibilities.set(
+ setOf(DokkaConfiguration.Visibility.PRIVATE)
+ )
+ }
+ sourceLink {
+ localDirectory.set(file("src/main"))
+ remoteUrl.set(
+ URL(
+ "https://github.com/Kotlin/dokka/tree/master/" +
+ "integration-tests/gradle/projects/it-basic/src/main"
+ )
+ )
+ }
+ }
+
+ register("myTest") {
+ kotlinSourceSet(kotlin.sourceSets["test"])
+ }
+ }
+ suppressObviousFunctions.set(false)
+
+ pluginsMapConfiguration.set(mapOf(DokkaBase::class.qualifiedName to """{ "customStyleSheets": ["${file("../customResources/logo-styles.css").invariantSeparatorsPath}", "${file("../customResources/custom-style-to-add.css").invariantSeparatorsPath}"], "customAssets" : ["${file("../customResources/custom-resource.svg").invariantSeparatorsPath}"] }"""))
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/custom-resource.svg b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/custom-resource.svg
new file mode 100644
index 00000000..1865f739
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/custom-resource.svg
@@ -0,0 +1,3 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M18 9C18 14 14 18 9 18C4 18 0 14 0 9C0 4 4 0 9 0C14 0 18 4 18 9ZM14.2 6.2L12.8 4.8L7.5 10.1L5.3 7.8L3.8 9.2L7.5 13L14.2 6.2Z" fill="#4DBB5F"/>
+</svg> \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/custom-style-to-add.css b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/custom-style-to-add.css
new file mode 100644
index 00000000..408c210e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/custom-style-to-add.css
@@ -0,0 +1 @@
+/* custom stylesheet */ \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/logo-styles.css b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/logo-styles.css
new file mode 100644
index 00000000..2ac57218
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/customResources/logo-styles.css
@@ -0,0 +1,3 @@
+#logo {
+ background-image: url('https://upload.wikimedia.org/wikipedia/commons/9/9d/Ubuntu_logo.svg');
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/settings.gradle.kts
new file mode 100644
index 00000000..d563ebcf
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/settings.gradle.kts
@@ -0,0 +1,5 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+apply(from = "./template.settings.gradle.kts")
+rootProject.name = "it-basic"
+
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/java/it/basic/java/SampleJavaClass.java b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/java/it/basic/java/SampleJavaClass.java
new file mode 100644
index 00000000..23b0202c
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/java/it/basic/java/SampleJavaClass.java
@@ -0,0 +1,17 @@
+package it.basic.java;
+
+import it.basic.PublicClass;
+
+/**
+ * This class is, unlike {@link PublicClass}, written in Java
+ */
+@SuppressWarnings("unused")
+public class SampleJavaClass {
+
+ /**
+ * @return Empty instance of {@link PublicClass}
+ */
+ public PublicClass publicDocumentedFunction() {
+ return new PublicClass();
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/RootPackageClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/RootPackageClass.kt
new file mode 100644
index 00000000..8ff6c750
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/RootPackageClass.kt
@@ -0,0 +1,8 @@
+@file:Suppress("unused")
+
+/**
+ * A class that lives inside the root package
+ */
+class RootPackageClass {
+ val description = "I do live in the root package!"
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/basic/PublicClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/basic/PublicClass.kt
new file mode 100644
index 00000000..2958948c
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/basic/PublicClass.kt
@@ -0,0 +1,69 @@
+@file:Suppress("unused")
+
+package it.basic
+
+import RootPackageClass
+
+/**
+ * This class, unlike [RootPackageClass] is located in a sub-package
+ *
+ * §PUBLIC§ (marker for asserts)
+ */
+class PublicClass {
+ /**
+ * This function is public and documented
+ */
+ fun publicDocumentedFunction(): String = ""
+
+ fun publicUndocumentedFunction(): String = ""
+
+ /**
+ * This function is internal and documented
+ */
+ internal fun internalDocumentedFunction(): String = ""
+
+ internal fun internalUndocumentedFunction(): String = ""
+
+ /**
+ * This function is protected and documented
+ */
+ protected fun protectedDocumentedFunction(): String = ""
+
+ protected fun protectedUndocumentedFunction(): String = ""
+
+ /**
+ * This function is private and documented
+ */
+ private fun privateDocumentedFunction(): String = ""
+
+ private fun privateUndocumentedFunction(): String = ""
+
+
+ /**
+ * This property is public and documented
+ */
+ val publicDocumentedProperty: Int = 0
+
+ val publicUndocumentedProperty: Int = 0
+
+ /**
+ * This property internal and documented
+ */
+ val internalDocumentedProperty: Int = 0
+
+ val internalUndocumentedProperty: Int = 0
+
+ /**
+ * This property is protected and documented
+ */
+ val protectedDocumentedProperty: Int = 0
+
+ val protectedUndocumentedProperty: Int = 0
+
+ /**
+ * This property private and documented
+ */
+ private val privateDocumentedProperty: Int = 0
+
+ private val privateUndocumentedProperty: Int = 0
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/internal/InternalClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/internal/InternalClass.kt
new file mode 100644
index 00000000..6173d239
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/internal/InternalClass.kt
@@ -0,0 +1,7 @@
+package it.internal
+
+/**
+ * §INTERNAL§ (marker for asserts)
+ * This class is internal and should not be rendered
+ */
+internal class InternalClass
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/overriddenVisibility/VisiblePrivateClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/overriddenVisibility/VisiblePrivateClass.kt
new file mode 100644
index 00000000..230f5e0b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/overriddenVisibility/VisiblePrivateClass.kt
@@ -0,0 +1,12 @@
+package it.overriddenVisibility
+
+/**
+ * Private classes and methods generally should not be visible, but [documentedVisibilities]
+ * are overriden for this specific package to include private code
+ *
+ * §PRIVATE§ (marker for asserts)
+ */
+private class VisiblePrivateClass {
+ private val privateVal: Int = 0
+ private fun privateMethod() {}
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/protected/ProtectedClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/protected/ProtectedClass.kt
new file mode 100644
index 00000000..ad19f1a1
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/protected/ProtectedClass.kt
@@ -0,0 +1,10 @@
+package it.protected
+
+/**
+ * Protected class should be visible because it's included in documentedVisibilities
+ *
+ * §PROTECTED§ (marker for asserts)
+ */
+protected class ProtectedClass {
+ protected fun protectedFun(): String = "protected"
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt
new file mode 100644
index 00000000..d8dc9cff
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt
@@ -0,0 +1,7 @@
+package it.suppressedByPackage
+
+/**
+ * §SUPPRESSED§
+ * This should not be rendered.
+ */
+class SuppressedByPackage
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt
new file mode 100644
index 00000000..4dda9da4
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt
@@ -0,0 +1,7 @@
+package it.suppressedByPath
+
+/**
+ * §SUPPRESSED§
+ * This should not be rendered.
+ */
+class SuppressedByPath
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/test/kotlin/it/basic/TestClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/test/kotlin/it/basic/TestClass.kt
new file mode 100644
index 00000000..3584bdef
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/src/test/kotlin/it/basic/TestClass.kt
@@ -0,0 +1,17 @@
+package it.basic
+
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+annotation class OurAnnotation
+
+class TestClass {
+ /**
+ * Asserts something. [PublicClass]
+ */
+ @Test
+ @OurAnnotation
+ fun test() {
+ assertTrue(1 == 1)
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/template.root.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/template.root.gradle.kts
new file mode 100644
index 00000000..f63c28e7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/template.root.gradle.kts
@@ -0,0 +1,23 @@
+allprojects {
+ repositories {
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenLocal()
+ mavenCentral()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") {
+ content {
+ includeGroup("org.jetbrains.kotlinx")
+ }
+ }
+ }
+}
+
+afterEvaluate {
+ logger.quiet("Gradle version: ${gradle.gradleVersion}")
+ logger.quiet("Kotlin version: ${properties["dokka_it_kotlin_version"]}")
+ properties["dokka_it_android_gradle_plugin_version"]?.let { androidVersion ->
+ logger.quiet("Android version: $androidVersion")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/template.settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/template.settings.gradle.kts
new file mode 100644
index 00000000..b7e3195e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokka/template.settings.gradle.kts
@@ -0,0 +1,38 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+pluginManagement {
+ val dokka_it_kotlin_version: String by settings
+ val dokka_it_android_gradle_plugin_version: String? by settings
+
+ plugins {
+ id("org.jetbrains.kotlin.js") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.jvm") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.android") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.multiplatform") version dokka_it_kotlin_version
+ }
+
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "org.jetbrains.dokka") {
+ useModule("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0")
+ }
+
+ if (requested.id.id == "com.android.library") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+
+ if (requested.id.id == "com.android.application") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+ }
+ }
+ repositories {
+ mavenLocal()
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/build.gradle.kts
new file mode 100644
index 00000000..a6b70f6c
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/build.gradle.kts
@@ -0,0 +1,64 @@
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.VisibilityModifier
+import org.jetbrains.dokka.dokkatoo.dokka.plugins.DokkaHtmlPluginParameters
+
+plugins {
+ kotlin("jvm") version "1.9.0"
+ id("org.jetbrains.dokka.dokkatoo") version "2.1.0-SNAPSHOT"
+}
+
+version = "1.9.0-SNAPSHOT"
+
+dependencies {
+ testImplementation(kotlin("test-junit"))
+}
+
+kotlin {
+ jvmToolchain(8)
+}
+
+dokkatoo {
+ moduleName.set("Basic Project")
+ dokkatooSourceSets.configureEach {
+ documentedVisibilities(
+ VisibilityModifier.PUBLIC,
+ VisibilityModifier.PROTECTED,
+ )
+ suppressedFiles.from(file("src/main/kotlin/it/suppressedByPath"))
+ perPackageOption {
+ matchingRegex.set("it.suppressedByPackage.*")
+ suppress.set(true)
+ }
+ perPackageOption {
+ matchingRegex.set("it.overriddenVisibility.*")
+ documentedVisibilities(
+ VisibilityModifier.PRIVATE,
+ )
+ }
+ sourceLink {
+ localDirectory.set(file("src/main"))
+ remoteUrl(
+ "https://github.com/Kotlin/dokka/tree/master/integration-tests/gradle/projects/it-basic/src/main"
+ )
+ }
+ }
+
+ pluginsConfiguration.named<DokkaHtmlPluginParameters>("html") {
+ customStyleSheets.from(
+ "./customResources/logo-styles.css",
+ "./customResources/custom-style-to-add.css",
+ )
+ customAssets.from(
+ "./customResources/custom-resource.svg",
+ )
+ }
+
+ dokkatooPublications.configureEach {
+ suppressObviousFunctions.set(false)
+ }
+}
+
+tasks.withType<org.jetbrains.dokka.dokkatoo.tasks.DokkatooGenerateTask>().configureEach {
+ generator.dokkaSourceSets.configureEach {
+ sourceSetScope.set(":dokkaHtml")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/custom-resource.svg b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/custom-resource.svg
new file mode 100644
index 00000000..1865f739
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/custom-resource.svg
@@ -0,0 +1,3 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M18 9C18 14 14 18 9 18C4 18 0 14 0 9C0 4 4 0 9 0C14 0 18 4 18 9ZM14.2 6.2L12.8 4.8L7.5 10.1L5.3 7.8L3.8 9.2L7.5 13L14.2 6.2Z" fill="#4DBB5F"/>
+</svg> \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/custom-style-to-add.css b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/custom-style-to-add.css
new file mode 100644
index 00000000..408c210e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/custom-style-to-add.css
@@ -0,0 +1 @@
+/* custom stylesheet */ \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/logo-styles.css b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/logo-styles.css
new file mode 100644
index 00000000..2ac57218
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/customResources/logo-styles.css
@@ -0,0 +1,3 @@
+#logo {
+ background-image: url('https://upload.wikimedia.org/wikipedia/commons/9/9d/Ubuntu_logo.svg');
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..a2872f47
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "it-basic"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/java/it/basic/java/SampleJavaClass.java b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/java/it/basic/java/SampleJavaClass.java
new file mode 100644
index 00000000..23b0202c
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/java/it/basic/java/SampleJavaClass.java
@@ -0,0 +1,17 @@
+package it.basic.java;
+
+import it.basic.PublicClass;
+
+/**
+ * This class is, unlike {@link PublicClass}, written in Java
+ */
+@SuppressWarnings("unused")
+public class SampleJavaClass {
+
+ /**
+ * @return Empty instance of {@link PublicClass}
+ */
+ public PublicClass publicDocumentedFunction() {
+ return new PublicClass();
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/RootPackageClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/RootPackageClass.kt
new file mode 100644
index 00000000..8ff6c750
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/RootPackageClass.kt
@@ -0,0 +1,8 @@
+@file:Suppress("unused")
+
+/**
+ * A class that lives inside the root package
+ */
+class RootPackageClass {
+ val description = "I do live in the root package!"
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/basic/PublicClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/basic/PublicClass.kt
new file mode 100644
index 00000000..2958948c
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/basic/PublicClass.kt
@@ -0,0 +1,69 @@
+@file:Suppress("unused")
+
+package it.basic
+
+import RootPackageClass
+
+/**
+ * This class, unlike [RootPackageClass] is located in a sub-package
+ *
+ * §PUBLIC§ (marker for asserts)
+ */
+class PublicClass {
+ /**
+ * This function is public and documented
+ */
+ fun publicDocumentedFunction(): String = ""
+
+ fun publicUndocumentedFunction(): String = ""
+
+ /**
+ * This function is internal and documented
+ */
+ internal fun internalDocumentedFunction(): String = ""
+
+ internal fun internalUndocumentedFunction(): String = ""
+
+ /**
+ * This function is protected and documented
+ */
+ protected fun protectedDocumentedFunction(): String = ""
+
+ protected fun protectedUndocumentedFunction(): String = ""
+
+ /**
+ * This function is private and documented
+ */
+ private fun privateDocumentedFunction(): String = ""
+
+ private fun privateUndocumentedFunction(): String = ""
+
+
+ /**
+ * This property is public and documented
+ */
+ val publicDocumentedProperty: Int = 0
+
+ val publicUndocumentedProperty: Int = 0
+
+ /**
+ * This property internal and documented
+ */
+ val internalDocumentedProperty: Int = 0
+
+ val internalUndocumentedProperty: Int = 0
+
+ /**
+ * This property is protected and documented
+ */
+ val protectedDocumentedProperty: Int = 0
+
+ val protectedUndocumentedProperty: Int = 0
+
+ /**
+ * This property private and documented
+ */
+ private val privateDocumentedProperty: Int = 0
+
+ private val privateUndocumentedProperty: Int = 0
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/internal/InternalClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/internal/InternalClass.kt
new file mode 100644
index 00000000..6173d239
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/internal/InternalClass.kt
@@ -0,0 +1,7 @@
+package it.internal
+
+/**
+ * §INTERNAL§ (marker for asserts)
+ * This class is internal and should not be rendered
+ */
+internal class InternalClass
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/overriddenVisibility/VisiblePrivateClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/overriddenVisibility/VisiblePrivateClass.kt
new file mode 100644
index 00000000..230f5e0b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/overriddenVisibility/VisiblePrivateClass.kt
@@ -0,0 +1,12 @@
+package it.overriddenVisibility
+
+/**
+ * Private classes and methods generally should not be visible, but [documentedVisibilities]
+ * are overriden for this specific package to include private code
+ *
+ * §PRIVATE§ (marker for asserts)
+ */
+private class VisiblePrivateClass {
+ private val privateVal: Int = 0
+ private fun privateMethod() {}
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/protected/ProtectedClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/protected/ProtectedClass.kt
new file mode 100644
index 00000000..ad19f1a1
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/protected/ProtectedClass.kt
@@ -0,0 +1,10 @@
+package it.protected
+
+/**
+ * Protected class should be visible because it's included in documentedVisibilities
+ *
+ * §PROTECTED§ (marker for asserts)
+ */
+protected class ProtectedClass {
+ protected fun protectedFun(): String = "protected"
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt
new file mode 100644
index 00000000..d8dc9cff
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt
@@ -0,0 +1,7 @@
+package it.suppressedByPackage
+
+/**
+ * §SUPPRESSED§
+ * This should not be rendered.
+ */
+class SuppressedByPackage
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt
new file mode 100644
index 00000000..4dda9da4
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt
@@ -0,0 +1,7 @@
+package it.suppressedByPath
+
+/**
+ * §SUPPRESSED§
+ * This should not be rendered.
+ */
+class SuppressedByPath
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/test/kotlin/it/basic/TestClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/test/kotlin/it/basic/TestClass.kt
new file mode 100644
index 00000000..3584bdef
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo/src/test/kotlin/it/basic/TestClass.kt
@@ -0,0 +1,17 @@
+package it.basic
+
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+annotation class OurAnnotation
+
+class TestClass {
+ /**
+ * Asserts something. [PublicClass]
+ */
+ @Test
+ @OurAnnotation
+ fun test() {
+ assertTrue(1 == 1)
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/build.gradle.kts
new file mode 100644
index 00000000..062f1eb7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/build.gradle.kts
@@ -0,0 +1 @@
+apply(from = "./template.root.gradle.kts")
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/build.gradle.kts
new file mode 100644
index 00000000..ab86c333
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/build.gradle.kts
@@ -0,0 +1,6 @@
+plugins {
+ // TODO: File bug report for gradle: :moduleA:moduleB:dokkaHtml is missing kotlin gradle plugin from
+ // the runtime classpath during execution without this plugin in the parent project
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/README.md b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/README.md
new file mode 100644
index 00000000..f8c52880
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/README.md
@@ -0,0 +1,2 @@
+# Module moduleB
+Here is some description for module B
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/build.gradle.kts
new file mode 100644
index 00000000..9f7e98de
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/build.gradle.kts
@@ -0,0 +1,4 @@
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/src/main/kotlin/org/jetbrains/dokka/it/moduleB/ModuleB.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/src/main/kotlin/org/jetbrains/dokka/it/moduleB/ModuleB.kt
new file mode 100644
index 00000000..430e2234
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleB/src/main/kotlin/org/jetbrains/dokka/it/moduleB/ModuleB.kt
@@ -0,0 +1,6 @@
+package org.jetbrains.dokka.it.moduleB
+
+@Suppress("unused")
+class ModuleB {
+ fun undocumentedPublicFunction() {}
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/README.md b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/README.md
new file mode 100644
index 00000000..4ead5671
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/README.md
@@ -0,0 +1,2 @@
+# Module moduleC
+Here is some description for module C
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/build.gradle.kts
new file mode 100644
index 00000000..9f7e98de
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/build.gradle.kts
@@ -0,0 +1,4 @@
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/src/main/kotlin/org/jetbrains/dokka/it/moduleC/ModuleC.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/src/main/kotlin/org/jetbrains/dokka/it/moduleC/ModuleC.kt
new file mode 100644
index 00000000..e14d68e0
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/moduleA/moduleC/src/main/kotlin/org/jetbrains/dokka/it/moduleC/ModuleC.kt
@@ -0,0 +1,6 @@
+package org.jetbrains.dokka.it.moduleC
+
+@Suppress("unused")
+class ModuleC {
+ fun undocumentedPublicFunction() {}
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/settings.gradle.kts
new file mode 100644
index 00000000..a1b4baa6
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/settings.gradle.kts
@@ -0,0 +1,5 @@
+apply(from = "./template.settings.gradle.kts")
+rootProject.name = "it-multimodule-0"
+include(":moduleA")
+include(":moduleA:moduleB")
+include(":moduleA:moduleC")
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/template.root.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/template.root.gradle.kts
new file mode 100644
index 00000000..f63c28e7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/template.root.gradle.kts
@@ -0,0 +1,23 @@
+allprojects {
+ repositories {
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenLocal()
+ mavenCentral()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") {
+ content {
+ includeGroup("org.jetbrains.kotlinx")
+ }
+ }
+ }
+}
+
+afterEvaluate {
+ logger.quiet("Gradle version: ${gradle.gradleVersion}")
+ logger.quiet("Kotlin version: ${properties["dokka_it_kotlin_version"]}")
+ properties["dokka_it_android_gradle_plugin_version"]?.let { androidVersion ->
+ logger.quiet("Android version: $androidVersion")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/template.settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/template.settings.gradle.kts
new file mode 100644
index 00000000..b7e3195e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokka/template.settings.gradle.kts
@@ -0,0 +1,38 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+pluginManagement {
+ val dokka_it_kotlin_version: String by settings
+ val dokka_it_android_gradle_plugin_version: String? by settings
+
+ plugins {
+ id("org.jetbrains.kotlin.js") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.jvm") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.android") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.multiplatform") version dokka_it_kotlin_version
+ }
+
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "org.jetbrains.dokka") {
+ useModule("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0")
+ }
+
+ if (requested.id.id == "com.android.library") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+
+ if (requested.id.id == "com.android.application") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+ }
+ }
+ repositories {
+ mavenLocal()
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..13811d9a
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "it-collector-0"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/build.gradle.kts
new file mode 100644
index 00000000..fc353172
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ id("org.jetbrains.dokka")
+ kotlin("js")
+}
+
+apply(from = "./template.root.gradle.kts")
+
+kotlin {
+ js(IR) {
+ browser()
+ nodejs()
+ }
+}
+
+dependencies {
+ implementation(npm("is-sorted", "1.0.5"))
+
+ val reactVersion = properties["react_version"]
+ implementation("org.jetbrains.kotlin-wrappers:kotlin-react:$reactVersion")
+ implementation("org.jetbrains.kotlin-wrappers:kotlin-react-dom:$reactVersion")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/settings.gradle.kts
new file mode 100644
index 00000000..431c0715
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/settings.gradle.kts
@@ -0,0 +1,5 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+apply(from = "./template.settings.gradle.kts")
+rootProject.name = "it-js-ir-0"
+
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/RootPackageClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/RootPackageClass.kt
new file mode 100644
index 00000000..cbe6240e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/RootPackageClass.kt
@@ -0,0 +1,26 @@
+@file:Suppress("unused")
+
+import org.w3c.dom.url.URLSearchParams
+import org.w3c.dom.HTMLAnchorElement
+import react.dom.html.AnchorHTMLAttributes
+import react.Props
+import react.State
+
+/**
+ * A class that lives inside the root package
+ */
+class RootPackageClass {
+ val description = "I do live in the root package!"
+}
+
+// sample method that uses classes from dom and react, should compile
+fun URLSearchParams.react(props: Props, state: State) {}
+
+fun test(list: MutableList<Int>) = "list"
+
+@JsModule("is-sorted")
+@JsNonModule
+external fun <T> sorted(a: Array<T>): Boolean
+
+// this declaration can be used to check deserialization of dynamic type
+external interface TextLinkProps: AnchorHTMLAttributes<HTMLAnchorElement> \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/basic/PublicClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/basic/PublicClass.kt
new file mode 100644
index 00000000..fc4b36bd
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/basic/PublicClass.kt
@@ -0,0 +1,53 @@
+@file:Suppress("unused")
+
+package it.basic
+
+import RootPackageClass
+
+/**
+ * This class, unlike [RootPackageClass] is located in a sub-package
+ */
+class PublicClass {
+ /**
+ * This function is public and documented
+ */
+ fun publicDocumentedFunction(): String = ""
+
+ fun publicUndocumentedFunction(): String = ""
+
+ /**
+ * This function is internal and documented
+ */
+ internal fun internalDocumentedFunction(): String = ""
+
+ internal fun internalUndocumentedFunction(): String = ""
+
+ /**
+ * This function is private and documented
+ */
+ private fun privateDocumentedFunction(): String = ""
+
+ private fun privateUndocumentedFunction(): String = ""
+
+
+ /**
+ * This property is public and documented
+ */
+ val publicDocumentedProperty: Int = 0
+
+ val publicUndocumentedProperty: Int = 0
+
+ /**
+ * This property internal and documented
+ */
+ val internalDocumentedProperty: Int = 0
+
+ val internalUndocumentedProperty: Int = 0
+
+ /**
+ * This property private and documented
+ */
+ private val privateDocumentedProperty: Int = 0
+
+ private val privateUndocumentedProperty: Int = 0
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/internal/InternalClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/internal/InternalClass.kt
new file mode 100644
index 00000000..7d42b978
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/internal/InternalClass.kt
@@ -0,0 +1,7 @@
+package it.internal
+
+/**
+ * §INTERNAL§
+ * This class is internal and should not be rendered
+ */
+internal class InternalClass
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt
new file mode 100644
index 00000000..d8dc9cff
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/suppressedByPackage/SuppressedByPackage.kt
@@ -0,0 +1,7 @@
+package it.suppressedByPackage
+
+/**
+ * §SUPPRESSED§
+ * This should not be rendered.
+ */
+class SuppressedByPackage
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt
new file mode 100644
index 00000000..4dda9da4
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/src/main/kotlin/it/suppressedByPath/SuppressedByPath.kt
@@ -0,0 +1,7 @@
+package it.suppressedByPath
+
+/**
+ * §SUPPRESSED§
+ * This should not be rendered.
+ */
+class SuppressedByPath
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/template.root.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/template.root.gradle.kts
new file mode 100644
index 00000000..f63c28e7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/template.root.gradle.kts
@@ -0,0 +1,23 @@
+allprojects {
+ repositories {
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenLocal()
+ mavenCentral()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") {
+ content {
+ includeGroup("org.jetbrains.kotlinx")
+ }
+ }
+ }
+}
+
+afterEvaluate {
+ logger.quiet("Gradle version: ${gradle.gradleVersion}")
+ logger.quiet("Kotlin version: ${properties["dokka_it_kotlin_version"]}")
+ properties["dokka_it_android_gradle_plugin_version"]?.let { androidVersion ->
+ logger.quiet("Android version: $androidVersion")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/template.settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/template.settings.gradle.kts
new file mode 100644
index 00000000..b7e3195e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokka/template.settings.gradle.kts
@@ -0,0 +1,38 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+pluginManagement {
+ val dokka_it_kotlin_version: String by settings
+ val dokka_it_android_gradle_plugin_version: String? by settings
+
+ plugins {
+ id("org.jetbrains.kotlin.js") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.jvm") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.android") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.multiplatform") version dokka_it_kotlin_version
+ }
+
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "org.jetbrains.dokka") {
+ useModule("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0")
+ }
+
+ if (requested.id.id == "com.android.library") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+
+ if (requested.id.id == "com.android.application") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+ }
+ }
+ repositories {
+ mavenLocal()
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..85b71839
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "it-js-ir-0"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/build.gradle.kts
new file mode 100644
index 00000000..062f1eb7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/build.gradle.kts
@@ -0,0 +1 @@
+apply(from = "./template.root.gradle.kts")
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/build.gradle.kts
new file mode 100644
index 00000000..ab86c333
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/build.gradle.kts
@@ -0,0 +1,6 @@
+plugins {
+ // TODO: File bug report for gradle: :moduleA:moduleB:dokkaHtml is missing kotlin gradle plugin from
+ // the runtime classpath during execution without this plugin in the parent project
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/Module.md b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/Module.md
new file mode 100644
index 00000000..0570f467
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/Module.md
@@ -0,0 +1,6 @@
+# Module !Module B!
+Here is some description for Module B
+
+Module B: Second paragraph
+# Module moduleB
+§IGNORED$This documentation shall be ignored, because wrong module name§IGNORED$
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/build.gradle.kts
new file mode 100644
index 00000000..1981701f
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/build.gradle.kts
@@ -0,0 +1,13 @@
+import org.jetbrains.dokka.gradle.DokkaTask
+
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
+
+tasks.withType<DokkaTask>().configureEach {
+ moduleName.set("!Module B!")
+ dokkaSourceSets.configureEach {
+ includes.from("Module.md")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/src/main/kotlin/org/jetbrains/dokka/it/moduleB/ModuleB.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/src/main/kotlin/org/jetbrains/dokka/it/moduleB/ModuleB.kt
new file mode 100644
index 00000000..430e2234
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleB/src/main/kotlin/org/jetbrains/dokka/it/moduleB/ModuleB.kt
@@ -0,0 +1,6 @@
+package org.jetbrains.dokka.it.moduleB
+
+@Suppress("unused")
+class ModuleB {
+ fun undocumentedPublicFunction() {}
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/Module.md b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/Module.md
new file mode 100644
index 00000000..4ead5671
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/Module.md
@@ -0,0 +1,2 @@
+# Module moduleC
+Here is some description for module C
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/build.gradle.kts
new file mode 100644
index 00000000..728e764d
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/build.gradle.kts
@@ -0,0 +1,12 @@
+import org.jetbrains.dokka.gradle.DokkaTask
+
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
+
+tasks.withType<DokkaTask>().configureEach {
+ dokkaSourceSets.configureEach {
+ includes.from("Module.md")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/src/main/kotlin/org/jetbrains/dokka/it/moduleC/ModuleC.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/src/main/kotlin/org/jetbrains/dokka/it/moduleC/ModuleC.kt
new file mode 100644
index 00000000..e14d68e0
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleC/src/main/kotlin/org/jetbrains/dokka/it/moduleC/ModuleC.kt
@@ -0,0 +1,6 @@
+package org.jetbrains.dokka.it.moduleC
+
+@Suppress("unused")
+class ModuleC {
+ fun undocumentedPublicFunction() {}
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleD/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleD/build.gradle.kts
new file mode 100644
index 00000000..e5edf940
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleD/build.gradle.kts
@@ -0,0 +1,6 @@
+import org.jetbrains.dokka.gradle.DokkaTask
+
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.dokka")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleD/src/main/kotlin/org/jetbrains/dokka/it/moduleD/ModuleC.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleD/src/main/kotlin/org/jetbrains/dokka/it/moduleD/ModuleC.kt
new file mode 100644
index 00000000..88174d53
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/moduleA/moduleD/src/main/kotlin/org/jetbrains/dokka/it/moduleD/ModuleC.kt
@@ -0,0 +1,6 @@
+package org.jetbrains.dokka.it.moduleD
+
+@Suppress("unused")
+class ModuleD {
+ fun undocumentedPublicFunction() {}
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/settings.gradle.kts
new file mode 100644
index 00000000..20cde260
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/settings.gradle.kts
@@ -0,0 +1,6 @@
+apply(from = "./template.settings.gradle.kts")
+rootProject.name = "it-multimodule-0"
+include(":moduleA")
+include(":moduleA:moduleB")
+include(":moduleA:moduleC")
+include(":moduleA:moduleD")
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/template.root.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/template.root.gradle.kts
new file mode 100644
index 00000000..f63c28e7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/template.root.gradle.kts
@@ -0,0 +1,23 @@
+allprojects {
+ repositories {
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenLocal()
+ mavenCentral()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") {
+ content {
+ includeGroup("org.jetbrains.kotlinx")
+ }
+ }
+ }
+}
+
+afterEvaluate {
+ logger.quiet("Gradle version: ${gradle.gradleVersion}")
+ logger.quiet("Kotlin version: ${properties["dokka_it_kotlin_version"]}")
+ properties["dokka_it_android_gradle_plugin_version"]?.let { androidVersion ->
+ logger.quiet("Android version: $androidVersion")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/template.settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/template.settings.gradle.kts
new file mode 100644
index 00000000..b7e3195e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokka/template.settings.gradle.kts
@@ -0,0 +1,38 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+pluginManagement {
+ val dokka_it_kotlin_version: String by settings
+ val dokka_it_android_gradle_plugin_version: String? by settings
+
+ plugins {
+ id("org.jetbrains.kotlin.js") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.jvm") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.android") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.multiplatform") version dokka_it_kotlin_version
+ }
+
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "org.jetbrains.dokka") {
+ useModule("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0")
+ }
+
+ if (requested.id.id == "com.android.library") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+
+ if (requested.id.id == "com.android.application") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+ }
+ }
+ repositories {
+ mavenLocal()
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..97712ae9
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "it-multimodule-0"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/build.gradle b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/build.gradle
new file mode 100644
index 00000000..57d22b79
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/build.gradle
@@ -0,0 +1,18 @@
+plugins {
+ id 'org.jetbrains.kotlin.jvm'
+ id("org.jetbrains.dokka")
+}
+
+apply from: '../template.root.gradle.kts'
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib"
+ implementation project(':first')
+ implementation project(':second')
+}
+
+
+subprojects {
+ apply plugin: 'org.jetbrains.kotlin.jvm'
+ apply plugin: 'org.jetbrains.dokka'
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/build.gradle b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/build.gradle
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/build.gradle
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/FirstClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/FirstClass.kt
new file mode 100644
index 00000000..93f73bf4
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/FirstClass.kt
@@ -0,0 +1,11 @@
+package foo
+
+/**
+ * First class description
+ */
+open class FirstClass{
+ /**
+ * PropertyOne description
+ */
+ val propertyOne: Int = 5
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/FirstSubclass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/FirstSubclass.kt
new file mode 100644
index 00000000..0deb65c0
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/FirstSubclass.kt
@@ -0,0 +1,12 @@
+package foo
+
+/**
+ * Subclass description
+ * @property surname Surname description
+ */
+class FirstSubclass(var surname: String) : FirstClass() {
+ /**
+ * printNewLine description
+ */
+ fun printNewline() = print("\r\n")
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/Main.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/Main.kt
new file mode 100644
index 00000000..8c7f58a7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/foo/Main.kt
@@ -0,0 +1,8 @@
+package foo
+
+/**
+ * Main function
+ */
+fun main(args : Array<String>) {
+ println("Hello, world!")
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/noPackage.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/noPackage.kt
new file mode 100644
index 00000000..c41e218f
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/first/src/main/kotlin/noPackage.kt
@@ -0,0 +1,3 @@
+fun noPackage(): String = "Hello there"
+
+open class NoPackage
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/build.gradle b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/build.gradle
new file mode 100644
index 00000000..2b62f963
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/build.gradle
@@ -0,0 +1,14 @@
+dependencies {
+ implementation project(":first")
+}
+dokkaHtml {
+ dependsOn(":first:dokkaHtml")
+ dokkaSourceSets {
+ "main" {
+ externalDocumentationLink {
+ url.set(new URL("file://" + rootProject.rootDir.toPath().toAbsolutePath().resolve("first/build/dokka/html/")))
+ packageListUrl.set(new URL("file://" + rootProject.rootDir.toPath().toAbsolutePath().resolve("first/build/dokka/html/first/package-list")))
+ }
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/NoPackageClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/NoPackageClass.kt
new file mode 100644
index 00000000..d2f30ef7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/NoPackageClass.kt
@@ -0,0 +1 @@
+class NoPackageClass : NoPackage()
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/bar/SecondClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/bar/SecondClass.kt
new file mode 100644
index 00000000..6a0c935e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/bar/SecondClass.kt
@@ -0,0 +1,21 @@
+package bar
+/**
+ * Second class in second module description [foo.FirstClass]
+ * @author John Doe
+ * @version 0.1.3
+ * @param name Name description text text text.
+*/
+class SecondClass(val name: String) {
+ val firstProperty = "propertystring"
+ /**
+ * Second property in second module description [foo.FirstSubclass]
+ */
+ var secondProperty: String = ""
+ set(value) {
+ println("Second property not set")
+ }
+
+ init {
+ println("InitializerBlock")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/foo/ThirdClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/foo/ThirdClass.kt
new file mode 100644
index 00000000..cc24a6b7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/second/src/main/kotlin/foo/ThirdClass.kt
@@ -0,0 +1,11 @@
+package foo
+
+/**
+ * Third class description
+ */
+open class ThirdClass{
+ /**
+ * PropertyOne description
+ */
+ val propertyOne: Int = 5
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/settings.gradle.kts
new file mode 100644
index 00000000..753b6ec2
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/settings.gradle.kts
@@ -0,0 +1,4 @@
+apply(from = "./template.settings.gradle.kts")
+rootProject.name = "it-multimodule-1"
+include(":first")
+include(":second")
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/template.root.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/template.root.gradle.kts
new file mode 100644
index 00000000..f63c28e7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/template.root.gradle.kts
@@ -0,0 +1,23 @@
+allprojects {
+ repositories {
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenLocal()
+ mavenCentral()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") {
+ content {
+ includeGroup("org.jetbrains.kotlinx")
+ }
+ }
+ }
+}
+
+afterEvaluate {
+ logger.quiet("Gradle version: ${gradle.gradleVersion}")
+ logger.quiet("Kotlin version: ${properties["dokka_it_kotlin_version"]}")
+ properties["dokka_it_android_gradle_plugin_version"]?.let { androidVersion ->
+ logger.quiet("Android version: $androidVersion")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/template.settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/template.settings.gradle.kts
new file mode 100644
index 00000000..b7e3195e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokka/template.settings.gradle.kts
@@ -0,0 +1,38 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+pluginManagement {
+ val dokka_it_kotlin_version: String by settings
+ val dokka_it_android_gradle_plugin_version: String? by settings
+
+ plugins {
+ id("org.jetbrains.kotlin.js") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.jvm") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.android") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.multiplatform") version dokka_it_kotlin_version
+ }
+
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "org.jetbrains.dokka") {
+ useModule("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0")
+ }
+
+ if (requested.id.id == "com.android.library") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+
+ if (requested.id.id == "com.android.application") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+ }
+ }
+ repositories {
+ mavenLocal()
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..6ce1808e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "it-multimodule-1"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/build.gradle b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/build.gradle
new file mode 100644
index 00000000..e04e412b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/build.gradle
@@ -0,0 +1,43 @@
+import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
+
+plugins {
+ id 'org.jetbrains.kotlin.jvm'
+ id("org.jetbrains.dokka")
+}
+
+apply from: '../template.root.gradle.kts'
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib"
+ dokkaPlugin "org.jetbrains.dokka:versioning-plugin:${System.getenv("DOKKA_VERSION")}"
+}
+
+
+subprojects {
+ apply plugin: 'org.jetbrains.kotlin.jvm'
+ apply plugin: 'org.jetbrains.dokka'
+}
+
+dokkaHtmlMultiModule {
+ pluginsMapConfiguration.set(["org.jetbrains.dokka.versioning.VersioningPlugin": """{ "version": "1.2", "olderVersionsDir": "$projectDir/dokkas" }"""])
+}
+
+tasks.register('dokkaHtmlMultiModuleBaseVersion', DokkaMultiModuleTask){
+ dependencies {
+ dokkaPlugin("org.jetbrains.dokka:all-modules-page-plugin:${System.getenv("DOKKA_VERSION")}")
+ dokkaPlugin("org.jetbrains.dokka:versioning-plugin:${System.getenv("DOKKA_VERSION")}")
+ }
+ outputDirectory.set(file(projectDir.toPath().resolve("dokkas").resolve("1.0")))
+ pluginsMapConfiguration.set(["org.jetbrains.dokka.versioning.VersioningPlugin": """{ "version": "1.0" }"""])
+ addChildTasks([project(":first"), project(":second")], "dokkaHtmlPartial")
+}
+
+tasks.register('dokkaHtmlMultiModuleNextVersion', DokkaMultiModuleTask){
+ dependencies {
+ dokkaPlugin("org.jetbrains.dokka:all-modules-page-plugin:${System.getenv("DOKKA_VERSION")}")
+ dokkaPlugin("org.jetbrains.dokka:versioning-plugin:${System.getenv("DOKKA_VERSION")}")
+ }
+ outputDirectory.set(file(projectDir.toPath().resolve("dokkas").resolve("1.1")))
+ pluginsMapConfiguration.set(["org.jetbrains.dokka.versioning.VersioningPlugin": """{ "version": "1.1", "olderVersionsDir": "$projectDir/dokkas" }"""])
+ addChildTasks([project(":first"), project(":second")], "dokkaHtmlPartial")
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/first/build.gradle b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/first/build.gradle
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/first/build.gradle
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/first/src/main/kotlin/foo/FirstClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/first/src/main/kotlin/foo/FirstClass.kt
new file mode 100644
index 00000000..93f73bf4
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/first/src/main/kotlin/foo/FirstClass.kt
@@ -0,0 +1,11 @@
+package foo
+
+/**
+ * First class description
+ */
+open class FirstClass{
+ /**
+ * PropertyOne description
+ */
+ val propertyOne: Int = 5
+} \ No newline at end of file
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/second/build.gradle b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/second/build.gradle
new file mode 100644
index 00000000..cf1d1f21
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/second/build.gradle
@@ -0,0 +1,3 @@
+dependencies {
+ implementation project(":first")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/second/src/main/kotlin/bar/SecondClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/second/src/main/kotlin/bar/SecondClass.kt
new file mode 100644
index 00000000..6a0c935e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/second/src/main/kotlin/bar/SecondClass.kt
@@ -0,0 +1,21 @@
+package bar
+/**
+ * Second class in second module description [foo.FirstClass]
+ * @author John Doe
+ * @version 0.1.3
+ * @param name Name description text text text.
+*/
+class SecondClass(val name: String) {
+ val firstProperty = "propertystring"
+ /**
+ * Second property in second module description [foo.FirstSubclass]
+ */
+ var secondProperty: String = ""
+ set(value) {
+ println("Second property not set")
+ }
+
+ init {
+ println("InitializerBlock")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/settings.gradle.kts
new file mode 100644
index 00000000..da027565
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/settings.gradle.kts
@@ -0,0 +1,4 @@
+apply(from = "./template.settings.gradle.kts")
+rootProject.name = "it-multimodule-versioning-0"
+include(":first")
+include(":second")
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/template.root.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/template.root.gradle.kts
new file mode 100644
index 00000000..f63c28e7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/template.root.gradle.kts
@@ -0,0 +1,23 @@
+allprojects {
+ repositories {
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenLocal()
+ mavenCentral()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") {
+ content {
+ includeGroup("org.jetbrains.kotlinx")
+ }
+ }
+ }
+}
+
+afterEvaluate {
+ logger.quiet("Gradle version: ${gradle.gradleVersion}")
+ logger.quiet("Kotlin version: ${properties["dokka_it_kotlin_version"]}")
+ properties["dokka_it_android_gradle_plugin_version"]?.let { androidVersion ->
+ logger.quiet("Android version: $androidVersion")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/template.settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/template.settings.gradle.kts
new file mode 100644
index 00000000..b7e3195e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokka/template.settings.gradle.kts
@@ -0,0 +1,38 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+pluginManagement {
+ val dokka_it_kotlin_version: String by settings
+ val dokka_it_android_gradle_plugin_version: String? by settings
+
+ plugins {
+ id("org.jetbrains.kotlin.js") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.jvm") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.android") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.multiplatform") version dokka_it_kotlin_version
+ }
+
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "org.jetbrains.dokka") {
+ useModule("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0")
+ }
+
+ if (requested.id.id == "com.android.library") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+
+ if (requested.id.id == "com.android.application") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+ }
+ }
+ repositories {
+ mavenLocal()
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..dbd0ee03
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "it-multimodule-versioning-0"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/build.gradle.kts
new file mode 100644
index 00000000..d7d47bd5
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/build.gradle.kts
@@ -0,0 +1,48 @@
+import org.jetbrains.dokka.gradle.DokkaTask
+import java.net.URL
+
+plugins {
+ kotlin("multiplatform")
+ id("org.jetbrains.dokka")
+}
+
+apply(from = "./template.root.gradle.kts")
+
+kotlin {
+ jvm()
+ linuxX64("linux")
+ macosX64("macos")
+ js(BOTH)
+ //TODO Add wasm when kx.coroutines will be supported and published into the main repo
+ sourceSets {
+ val commonMain by sourceSets.getting
+ val linuxMain by sourceSets.getting
+ val macosMain by sourceSets.getting
+ val desktopMain by sourceSets.creating {
+ dependsOn(commonMain)
+ linuxMain.dependsOn(this)
+ macosMain.dependsOn(this)
+ }
+ named("commonMain") {
+ dependencies {
+ if (properties["dokka_it_kotlin_version"] in listOf("1.4.32", "1.5.31"))
+ // otherwise for a modern versin of coroutines:
+ // Failed to resolve Kotlin library: project/build/kotlinSourceSetMetadata/commonMain/org.jetbrains.kotlinx-kotlinx-coroutines-core/org.jetbrains.kotlinx-kotlinx-coroutines-core-commonMain.klib
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
+ else
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
+ }
+ }
+ }
+}
+
+tasks.withType<DokkaTask>().configureEach {
+ dokkaSourceSets {
+ configureEach {
+ externalDocumentationLink {
+ url.set(URL("https://kotlinlang.org/api/kotlinx.coroutines/"))
+ //packageListUrl.set(URL("https://kotlinlang.org/api/kotlinx.coroutines/package-list"))
+ }
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/settings.gradle.kts
new file mode 100644
index 00000000..1d4f1681
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/settings.gradle.kts
@@ -0,0 +1,2 @@
+apply(from = "./template.settings.gradle.kts")
+rootProject.name = "it-multiplatform-0"
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/CommonMainClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/CommonMainClass.kt
new file mode 100644
index 00000000..499a4f1e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/CommonMainClass.kt
@@ -0,0 +1,8 @@
+package it.mpp0
+
+/**
+ * This class is defined in commonMain
+ */
+class CommonMainClass {
+ fun publicFunction(): String = "public"
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/ExpectedClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/ExpectedClass.kt
new file mode 100644
index 00000000..e610b09a
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/ExpectedClass.kt
@@ -0,0 +1,5 @@
+package it.mpp0
+
+expect class ExpectedClass {
+ val platform: String
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/coroutines.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/coroutines.kt
new file mode 100644
index 00000000..8eee326f
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/commonMain/kotlin/it/mpp0/coroutines.kt
@@ -0,0 +1,5 @@
+package it.mpp0
+
+import kotlinx.coroutines.CoroutineScope
+
+expect fun <T> CoroutineScope.runBlocking(block: suspend () -> T) : T
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/desktopMain/kotlin/it/mpp0/CPointerExtension.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/desktopMain/kotlin/it/mpp0/CPointerExtension.kt
new file mode 100644
index 00000000..342a749e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/desktopMain/kotlin/it/mpp0/CPointerExtension.kt
@@ -0,0 +1,11 @@
+package it.mpp0
+
+import kotlinx.cinterop.CPointed
+import kotlinx.cinterop.CPointer
+
+/**
+ * Will print the raw value
+ */
+fun CPointer<CPointed>.customExtension() {
+ println(this.rawValue)
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/desktopMain/kotlin/it/mpp0/ExpectedClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/desktopMain/kotlin/it/mpp0/ExpectedClass.kt
new file mode 100644
index 00000000..19070a96
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/desktopMain/kotlin/it/mpp0/ExpectedClass.kt
@@ -0,0 +1,5 @@
+package it.mpp0
+
+actual class ExpectedClass {
+ actual val platform: String = "linux"
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jsMain/kotlin/it/mpp0/ExpectedClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jsMain/kotlin/it/mpp0/ExpectedClass.kt
new file mode 100644
index 00000000..1e4a6d22
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jsMain/kotlin/it/mpp0/ExpectedClass.kt
@@ -0,0 +1,5 @@
+package it.mpp0
+
+actual class ExpectedClass {
+ actual val platform: String = "js"
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jsMain/kotlin/it/mpp0/runBlocking.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jsMain/kotlin/it/mpp0/runBlocking.kt
new file mode 100644
index 00000000..03b3b0ea
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jsMain/kotlin/it/mpp0/runBlocking.kt
@@ -0,0 +1,7 @@
+package it.mpp0
+
+import kotlinx.coroutines.CoroutineScope
+
+actual fun <T> CoroutineScope.runBlocking(block: suspend () -> T): T {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/ExpectedClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/ExpectedClass.kt
new file mode 100644
index 00000000..6de30de6
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/ExpectedClass.kt
@@ -0,0 +1,11 @@
+package it.mpp0
+
+actual class ExpectedClass {
+ actual val platform: String = "jvm"
+
+ /**
+ * This function can only be used by JVM consumers
+ */
+ fun jvmOnlyFunction() = Unit
+
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/JvmOnlyClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/JvmOnlyClass.kt
new file mode 100644
index 00000000..21101a89
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/JvmOnlyClass.kt
@@ -0,0 +1,13 @@
+@file:Suppress("unused")
+
+package it.mpp0
+
+/**
+ * This class can only be used by JVM consumers
+ */
+class JvmOnlyClass {
+ /**
+ * This function can only be used by JVM consumers
+ */
+ fun myJvm() = println("HI")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/runBlocking.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/runBlocking.kt
new file mode 100644
index 00000000..03b3b0ea
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/jvmMain/kotlin/it/mpp0/runBlocking.kt
@@ -0,0 +1,7 @@
+package it.mpp0
+
+import kotlinx.coroutines.CoroutineScope
+
+actual fun <T> CoroutineScope.runBlocking(block: suspend () -> T): T {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/CPointerExtension.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/CPointerExtension.kt
new file mode 100644
index 00000000..342a749e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/CPointerExtension.kt
@@ -0,0 +1,11 @@
+package it.mpp0
+
+import kotlinx.cinterop.CPointed
+import kotlinx.cinterop.CPointer
+
+/**
+ * Will print the raw value
+ */
+fun CPointer<CPointed>.customExtension() {
+ println(this.rawValue)
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/ExpectedClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/ExpectedClass.kt
new file mode 100644
index 00000000..19070a96
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/ExpectedClass.kt
@@ -0,0 +1,5 @@
+package it.mpp0
+
+actual class ExpectedClass {
+ actual val platform: String = "linux"
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/runBlocking.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/runBlocking.kt
new file mode 100644
index 00000000..b56fb80a
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/linuxMain/kotlin/it/mpp0/runBlocking.kt
@@ -0,0 +1,13 @@
+package it.mpp0
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.async
+
+actual fun <T> CoroutineScope.runBlocking(block: suspend () -> T): T {
+ TODO("Not yet implemented")
+}
+
+fun <T> CoroutineScope.customAsync(block: suspend () -> T): Deferred<T> {
+ return async { block() }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/macosMain/kotlin/it/mpp0/ExpectedClass.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/macosMain/kotlin/it/mpp0/ExpectedClass.kt
new file mode 100644
index 00000000..7a4a8f75
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/macosMain/kotlin/it/mpp0/ExpectedClass.kt
@@ -0,0 +1,5 @@
+package it.mpp0
+
+actual class ExpectedClass {
+ actual val platform: String = "macos"
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/macosMain/kotlin/it/mpp0/runBlocking.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/macosMain/kotlin/it/mpp0/runBlocking.kt
new file mode 100644
index 00000000..03b3b0ea
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/src/macosMain/kotlin/it/mpp0/runBlocking.kt
@@ -0,0 +1,7 @@
+package it.mpp0
+
+import kotlinx.coroutines.CoroutineScope
+
+actual fun <T> CoroutineScope.runBlocking(block: suspend () -> T): T {
+ TODO("Not yet implemented")
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/template.root.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/template.root.gradle.kts
new file mode 100644
index 00000000..f63c28e7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/template.root.gradle.kts
@@ -0,0 +1,23 @@
+allprojects {
+ repositories {
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenLocal()
+ mavenCentral()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") {
+ content {
+ includeGroup("org.jetbrains.kotlinx")
+ }
+ }
+ }
+}
+
+afterEvaluate {
+ logger.quiet("Gradle version: ${gradle.gradleVersion}")
+ logger.quiet("Kotlin version: ${properties["dokka_it_kotlin_version"]}")
+ properties["dokka_it_android_gradle_plugin_version"]?.let { androidVersion ->
+ logger.quiet("Android version: $androidVersion")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/template.settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/template.settings.gradle.kts
new file mode 100644
index 00000000..b7e3195e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokka/template.settings.gradle.kts
@@ -0,0 +1,38 @@
+@file:Suppress("LocalVariableName", "UnstableApiUsage")
+
+pluginManagement {
+ val dokka_it_kotlin_version: String by settings
+ val dokka_it_android_gradle_plugin_version: String? by settings
+
+ plugins {
+ id("org.jetbrains.kotlin.js") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.jvm") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.android") version dokka_it_kotlin_version
+ id("org.jetbrains.kotlin.multiplatform") version dokka_it_kotlin_version
+ }
+
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "org.jetbrains.dokka") {
+ useModule("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0")
+ }
+
+ if (requested.id.id == "com.android.library") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+
+ if (requested.id.id == "com.android.application") {
+ useModule("com.android.tools.build:gradle:$dokka_it_android_gradle_plugin_version")
+ }
+ }
+ }
+ repositories {
+ mavenLocal()
+ maven("https://cache-redirector.jetbrains.com/jcenter.bintray.com")
+ mavenCentral()
+ gradlePluginPortal()
+ google()
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
+ maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..3e4d074a
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokkatoo/settings.gradle.kts
@@ -0,0 +1,17 @@
+rootProject.name = "it-multiplatform-0"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+ repositories {
+ mavenCentral()
+ maven(providers.gradleProperty("testMavenRepo"))
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/CustomFormatExampleTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/CustomFormatExampleTest.kt
new file mode 100644
index 00000000..b2dbdb1e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/CustomFormatExampleTest.kt
@@ -0,0 +1,197 @@
+package org.jetbrains.dokka.dokkatoo.tests.examples
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooConstants.DOKKA_VERSION
+import org.jetbrains.dokka.dokkatoo.utils.*
+import io.kotest.assertions.withClue
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.file.shouldBeAFile
+import io.kotest.matchers.file.shouldHaveSameStructureAndContentAs
+import io.kotest.matchers.file.shouldHaveSameStructureAs
+import io.kotest.matchers.nulls.shouldNotBeNull
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
+import io.kotest.matchers.string.shouldNotContain
+import java.io.File
+
+class CustomFormatExampleTest : FunSpec({
+
+ val dokkaProject = initDokkaProject(
+ GradleProjectTest.projectTestTempDir.resolve("it/examples/custom-format-dokka").toFile()
+ )
+
+ val dokkatooProject = initDokkatooProject(
+ GradleProjectTest.projectTestTempDir.resolve("it/examples/custom-format-dokkatoo").toFile()
+ )
+
+ context("compare dokka and dokkatoo HTML generators") {
+ test("expect dokka can generate HTML") {
+ dokkaProject.runner
+ .addArguments(
+ "clean",
+ "dokkaHtml",
+ "--stacktrace",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Generation completed successfully"
+ }
+ }
+
+ test("expect dokkatoo can generate HTML") {
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContain "BUILD SUCCESSFUL"
+
+ dokkatooProject
+ .findFiles { it.name == "dokka-worker.log" }
+ .shouldBeSingleton { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldNotBeNull().shouldBeAFile()
+ dokkaWorkerLog.readText() shouldContain "Generation completed successfully"
+ }
+ }
+ }
+
+ context("expect dokka and dokkatoo HTML is the same") {
+ val dokkaHtmlDir = dokkaProject.projectDir.resolve("build/dokka/html")
+ val dokkatooHtmlDir = dokkatooProject.projectDir.resolve("build/dokka/html")
+
+ test("expect file trees are the same") {
+ val expectedFileTree = dokkaHtmlDir.toTreeString()
+ val actualFileTree = dokkatooHtmlDir.toTreeString()
+ println((actualFileTree to expectedFileTree).sideBySide())
+ // drop the first line from each, since the directory name is different
+ expectedFileTree.substringAfter("\n") shouldBe actualFileTree.substringAfter("\n")
+ }
+
+ test("expect directories are the same") {
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAs(dokkaHtmlDir.toFile())
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAndContentAs(dokkaHtmlDir.toFile())
+ }
+ }
+ }
+
+
+ context("Gradle caching") {
+ test("expect Dokkatoo is compatible with Gradle Build Cache") {
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContain "BUILD SUCCESSFUL"
+
+ dokkatooProject
+ .findFiles { it.name == "dokka-worker.log" }
+ .shouldBeSingleton { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldNotBeNull().shouldBeAFile()
+ dokkaWorkerLog.readText() shouldContain "Generation completed successfully"
+ }
+ }
+
+ dokkatooProject.runner
+ .addArguments(
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--build-cache",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContainAll listOf(
+ "> Task :dokkatooGeneratePublicationHtml UP-TO-DATE",
+ "BUILD SUCCESSFUL",
+ "1 actionable task: 1 up-to-date",
+ )
+ withClue("Dokka Generator should not be triggered, so check it doesn't log anything") {
+ output shouldNotContain "Generation completed successfully"
+ }
+ }
+ }
+
+ context("expect Dokkatoo is compatible with Gradle Configuration Cache") {
+ dokkatooProject.file(".gradle/configuration-cache").toFile().deleteRecursively()
+ dokkatooProject.file("build/reports/configuration-cache").toFile().deleteRecursively()
+
+ val configCacheRunner =
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--no-build-cache",
+ "--configuration-cache",
+ )
+ .forwardOutput()
+
+ test("first build should store the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry stored"
+ output shouldNotContain "problems were found storing the configuration cache"
+ }
+ }
+
+ test("second build should reuse the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry reused"
+ }
+ }
+ }
+ }
+})
+
+private fun initDokkaProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyExampleProject("custom-format-example/dokka")
+
+ buildGradleKts = buildGradleKts
+ .replace(
+ Regex("""id\("org\.jetbrains\.dokka"\) version \("[\d.]+"\)"""),
+ Regex.escapeReplacement("""id("org.jetbrains.dokka") version "$DOKKA_VERSION""""),
+ )
+ .replace(
+ "org.jetbrains.dokka:dokka-base:1.7.10",
+ "org.jetbrains.dokka:dokka-base:1.7.20",
+ )
+
+ settingsGradleKts = settingsGradleKts
+ .replace(
+ """rootProject.name = "dokka-customFormat-example"""",
+ """rootProject.name = "customFormat-example"""",
+ )
+ }
+}
+
+private fun initDokkatooProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyExampleProject("custom-format-example/dokkatoo")
+
+ buildGradleKts += """
+ |
+ |tasks.withType<org.jetbrains.dokka.dokkatoo.tasks.DokkatooGenerateTask>().configureEach {
+ | generator.dokkaSourceSets.configureEach {
+ | sourceSetScope.set(":dokkaHtml") // only necessary for testing
+ | }
+ |}
+ |
+ """.trimMargin()
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/GradleExampleTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/GradleExampleTest.kt
new file mode 100644
index 00000000..371ac938
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/GradleExampleTest.kt
@@ -0,0 +1,190 @@
+package org.jetbrains.dokka.dokkatoo.tests.examples
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooConstants.DOKKA_VERSION
+import org.jetbrains.dokka.dokkatoo.utils.*
+import org.jetbrains.dokka.dokkatoo.utils.GradleProjectTest.Companion.projectTestTempDir
+import io.kotest.assertions.withClue
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.file.shouldBeAFile
+import io.kotest.matchers.file.shouldHaveSameStructureAndContentAs
+import io.kotest.matchers.file.shouldHaveSameStructureAs
+import io.kotest.matchers.nulls.shouldNotBeNull
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
+import io.kotest.matchers.string.shouldNotContain
+import java.io.File
+import kotlin.text.Regex.Companion.escapeReplacement
+
+class GradleExampleTest : FunSpec({
+
+ val dokkaProject = initDokkaProject(
+ projectTestTempDir.resolve("it/examples/gradle-example/dokka").toFile()
+ )
+
+ val dokkatooProject = initDokkatooProject(
+ projectTestTempDir.resolve("it/examples/gradle-example/dokkatoo").toFile()
+ )
+
+ context("compare dokka and dokkatoo HTML generators") {
+ test("expect dokka can generate HTML") {
+ dokkaProject.runner
+ .addArguments(
+ "clean",
+ "dokkaHtml",
+ "--stacktrace",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Generation completed successfully"
+ }
+ }
+
+ test("expect dokkatoo can generate HTML") {
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContain "BUILD SUCCESSFUL"
+
+ dokkatooProject
+ .findFiles { it.name == "dokka-worker.log" }
+ .shouldBeSingleton { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldNotBeNull().shouldBeAFile()
+ dokkaWorkerLog.readText() shouldContain "Generation completed successfully"
+ }
+ }
+ }
+
+ context("expect dokka and dokkatoo HTML is the same") {
+ val dokkaHtmlDir = dokkaProject.projectDir.resolve("build/dokka/html")
+ val dokkatooHtmlDir = dokkatooProject.projectDir.resolve("build/dokka/html")
+
+ test("expect file trees are the same") {
+ val expectedFileTree = dokkaHtmlDir.toTreeString()
+ val actualFileTree = dokkatooHtmlDir.toTreeString()
+ println((actualFileTree to expectedFileTree).sideBySide())
+ expectedFileTree shouldBe actualFileTree
+ }
+
+ test("expect directories are the same") {
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAs(dokkaHtmlDir.toFile())
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAndContentAs(dokkaHtmlDir.toFile())
+ }
+ }
+ }
+
+
+ context("Gradle caching") {
+ test("expect Dokkatoo is compatible with Gradle Build Cache") {
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContain "BUILD SUCCESSFUL"
+
+
+ dokkatooProject
+ .findFiles { it.name == "dokka-worker.log" }
+ .shouldBeSingleton { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldNotBeNull().shouldBeAFile()
+ dokkaWorkerLog.readText() shouldContain "Generation completed successfully"
+ }
+ }
+
+ dokkatooProject.runner
+ .addArguments(
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--info",
+ "--build-cache",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContainAll listOf(
+ "> Task :dokkatooGeneratePublicationHtml UP-TO-DATE",
+ "BUILD SUCCESSFUL",
+ "1 actionable task: 1 up-to-date",
+ )
+ withClue("Dokka Generator should not be triggered, so check it doesn't log anything") {
+ output shouldNotContain "Generation completed successfully"
+ }
+ }
+ }
+
+ context("expect Dokkatoo is compatible with Gradle Configuration Cache") {
+ dokkatooProject.file(".gradle/configuration-cache").toFile().deleteRecursively()
+ dokkatooProject.file("build/reports/configuration-cache").toFile().deleteRecursively()
+
+ val configCacheRunner =
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--no-build-cache",
+ "--configuration-cache",
+ )
+ .forwardOutput()
+
+ test("first build should store the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry stored"
+ output shouldNotContain "problems were found storing the configuration cache"
+ }
+ }
+
+ test("second build should reuse the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry reused"
+ }
+ }
+ }
+ }
+})
+
+
+private fun initDokkaProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyExampleProject("gradle-example/dokka")
+
+ buildGradleKts = buildGradleKts
+ .replace(
+ Regex("""id\("org\.jetbrains\.dokka"\) version \("[\d.]+"\)"""),
+ escapeReplacement("""id("org.jetbrains.dokka") version "$DOKKA_VERSION""""),
+ )
+ }
+}
+
+private fun initDokkatooProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyExampleProject("gradle-example/dokkatoo")
+
+ buildGradleKts += """
+ |
+ |tasks.withType<org.jetbrains.dokka.dokkatoo.tasks.DokkatooGenerateTask>().configureEach {
+ | generator.dokkaSourceSets.configureEach {
+ | sourceSetScope.set(":dokkaHtml") // only necessary for testing
+ | }
+ |}
+ |
+ """.trimMargin()
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/KotlinMultiplatformExampleTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/KotlinMultiplatformExampleTest.kt
new file mode 100644
index 00000000..4a9efaac
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/KotlinMultiplatformExampleTest.kt
@@ -0,0 +1,226 @@
+package org.jetbrains.dokka.dokkatoo.tests.examples
+
+import org.jetbrains.dokka.dokkatoo.utils.*
+import org.jetbrains.dokka.dokkatoo.utils.GradleProjectTest.Companion.projectTestTempDir
+import io.kotest.assertions.withClue
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.file.shouldBeAFile
+import io.kotest.matchers.file.shouldHaveSameStructureAndContentAs
+import io.kotest.matchers.file.shouldHaveSameStructureAs
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
+import io.kotest.matchers.string.shouldNotContain
+import java.io.File
+
+class KotlinMultiplatformExampleTest : FunSpec({
+
+ val dokkaProject = initDokkaProject(
+ projectTestTempDir.resolve("it/examples/multiplatform-example/dokka").toFile()
+ )
+
+ val dokkatooProject = initDokkatooProject(
+ projectTestTempDir.resolve("it/examples/multiplatform-example/dokkatoo").toFile()
+ )
+
+ context("compare dokka and dokkatoo HTML generators") {
+ test("expect dokka can generate HTML") {
+ dokkaProject.runner
+ .addArguments(
+ "clean",
+ "dokkaHtml",
+ "--stacktrace",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Generation completed successfully"
+ }
+ }
+
+ context("when Dokkatoo generates HTML") {
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ test("expect build is successful") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+
+ test("expect all dokka workers are successful") {
+ dokkatooProject
+ .findFiles { it.name == "dokka-worker.log" }
+ .shouldBeSingleton { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldBeAFile()
+ dokkaWorkerLog.readText().shouldNotContainAnyOf(
+ "[ERROR]",
+ "[WARN]",
+ )
+ }
+ }
+ }
+ }
+
+ context("expect dokka and dokkatoo HTML is the same") {
+ val dokkaHtmlDir =
+ dokkaProject.projectDir.resolve("build/dokka/html")
+ val dokkatooHtmlDir = dokkatooProject.projectDir.resolve("build/dokka/html")
+
+ test("expect file trees are the same") {
+ val expectedFileTree = dokkaHtmlDir.toTreeString()
+ val actualFileTree = dokkatooHtmlDir.toTreeString()
+ println((actualFileTree to expectedFileTree).sideBySide())
+ expectedFileTree shouldBe actualFileTree
+ }
+
+ test("expect directories are the same") {
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAs(dokkaHtmlDir.toFile())
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAndContentAs(dokkaHtmlDir.toFile())
+ }
+ }
+ }
+
+
+ context("Gradle caching") {
+
+ context("expect Dokkatoo is compatible with Gradle Build Cache") {
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ )
+ .forwardOutput()
+ .build {
+ test("expect build is successful") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+
+ test("expect all dokka workers are successful") {
+ dokkatooProject
+ .findFiles { it.name == "dokka-worker.log" }
+ .shouldBeSingleton { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldBeAFile()
+ dokkaWorkerLog.readText().shouldNotContainAnyOf(
+ "[ERROR]",
+ "[WARN]",
+ )
+ }
+ }
+ }
+
+ test("expect tasks are UP-TO-DATE") {
+ dokkatooProject.runner
+ .addArguments(
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--info",
+ "--build-cache",
+ )
+ .forwardOutput()
+ .build {
+
+ output shouldContainAll listOf(
+ "> Task :dokkatooGeneratePublicationHtml UP-TO-DATE",
+ "BUILD SUCCESSFUL",
+ "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"
+ }
+ }
+ }
+ }
+
+ context("expect Dokkatoo is compatible with Gradle Configuration Cache") {
+ dokkatooProject.file(".gradle/configuration-cache").toFile().deleteRecursively()
+ dokkatooProject.file("build/reports/configuration-cache").toFile().deleteRecursively()
+
+ val configCacheRunner =
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--no-build-cache",
+ "--configuration-cache",
+ )
+ .forwardOutput()
+
+ test("first build should store the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry stored"
+ output shouldNotContain "problems were found storing the configuration cache"
+ }
+ }
+
+ test("second build should reuse the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry reused"
+ }
+ }
+ }
+ }
+})
+
+
+private fun initDokkaProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyExampleProject("multiplatform-example/dokka")
+
+ settingsGradleKts = settingsGradleKts
+ .replace(
+ """pluginManagement {""",
+ """
+ |
+ |pluginManagement {
+ | repositories {
+ | gradlePluginPortal()
+ | mavenCentral()
+ | mavenLocal()
+ | }
+ |
+ """.trimMargin()
+ ) + """
+ |
+ |dependencyResolutionManagement {
+ | repositories {
+ | mavenCentral()
+ | mavenLocal()
+ | }
+ |}
+ |
+ """.trimMargin()
+
+ buildGradleKts += """
+ |
+ |val hackDokkaHtmlDir by tasks.registering(Sync::class) {
+ | // sync directories so the dirs in both dokka and dokkatoo are the same
+ | from(layout.buildDirectory.dir("dokka/htmlMultiModule"))
+ | into(layout.buildDirectory.dir("dokka/html"))
+ |}
+ |
+ |tasks.matching { "dokka" in it.name.toLowerCase() && it.name != hackDokkaHtmlDir.name }.configureEach {
+ | finalizedBy(hackDokkaHtmlDir)
+ |}
+ |
+ """.trimMargin()
+ }
+}
+
+private fun initDokkatooProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyExampleProject("multiplatform-example/dokkatoo")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/MultimoduleExampleTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/MultimoduleExampleTest.kt
new file mode 100644
index 00000000..8e2215c2
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testExamples/kotlin/MultimoduleExampleTest.kt
@@ -0,0 +1,244 @@
+package org.jetbrains.dokka.dokkatoo.tests.examples
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooConstants.DOKKA_VERSION
+import org.jetbrains.dokka.dokkatoo.utils.*
+import org.jetbrains.dokka.dokkatoo.utils.GradleProjectTest.Companion.projectTestTempDir
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.inspectors.shouldForAll
+import io.kotest.matchers.collections.shouldHaveSize
+import io.kotest.matchers.file.shouldBeAFile
+import io.kotest.matchers.file.shouldHaveSameStructureAndContentAs
+import io.kotest.matchers.file.shouldHaveSameStructureAs
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
+import io.kotest.matchers.string.shouldNotContain
+import java.io.File
+import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE
+
+class MultimoduleExampleTest : FunSpec({
+
+ val dokkaProject = initDokkaProject(
+ projectTestTempDir.resolve("it/examples/multimodule-example/dokka").toFile()
+ )
+
+ val dokkatooProject = initDokkatooProject(
+ projectTestTempDir.resolve("it/examples/multimodule-example/dokkatoo").toFile()
+ )
+
+ context("compare dokka and dokkatoo HTML generators") {
+ test("expect dokka can generate HTML") {
+ dokkaProject.runner
+ .addArguments(
+ "clean",
+ "dokkaHtmlMultiModule",
+ "--stacktrace",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Generation completed successfully"
+ }
+ }
+
+ context("when Dokkatoo generates HTML") {
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":parentProject:dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--info",
+ )
+ .forwardOutput()
+ .build {
+ test("expect build is successful") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+
+ test("expect all dokka workers are successful") {
+ dokkatooProject
+ .findFiles { it.name == "dokka-worker.log" }
+ .shouldForAll { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldBeAFile()
+ dokkaWorkerLog.readText().shouldNotContainAnyOf(
+ "[ERROR]",
+ "[WARN]",
+ )
+ }
+ }
+ }
+ }
+
+ context("expect dokka and dokkatoo HTML is the same") {
+ val dokkaHtmlDir =
+ dokkaProject.projectDir.resolve("parentProject/build/dokka/html")
+ val dokkatooHtmlDir = dokkatooProject.projectDir.resolve("parentProject/build/dokka/html")
+
+ test("expect file trees are the same") {
+ val expectedFileTree = dokkaHtmlDir.toTreeString()
+ val actualFileTree = dokkatooHtmlDir.toTreeString()
+ println((actualFileTree to expectedFileTree).sideBySide())
+ expectedFileTree shouldBe actualFileTree
+ }
+
+ test("expect directories are the same") {
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAs(dokkaHtmlDir.toFile())
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAndContentAs(dokkaHtmlDir.toFile())
+ }
+ }
+ }
+
+
+ context("Gradle caching") {
+
+ context("expect Dokkatoo is compatible with Gradle Build Cache") {
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":parentProject:dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ )
+ .forwardOutput()
+ .build {
+ test("expect build is successful") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+
+ test("expect all dokka workers are successful") {
+ dokkatooProject
+ .findFiles { it.name == "dokka-worker.log" }
+ .shouldForAll { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldBeAFile()
+ dokkaWorkerLog.readText().shouldNotContainAnyOf(
+ "[ERROR]",
+ "[WARN]",
+ )
+ }
+ }
+ }
+
+ dokkatooProject.runner
+ .addArguments(
+ ":parentProject:dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--info",
+ "--build-cache",
+ )
+ .forwardOutput()
+ .build {
+ test("expect build is successful") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+
+ test("expect all tasks are UP-TO-DATE") {
+ val nonLoggingTasks =
+ tasks.filter { it.name != "logLinkDokkatooGeneratePublicationHtml" }
+ nonLoggingTasks.shouldForAll {
+ it shouldHaveOutcome UP_TO_DATE
+ }
+ tasks.shouldHaveSize(6)
+ }
+
+ test("expect Dokka Generator is not triggered") {
+ // Dokka Generator shouldn't run, so check it doesn't log anything
+ output shouldNotContain "Generation completed successfully"
+ }
+ }
+ }
+
+
+ context("expect Dokkatoo is compatible with Gradle Configuration Cache") {
+ dokkatooProject.file(".gradle/configuration-cache").toFile().deleteRecursively()
+ dokkatooProject.file("build/reports/configuration-cache").toFile().deleteRecursively()
+
+ val configCacheRunner =
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ ":parentProject:dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--no-build-cache",
+ "--configuration-cache",
+ )
+ .forwardOutput()
+
+ test("first build should store the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry stored"
+ output shouldNotContain "problems were found storing the configuration cache"
+ }
+ }
+
+ test("second build should reuse the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry reused"
+ }
+ }
+ }
+ }
+})
+
+
+private fun initDokkaProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyExampleProject("multimodule-example/dokka")
+
+ gradleProperties = gradleProperties.lines().joinToString("\n") { line ->
+ when {
+ line.startsWith("dokkaVersion=") -> "dokkaVersion=$DOKKA_VERSION"
+ else -> line
+ }
+ }
+
+ settingsGradleKts = settingsGradleKts
+ .replace(
+ """pluginManagement {""",
+ """
+ |
+ |pluginManagement {
+ | repositories {
+ | mavenCentral()
+ | gradlePluginPortal()
+ | }
+ |
+ """.trimMargin()
+ ) + """
+ |
+ |dependencyResolutionManagement {
+ | repositories {
+ | mavenCentral()
+ | }
+ |}
+ |
+ """.trimMargin()
+
+ dir("parentProject") {
+
+ buildGradleKts += """
+ |
+ |val hackDokkaHtmlDir by tasks.registering(Sync::class) {
+ | // sync directories so the dirs in both dokka and dokkatoo are the same
+ | from(layout.buildDirectory.dir("dokka/htmlMultiModule"))
+ | into(layout.buildDirectory.dir("dokka/html"))
+ |}
+ |
+ |tasks.matching { it.name.contains("dokka", ignoreCase = true) && it.name != hackDokkaHtmlDir.name }.configureEach {
+ | finalizedBy(hackDokkaHtmlDir)
+ |}
+ |
+ """.trimMargin()
+ }
+ }
+}
+
+private fun initDokkatooProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyExampleProject("multimodule-example/dokkatoo")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testFixtures/kotlin/templateProjectUtils.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testFixtures/kotlin/templateProjectUtils.kt
new file mode 100644
index 00000000..93a52564
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testFixtures/kotlin/templateProjectUtils.kt
@@ -0,0 +1,17 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+
+fun GradleProjectTest.copyExampleProject(path: String) {
+ GradleProjectTest.exampleProjectsDir
+ .resolve(path)
+ .toFile()
+ .copyRecursively(projectDir.toFile(), overwrite = true) { _, _ -> OnErrorAction.SKIP }
+}
+
+
+fun GradleProjectTest.copyIntegrationTestProject(path: String) {
+ GradleProjectTest.integrationTestProjectsDir
+ .resolve(path)
+ .toFile()
+ .copyRecursively(projectDir.toFile(), overwrite = true) { _, _ -> OnErrorAction.SKIP }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/kotlin/AndroidProjectIntegrationTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/kotlin/AndroidProjectIntegrationTest.kt
new file mode 100644
index 00000000..9389a798
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/kotlin/AndroidProjectIntegrationTest.kt
@@ -0,0 +1,166 @@
+package org.jetbrains.dokka.dokkatoo.tests.integration
+
+import org.jetbrains.dokka.dokkatoo.utils.*
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.file.shouldBeAFile
+import io.kotest.matchers.file.shouldHaveSameStructureAndContentAs
+import io.kotest.matchers.file.shouldHaveSameStructureAs
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
+import io.kotest.matchers.string.shouldNotContain
+import java.io.File
+import kotlin.io.path.deleteIfExists
+
+/**
+ * Integration test for the `it-android-0` project in Dokka
+ *
+ * Runs Dokka & Dokkatoo, and compares the resulting HTML site.
+ */
+class AndroidProjectIntegrationTest : FunSpec({
+
+ val tempDir = GradleProjectTest.projectTestTempDir.resolve("it/it-android-0").toFile()
+
+ val dokkatooProject = initDokkatooProject(tempDir.resolve("dokkatoo"))
+ val dokkaProject = initDokkaProject(tempDir.resolve("dokka"))
+
+ context("when generating HTML") {
+ context("with Dokka") {
+ dokkaProject.runner
+ .addArguments(
+ "clean",
+ "dokkaHtml",
+ "--stacktrace",
+ )
+ .forwardOutput()
+ .build {
+ test("expect project builds successfully") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+ }
+ }
+
+ context("with Dokkatoo") {
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ "dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ )
+ .forwardOutput()
+ .build {
+ test("expect project builds successfully") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+
+ test("expect all dokka workers are successful") {
+ dokkatooProject
+ .findFiles { it.name == "dokka-worker.log" }
+ .shouldBeSingleton { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldBeAFile()
+ dokkaWorkerLog.readText().shouldNotContainAnyOf(
+ "[ERROR]",
+ "[WARN]",
+ )
+ }
+ }
+ }
+ }
+
+ test("expect the same HTML is generated") {
+
+ val dokkaHtmlDir = dokkaProject.projectDir.resolve("build/dokka/html")
+ val dokkatooHtmlDir = dokkatooProject.projectDir.resolve("build/dokka/html")
+
+ val expectedFileTree = dokkaHtmlDir.toTreeString()
+ val actualFileTree = dokkatooHtmlDir.toTreeString()
+ println((actualFileTree to expectedFileTree).sideBySide())
+ expectedFileTree shouldBe actualFileTree
+
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAs(dokkaHtmlDir.toFile())
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAndContentAs(dokkaHtmlDir.toFile())
+ }
+
+ test("Dokkatoo tasks should be cacheable") {
+ dokkatooProject.runner
+ .addArguments(
+ "dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--build-cache",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContainAll listOf(
+ "Task :dokkatooGeneratePublicationHtml UP-TO-DATE",
+ )
+ }
+ }
+
+ context("expect Dokkatoo is compatible with Gradle Configuration Cache") {
+ dokkatooProject.file(".gradle/configuration-cache").toFile().deleteRecursively()
+ dokkatooProject.file("build/reports/configuration-cache").toFile().deleteRecursively()
+
+ val configCacheRunner =
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ "dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--no-build-cache",
+ "--configuration-cache",
+ )
+ .forwardOutput()
+
+ test("first build should store the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry stored"
+ output shouldNotContain "problems were found storing the configuration cache"
+ }
+ }
+
+ test("second build should reuse the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry reused"
+ }
+ }
+ }
+ }
+})
+
+private fun initDokkaProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyIntegrationTestProject("it-android-0/dokka")
+
+ gradleProperties = gradleProperties
+ .replace(
+ "dokka_it_android_gradle_plugin_version=4.2.2",
+ "dokka_it_android_gradle_plugin_version=8.0.2",
+ )
+
+ file("src/main/AndroidManifest.xml").deleteIfExists()
+
+ buildGradleKts += """
+
+ android {
+ namespace = "org.jetbrains.dokka.it.android"
+ }
+
+ java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+ }
+ """.trimIndent()
+ }
+}
+
+private fun initDokkatooProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyIntegrationTestProject("it-android-0/dokkatoo")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/kotlin/BasicProjectIntegrationTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/kotlin/BasicProjectIntegrationTest.kt
new file mode 100644
index 00000000..265e7c18
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/kotlin/BasicProjectIntegrationTest.kt
@@ -0,0 +1,153 @@
+package org.jetbrains.dokka.dokkatoo.tests.integration
+
+import org.jetbrains.dokka.dokkatoo.utils.*
+import org.jetbrains.dokka.dokkatoo.utils.GradleProjectTest.Companion.projectTestTempDir
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.file.shouldBeAFile
+import io.kotest.matchers.file.shouldHaveSameStructureAndContentAs
+import io.kotest.matchers.file.shouldHaveSameStructureAs
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
+import io.kotest.matchers.string.shouldNotContain
+import java.io.File
+
+/**
+ * Integration test for the `it-basic` project in Dokka
+ *
+ * Runs Dokka & Dokkatoo, and compares the resulting HTML site.
+ */
+class BasicProjectIntegrationTest : FunSpec({
+
+ val tempDir = projectTestTempDir.resolve("it/it-basic").toFile()
+
+ val dokkatooProject = initDokkatooProject(tempDir.resolve("dokkatoo"))
+ val dokkaProject = initDokkaProject(tempDir.resolve("dokka"))
+
+ context("when generating HTML") {
+ dokkaProject.runner
+ .addArguments(
+ "clean",
+ "dokkaHtml",
+ "--stacktrace",
+ )
+ .forwardOutput()
+ .build {
+ context("with Dokka") {
+ test("expect project builds successfully") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+ }
+ }
+
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ "dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ )
+ .forwardOutput()
+ .build {
+ context("with Dokkatoo") {
+ test("expect project builds successfully") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+
+ test("expect all dokka workers are successful") {
+ dokkatooProject
+ .findFiles { it.name == "dokka-worker.log" }
+ .shouldBeSingleton { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldBeAFile()
+ dokkaWorkerLog.readText().shouldNotContainAnyOf(
+ "[ERROR]",
+ "[WARN]",
+ )
+ }
+ }
+ }
+ }
+
+ test("expect the same HTML is generated") {
+
+ val dokkaHtmlDir = dokkaProject.projectDir.resolve("build/dokka/html")
+ val dokkatooHtmlDir = dokkatooProject.projectDir.resolve("build/dokka/html")
+
+ val expectedFileTree = dokkaHtmlDir.toTreeString()
+ val actualFileTree = dokkatooHtmlDir.toTreeString()
+ println((actualFileTree to expectedFileTree).sideBySide())
+ expectedFileTree shouldBe actualFileTree
+
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAs(dokkaHtmlDir.toFile())
+ dokkatooHtmlDir.toFile().shouldHaveSameStructureAndContentAs(dokkaHtmlDir.toFile())
+ }
+
+ test("Dokkatoo tasks should be cacheable") {
+ dokkatooProject.runner
+ .addArguments(
+ "dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--build-cache",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContainAll listOf(
+ "Task :dokkatooGeneratePublicationHtml UP-TO-DATE",
+ )
+ }
+ }
+
+ context("expect Dokkatoo is compatible with Gradle Configuration Cache") {
+ dokkatooProject.file(".gradle/configuration-cache").toFile().deleteRecursively()
+ dokkatooProject.file("build/reports/configuration-cache").toFile().deleteRecursively()
+
+ val configCacheRunner =
+ dokkatooProject.runner
+ .addArguments(
+ "clean",
+ "dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--no-build-cache",
+ "--configuration-cache",
+ )
+ .forwardOutput()
+
+ test("first build should store the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry stored"
+ output shouldNotContain "problems were found storing the configuration cache"
+ }
+ }
+
+ test("second build should reuse the configuration cache") {
+ configCacheRunner.build {
+ output shouldContain "BUILD SUCCESSFUL"
+ output shouldContain "Configuration cache entry reused"
+ }
+ }
+ }
+ }
+})
+
+
+private fun initDokkaProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyIntegrationTestProject("it-basic/dokka")
+
+ buildGradleKts = buildGradleKts
+ .replace(
+ // no idea why this needs to be changed
+ """file("../customResources/""",
+ """file("./customResources/""",
+ )
+ }
+}
+
+private fun initDokkatooProject(
+ destinationDir: File,
+): GradleProjectTest {
+ return GradleProjectTest(destinationDir.toPath()).apply {
+ copyIntegrationTestProject("it-basic/dokkatoo")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/childProjectA.json b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/childProjectA.json
new file mode 100644
index 00000000..21b8498f
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/childProjectA.json
@@ -0,0 +1,83 @@
+{
+ "moduleName": "childProjectA",
+ "moduleVersion": "unspecified",
+ "outputDir": ".../dokka-multimodule-example/parentProject/childProjectA/build/dokka/htmlPartial",
+ "cacheRoot": null,
+ "offlineMode": false,
+ "sourceSets": [
+ {
+ "displayName": "jvm",
+ "sourceSetID": {
+ "scopeId": ":parentProject:childProjectA:dokkaHtmlPartial",
+ "sourceSetName": "main"
+ },
+ "classpath": [
+ ".../kotlin-stdlib-1.7.20.jar",
+ ".../kotlin-stdlib-common-1.7.20.jar",
+ ".../annotations-13.0.jar"
+ ],
+ "sourceRoots": [
+ ".../dokka-multimodule-example/parentProject/childProjectA/src/main/kotlin"
+ ],
+ "dependentSourceSets": [],
+ "samples": [],
+ "includes": [
+ ".../dokka-multimodule-example/parentProject/childProjectA/Module.md"
+ ],
+ "includeNonPublic": false,
+ "reportUndocumented": false,
+ "skipEmptyPackages": true,
+ "skipDeprecated": false,
+ "jdkVersion": 8,
+ "sourceLinks": [],
+ "perPackageOptions": [],
+ "externalDocumentationLinks": [
+ {
+ "url": "https://kotlinlang.org/api/latest/jvm/stdlib/",
+ "packageListUrl": "https://kotlinlang.org/api/latest/jvm/stdlib/package-list"
+ },
+ {
+ "url": "https://docs.oracle.com/javase/8/docs/api/",
+ "packageListUrl": "https://docs.oracle.com/javase/8/docs/api/package-list"
+ }
+ ],
+ "languageVersion": null,
+ "apiVersion": null,
+ "noStdlibLink": false,
+ "noJdkLink": false,
+ "suppressedFiles": [],
+ "analysisPlatform": "jvm",
+ "documentedVisibilities": [
+ "PUBLIC"
+ ]
+ }
+ ],
+ "pluginsClasspath": [
+ ".../dokka-analysis-for-integration-tests-SNAPSHOT.jar",
+ ".../dokka-base-for-integration-tests-SNAPSHOT.jar",
+ ".../kotlin-analysis-intellij-for-integration-tests-SNAPSHOT.jar",
+ ".../kotlin-analysis-compiler-for-integration-tests-SNAPSHOT.jar",
+ ".../kotlinx-html-jvm-0.7.5.jar",
+ ".../kotlinx-coroutines-core-jvm-1.6.3.jar",
+ ".../kotlin-stdlib-jdk8-1.7.20.jar",
+ ".../jackson-databind-2.12.7.jar",
+ ".../jackson-annotations-2.12.7.jar",
+ ".../jackson-core-2.12.7.jar",
+ ".../jackson-module-kotlin-2.12.7.jar",
+ ".../kotlin-reflect-1.7.20.jar",
+ ".../kotlin-stdlib-jdk7-1.7.20.jar",
+ ".../kotlin-stdlib-1.7.20.jar",
+ ".../jsoup-1.14.3.jar",
+ ".../freemarker-2.3.31.jar",
+ ".../kotlin-stdlib-common-1.7.20.jar",
+ ".../annotations-13.0.jar"
+ ],
+ "pluginsConfiguration": [],
+ "modules": [],
+ "failOnWarning": false,
+ "delayTemplateSubstitution": true,
+ "suppressObviousFunctions": true,
+ "includes": [],
+ "suppressInheritedMembers": false,
+ "finalizeCoroutines": true
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/childProjectB.json b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/childProjectB.json
new file mode 100644
index 00000000..98d218d3
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/childProjectB.json
@@ -0,0 +1,83 @@
+{
+ "moduleName": "childProjectB",
+ "moduleVersion": "unspecified",
+ "outputDir": ".../dokka-multimodule-example/parentProject/childProjectB/build/dokka/htmlPartial",
+ "cacheRoot": null,
+ "offlineMode": false,
+ "sourceSets": [
+ {
+ "displayName": "jvm",
+ "sourceSetID": {
+ "scopeId": ":parentProject:childProjectB:dokkaHtmlPartial",
+ "sourceSetName": "main"
+ },
+ "classpath": [
+ ".../kotlin-stdlib-1.7.20.jar",
+ ".../kotlin-stdlib-common-1.7.20.jar",
+ ".../annotations-13.0.jar"
+ ],
+ "sourceRoots": [
+ ".../dokka-multimodule-example/parentProject/childProjectB/src/main/kotlin"
+ ],
+ "dependentSourceSets": [],
+ "samples": [],
+ "includes": [
+ ".../dokka-multimodule-example/parentProject/childProjectB/Module.md"
+ ],
+ "includeNonPublic": false,
+ "reportUndocumented": false,
+ "skipEmptyPackages": true,
+ "skipDeprecated": false,
+ "jdkVersion": 8,
+ "sourceLinks": [],
+ "perPackageOptions": [],
+ "externalDocumentationLinks": [
+ {
+ "url": "https://kotlinlang.org/api/latest/jvm/stdlib/",
+ "packageListUrl": "https://kotlinlang.org/api/latest/jvm/stdlib/package-list"
+ },
+ {
+ "url": "https://docs.oracle.com/javase/8/docs/api/",
+ "packageListUrl": "https://docs.oracle.com/javase/8/docs/api/package-list"
+ }
+ ],
+ "languageVersion": null,
+ "apiVersion": null,
+ "noStdlibLink": false,
+ "noJdkLink": false,
+ "suppressedFiles": [],
+ "analysisPlatform": "jvm",
+ "documentedVisibilities": [
+ "PUBLIC"
+ ]
+ }
+ ],
+ "pluginsClasspath": [
+ ".../dokka-analysis-for-integration-tests-SNAPSHOT.jar",
+ ".../dokka-base-for-integration-tests-SNAPSHOT.jar",
+ ".../kotlin-analysis-intellij-for-integration-tests-SNAPSHOT.jar",
+ ".../kotlin-analysis-compiler-for-integration-tests-SNAPSHOT.jar",
+ ".../kotlinx-html-jvm-0.7.5.jar",
+ ".../kotlinx-coroutines-core-jvm-1.6.3.jar",
+ ".../kotlin-stdlib-jdk8-1.7.20.jar",
+ ".../jackson-databind-2.12.7.jar",
+ ".../jackson-annotations-2.12.7.jar",
+ ".../jackson-core-2.12.7.jar",
+ ".../jackson-module-kotlin-2.12.7.jar",
+ ".../kotlin-reflect-1.7.20.jar",
+ ".../kotlin-stdlib-jdk7-1.7.20.jar",
+ ".../kotlin-stdlib-1.7.20.jar",
+ ".../jsoup-1.14.3.jar",
+ ".../freemarker-2.3.31.jar",
+ ".../kotlin-stdlib-common-1.7.20.jar",
+ ".../annotations-13.0.jar"
+ ],
+ "pluginsConfiguration": [],
+ "modules": [],
+ "failOnWarning": false,
+ "delayTemplateSubstitution": true,
+ "suppressObviousFunctions": true,
+ "includes": [],
+ "suppressInheritedMembers": false,
+ "finalizeCoroutines": true
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/parentProject.json b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/parentProject.json
new file mode 100644
index 00000000..fb300e37
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/parentProject.json
@@ -0,0 +1,55 @@
+{
+ "moduleName": "parentProject",
+ "moduleVersion": null,
+ "outputDir": ".../dokka-multimodule-example/parentProject/build/dokka/htmlMultiModule",
+ "cacheRoot": null,
+ "offlineMode": false,
+ "sourceSets": [],
+ "pluginsClasspath": [
+ ".../dokka-analysis-for-integration-tests-SNAPSHOT.jar",
+ ".../all-modules-page-plugin-for-integration-tests-SNAPSHOT.jar",
+ ".../templating-plugin-for-integration-tests-SNAPSHOT.jar",
+ ".../dokka-base-for-integration-tests-SNAPSHOT.jar",
+ ".../kotlin-analysis-intellij-for-integration-tests-SNAPSHOT.jar",
+ ".../kotlin-analysis-compiler-for-integration-tests-SNAPSHOT.jar",
+ "../kotlinx-html-jvm-0.7.5.jar",
+ "../kotlinx-coroutines-core-jvm-1.6.3.jar",
+ "../kotlin-stdlib-jdk8-1.7.20.jar",
+ "../jackson-databind-2.12.7.jar",
+ "../jackson-annotations-2.12.7.jar",
+ "../jackson-core-2.12.7.jar",
+ "../jackson-module-kotlin-2.12.7.jar",
+ "../kotlin-reflect-1.7.20.jar",
+ "../kotlin-stdlib-jdk7-1.7.20.jar",
+ "../kotlin-stdlib-1.7.20.jar",
+ "../jsoup-1.14.3.jar",
+ "../freemarker-2.3.31.jar",
+ "../kotlin-stdlib-common-1.7.20.jar",
+ "../annotations-13.0.jar"
+ ],
+ "pluginsConfiguration": [],
+ "modules": [
+ {
+ "name": "childProjectA",
+ "relativePathToOutputDirectory": "childProjectA",
+ "includes": [
+ ".../dokka-multimodule-example/parentProject/childProjectA/Module.md"
+ ],
+ "sourceOutputDirectory": ".../dokka-multimodule-example/parentProject/childProjectA/build/dokka/htmlPartial"
+ },
+ {
+ "name": "childProjectB",
+ "relativePathToOutputDirectory": "childProjectB",
+ "includes": [
+ ".../dokka-multimodule-example/parentProject/childProjectB/Module.md"
+ ],
+ "sourceOutputDirectory": ".../dokka-multimodule-example/parentProject/childProjectB/build/dokka/htmlPartial"
+ }
+ ],
+ "failOnWarning": false,
+ "delayTemplateSubstitution": false,
+ "suppressObviousFunctions": true,
+ "includes": [],
+ "suppressInheritedMembers": false,
+ "finalizeCoroutines": true
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/readme.md b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/readme.md
new file mode 100644
index 00000000..cffaf26b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin-integration-tests/src/testIntegration/resources/it/example/dokka-multi-module/readme.md
@@ -0,0 +1,5 @@
+This directory contains example JSON that the current Dokka plugin generates
+for the dokka-multi-module example.
+
+It is committed here for development so that I can manually compare and contrast.
+In time, these files can be removed.
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/api/dokkatoo-plugin.api b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/api/dokkatoo-plugin.api
new file mode 100644
index 00000000..d767d2ec
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/api/dokkatoo-plugin.api
@@ -0,0 +1,397 @@
+public abstract class dev/adamko/dokkatoo/DokkatooBasePlugin : org/gradle/api/Plugin {
+ public static final field Companion Ldev/adamko/dokkatoo/DokkatooBasePlugin$Companion;
+ public static final field EXTENSION_NAME Ljava/lang/String;
+ public static final field TASK_GROUP Ljava/lang/String;
+ public synthetic fun apply (Ljava/lang/Object;)V
+ public fun apply (Lorg/gradle/api/Project;)V
+}
+
+public final class dev/adamko/dokkatoo/DokkatooBasePlugin$Companion {
+ public final fun getDependencyContainerNames ()Ldev/adamko/dokkatoo/DokkatooBasePlugin$DependencyContainerNames;
+ public final fun getTaskNames ()Ldev/adamko/dokkatoo/DokkatooBasePlugin$TaskNames;
+}
+
+public final class dev/adamko/dokkatoo/DokkatooBasePlugin$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action {
+ public fun <init> (Lkotlin/jvm/functions/Function1;)V
+ public final synthetic fun execute (Ljava/lang/Object;)V
+}
+
+public abstract class dev/adamko/dokkatoo/DokkatooExtension : java/io/Serializable, org/gradle/api/plugins/ExtensionAware {
+ public abstract fun getDokkatooCacheDirectory ()Lorg/gradle/api/file/DirectoryProperty;
+ public abstract fun getDokkatooConfigurationsDirectory ()Lorg/gradle/api/file/DirectoryProperty;
+ public abstract fun getDokkatooModuleDirectory ()Lorg/gradle/api/file/DirectoryProperty;
+ public abstract fun getDokkatooPublicationDirectory ()Lorg/gradle/api/file/DirectoryProperty;
+ public final fun getDokkatooPublications ()Lorg/gradle/api/NamedDomainObjectContainer;
+ public final fun getDokkatooSourceSets ()Lorg/gradle/api/NamedDomainObjectContainer;
+ public abstract fun getModuleName ()Lorg/gradle/api/provider/Property;
+ public abstract fun getModulePath ()Lorg/gradle/api/provider/Property;
+ public abstract fun getModuleVersion ()Lorg/gradle/api/provider/Property;
+ public final fun getPluginsConfiguration ()Lorg/gradle/api/ExtensiblePolymorphicDomainObjectContainer;
+ public abstract fun getSourceSetScopeDefault ()Lorg/gradle/api/provider/Property;
+ public final fun getVersions ()Ldev/adamko/dokkatoo/DokkatooExtension$Versions;
+}
+
+public abstract interface class dev/adamko/dokkatoo/DokkatooExtension$Versions : org/gradle/api/plugins/ExtensionAware {
+ public static final field Companion Ldev/adamko/dokkatoo/DokkatooExtension$Versions$Companion;
+ public abstract fun getFreemarker ()Lorg/gradle/api/provider/Property;
+ public abstract fun getJetbrainsDokka ()Lorg/gradle/api/provider/Property;
+ public abstract fun getJetbrainsMarkdown ()Lorg/gradle/api/provider/Property;
+ public abstract fun getKotlinxCoroutines ()Lorg/gradle/api/provider/Property;
+ public abstract fun getKotlinxHtml ()Lorg/gradle/api/provider/Property;
+}
+
+public final class dev/adamko/dokkatoo/DokkatooExtension$Versions$Companion {
+}
+
+public abstract class dev/adamko/dokkatoo/DokkatooPlugin : org/gradle/api/Plugin {
+ public synthetic fun apply (Ljava/lang/Object;)V
+ public fun apply (Lorg/gradle/api/Project;)V
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/DokkaPublication : java/io/Serializable, org/gradle/api/Named, org/gradle/api/plugins/ExtensionAware {
+ public abstract fun getCacheRoot ()Lorg/gradle/api/file/DirectoryProperty;
+ public abstract fun getDelayTemplateSubstitution ()Lorg/gradle/api/provider/Property;
+ public abstract fun getEnabled ()Lorg/gradle/api/provider/Property;
+ public abstract fun getFailOnWarning ()Lorg/gradle/api/provider/Property;
+ public abstract fun getFinalizeCoroutines ()Lorg/gradle/api/provider/Property;
+ public final fun getFormatName ()Ljava/lang/String;
+ public abstract fun getIncludes ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public abstract fun getModuleName ()Lorg/gradle/api/provider/Property;
+ public abstract fun getModuleVersion ()Lorg/gradle/api/provider/Property;
+ public fun getName ()Ljava/lang/String;
+ public abstract fun getOfflineMode ()Lorg/gradle/api/provider/Property;
+ public abstract fun getOutputDir ()Lorg/gradle/api/file/DirectoryProperty;
+ public final fun getPluginsConfiguration ()Lorg/gradle/api/ExtensiblePolymorphicDomainObjectContainer;
+ public abstract fun getSuppressInheritedMembers ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSuppressObviousFunctions ()Lorg/gradle/api/provider/Property;
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/parameters/DokkaExternalDocumentationLinkSpec : java/io/Serializable, org/gradle/api/Named {
+ public abstract fun getEnabled ()Lorg/gradle/api/provider/Property;
+ public fun getName ()Ljava/lang/String;
+ public abstract fun getPackageListUrl ()Lorg/gradle/api/provider/Property;
+ public abstract fun getUrl ()Lorg/gradle/api/provider/Property;
+ public final fun packageListUrl (Ljava/lang/String;)V
+ public final fun packageListUrl (Lorg/gradle/api/provider/Provider;)V
+ public final fun url (Ljava/lang/String;)V
+ public final fun url (Lorg/gradle/api/provider/Provider;)V
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/parameters/DokkaGeneratorParametersSpec : org/gradle/api/plugins/ExtensionAware {
+ public abstract fun getDokkaModuleFiles ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public final fun getDokkaSourceSets ()Lorg/gradle/api/NamedDomainObjectContainer;
+ public abstract fun getFailOnWarning ()Lorg/gradle/api/provider/Property;
+ public abstract fun getFinalizeCoroutines ()Lorg/gradle/api/provider/Property;
+ public abstract fun getIncludes ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public abstract fun getModuleName ()Lorg/gradle/api/provider/Property;
+ public abstract fun getModuleVersion ()Lorg/gradle/api/provider/Property;
+ public abstract fun getOfflineMode ()Lorg/gradle/api/provider/Property;
+ public abstract fun getPluginsClasspath ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public final fun getPluginsConfiguration ()Lorg/gradle/api/ExtensiblePolymorphicDomainObjectContainer;
+ public abstract fun getSuppressInheritedMembers ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSuppressObviousFunctions ()Lorg/gradle/api/provider/Property;
+}
+
+public final class dev/adamko/dokkatoo/dokka/parameters/DokkaModuleDescriptionKxs$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Ldev/adamko/dokkatoo/dokka/parameters/DokkaModuleDescriptionKxs$$serializer;
+ public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/adamko/dokkatoo/dokka/parameters/DokkaModuleDescriptionKxs;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/adamko/dokkatoo/dokka/parameters/DokkaModuleDescriptionKxs;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class dev/adamko/dokkatoo/dokka/parameters/DokkaModuleDescriptionKxs$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/parameters/DokkaPackageOptionsSpec : dev/adamko/dokkatoo/dokka/parameters/HasConfigurableVisibilityModifiers, java/io/Serializable {
+ public abstract fun getDocumentedVisibilities ()Lorg/gradle/api/provider/SetProperty;
+ public abstract fun getMatchingRegex ()Lorg/gradle/api/provider/Property;
+ public abstract fun getReportUndocumented ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSkipDeprecated ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSuppress ()Lorg/gradle/api/provider/Property;
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/parameters/DokkaSourceLinkSpec : java/io/Serializable {
+ public abstract fun getLocalDirectory ()Lorg/gradle/api/file/DirectoryProperty;
+ public abstract fun getRemoteLineSuffix ()Lorg/gradle/api/provider/Property;
+ public abstract fun getRemoteUrl ()Lorg/gradle/api/provider/Property;
+ public final fun remoteUrl (Ljava/lang/String;)V
+ public final fun remoteUrl (Lorg/gradle/api/provider/Provider;)V
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/parameters/DokkaSourceSetIdSpec : java/io/Serializable, org/gradle/api/Named {
+ public static final field Companion Ldev/adamko/dokkatoo/dokka/parameters/DokkaSourceSetIdSpec$Companion;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getName ()Ljava/lang/String;
+ public final fun getScopeId ()Ljava/lang/String;
+ public final fun getSourceSetName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class dev/adamko/dokkatoo/dokka/parameters/DokkaSourceSetIdSpec$Companion {
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/parameters/DokkaSourceSetSpec : dev/adamko/dokkatoo/dokka/parameters/HasConfigurableVisibilityModifiers, java/io/Serializable, org/gradle/api/Named, org/gradle/api/plugins/ExtensionAware {
+ public abstract fun getAnalysisPlatform ()Lorg/gradle/api/provider/Property;
+ public abstract fun getApiVersion ()Lorg/gradle/api/provider/Property;
+ public abstract fun getClasspath ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public final fun getDependentSourceSets ()Lorg/gradle/api/NamedDomainObjectContainer;
+ public abstract fun getDisplayName ()Lorg/gradle/api/provider/Property;
+ public abstract fun getDocumentedVisibilities ()Lorg/gradle/api/provider/SetProperty;
+ public abstract fun getEnableAndroidDocumentationLink ()Lorg/gradle/api/provider/Property;
+ public abstract fun getEnableJdkDocumentationLink ()Lorg/gradle/api/provider/Property;
+ public abstract fun getEnableKotlinStdLibDocumentationLink ()Lorg/gradle/api/provider/Property;
+ public final fun getExternalDocumentationLinks ()Lorg/gradle/api/NamedDomainObjectContainer;
+ public abstract fun getIncludes ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public abstract fun getJdkVersion ()Lorg/gradle/api/provider/Property;
+ public abstract fun getLanguageVersion ()Lorg/gradle/api/provider/Property;
+ public fun getName ()Ljava/lang/String;
+ public abstract fun getPerPackageOptions ()Lorg/gradle/api/DomainObjectSet;
+ public abstract fun getReportUndocumented ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSamples ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public abstract fun getSkipDeprecated ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSkipEmptyPackages ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSourceLinks ()Lorg/gradle/api/DomainObjectSet;
+ public abstract fun getSourceRoots ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public final fun getSourceSetId ()Lorg/gradle/api/provider/Provider;
+ public abstract fun getSourceSetScope ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSuppress ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSuppressGeneratedFiles ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSuppressedFiles ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public final fun perPackageOption (Lorg/gradle/api/Action;)V
+ public final fun sourceLink (Lorg/gradle/api/Action;)V
+}
+
+public final class dev/adamko/dokkatoo/dokka/parameters/KotlinPlatform : java/lang/Enum {
+ public static final field AndroidJVM Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform;
+ public static final field Common Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform;
+ public static final field Companion Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform$Companion;
+ public static final field JS Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform;
+ public static final field JVM Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform;
+ public static final field Native Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform;
+ public static final field WASM Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform;
+ public static fun valueOf (Ljava/lang/String;)Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform;
+ public static fun values ()[Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform;
+}
+
+public final class dev/adamko/dokkatoo/dokka/parameters/KotlinPlatform$Companion {
+ public final fun fromString (Ljava/lang/String;)Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform;
+ public final fun getDEFAULT ()Ldev/adamko/dokkatoo/dokka/parameters/KotlinPlatform;
+}
+
+public final class dev/adamko/dokkatoo/dokka/parameters/VisibilityModifier : java/lang/Enum {
+ public static final field Companion Ldev/adamko/dokkatoo/dokka/parameters/VisibilityModifier$Companion;
+ public static final field INTERNAL Ldev/adamko/dokkatoo/dokka/parameters/VisibilityModifier;
+ public static final field PACKAGE Ldev/adamko/dokkatoo/dokka/parameters/VisibilityModifier;
+ public static final field PRIVATE Ldev/adamko/dokkatoo/dokka/parameters/VisibilityModifier;
+ public static final field PROTECTED Ldev/adamko/dokkatoo/dokka/parameters/VisibilityModifier;
+ public static final field PUBLIC Ldev/adamko/dokkatoo/dokka/parameters/VisibilityModifier;
+ public static fun valueOf (Ljava/lang/String;)Ldev/adamko/dokkatoo/dokka/parameters/VisibilityModifier;
+ public static fun values ()[Ldev/adamko/dokkatoo/dokka/parameters/VisibilityModifier;
+}
+
+public final class dev/adamko/dokkatoo/dokka/parameters/VisibilityModifier$Companion {
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/plugins/DokkaHtmlPluginParameters : dev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBaseSpec {
+ public static final field Companion Ldev/adamko/dokkatoo/dokka/plugins/DokkaHtmlPluginParameters$Companion;
+ public static final field DOKKA_HTML_PARAMETERS_NAME Ljava/lang/String;
+ public static final field DOKKA_HTML_PLUGIN_FQN Ljava/lang/String;
+ public abstract fun getCustomAssets ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public abstract fun getCustomStyleSheets ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public abstract fun getFooterMessage ()Lorg/gradle/api/provider/Property;
+ public abstract fun getMergeImplicitExpectActualDeclarations ()Lorg/gradle/api/provider/Property;
+ public abstract fun getSeparateInheritedMembers ()Lorg/gradle/api/provider/Property;
+ public abstract fun getTemplatesDir ()Lorg/gradle/api/file/DirectoryProperty;
+ public fun jsonEncode ()Ljava/lang/String;
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/DokkaHtmlPluginParameters$Companion {
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBaseSpec : java/io/Serializable, org/gradle/api/Named {
+ public fun getName ()Ljava/lang/String;
+ public fun getPluginFqn ()Ljava/lang/String;
+ public abstract fun jsonEncode ()Ljava/lang/String;
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder : dev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBaseSpec {
+ public static final field Companion Ldev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder$Companion;
+ public fun getPluginFqn ()Ljava/lang/String;
+ public fun jsonEncode ()Ljava/lang/String;
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder$Companion {
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilderKt {
+ public static final fun PluginConfigBooleanValue (Lorg/gradle/api/provider/Provider;)Lorg/gradle/api/provider/Provider;
+ public static final fun PluginConfigNumberValue (Lorg/gradle/api/provider/Provider;)Lorg/gradle/api/provider/Provider;
+ public static final fun PluginConfigStringValue (Lorg/gradle/api/provider/Provider;)Lorg/gradle/api/provider/Provider;
+ public static final fun PluginConfigValue (Ljava/lang/Number;)Ldev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$NumberValue;
+ public static final fun PluginConfigValue (Ljava/lang/String;)Ldev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$StringValue;
+ public static final fun PluginConfigValue (Z)Ldev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$BooleanValue;
+ public static final fun add (Ldev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Values;Ljava/lang/Number;)V
+ public static final fun add (Ldev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Values;Ljava/lang/String;)V
+ public static final fun add (Ldev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Values;Z)V
+ public static final fun addBoolean (Ldev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Values;Lorg/gradle/api/provider/Provider;)V
+ public static final fun addNumber (Ldev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Values;Lorg/gradle/api/provider/Provider;)V
+ public static final fun addString (Ldev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Values;Lorg/gradle/api/provider/Provider;)V
+ public static final fun booleanProperty (Ldev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder;Ljava/lang/String;Lorg/gradle/api/provider/Provider;)V
+ public static final fun files (Ldev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
+ public static final fun numberProperty (Ldev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder;Ljava/lang/String;Lorg/gradle/api/provider/Provider;)V
+ public static final fun pluginParameters (Lorg/gradle/api/ExtensiblePolymorphicDomainObjectContainer;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
+ public static final fun properties (Ldev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
+ public static final fun property (Ldev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder;Ljava/lang/String;Ljava/lang/Number;)V
+ public static final fun property (Ldev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder;Ljava/lang/String;Ljava/lang/String;)V
+ public static final fun property (Ldev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder;Ljava/lang/String;Z)V
+ public static final fun stringProperty (Ldev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBuilder;Ljava/lang/String;Lorg/gradle/api/provider/Provider;)V
+}
+
+public abstract class dev/adamko/dokkatoo/dokka/plugins/DokkaVersioningPluginParameters : dev/adamko/dokkatoo/dokka/plugins/DokkaPluginParametersBaseSpec {
+ public static final field Companion Ldev/adamko/dokkatoo/dokka/plugins/DokkaVersioningPluginParameters$Companion;
+ public static final field DOKKA_VERSIONING_PLUGIN_FQN Ljava/lang/String;
+ public static final field DOKKA_VERSIONING_PLUGIN_PARAMETERS_NAME Ljava/lang/String;
+ public abstract fun getOlderVersions ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public abstract fun getOlderVersionsDir ()Lorg/gradle/api/file/DirectoryProperty;
+ public abstract fun getRenderVersionsNavigationOnAllPages ()Lorg/gradle/api/provider/Property;
+ public abstract fun getVersion ()Lorg/gradle/api/provider/Property;
+ public abstract fun getVersionsOrdering ()Lorg/gradle/api/provider/ListProperty;
+ public fun jsonEncode ()Ljava/lang/String;
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/DokkaVersioningPluginParameters$Companion {
+}
+
+public abstract interface class dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue {
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$BooleanValue : dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Primitive {
+ public fun <init> (Z)V
+ public final fun getBoolean ()Z
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$DirectoryValue : dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue {
+ public fun <init> (Lorg/gradle/api/file/DirectoryProperty;)V
+ public final fun getDirectory ()Lorg/gradle/api/file/DirectoryProperty;
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$FileValue : dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue {
+ public fun <init> (Lorg/gradle/api/file/RegularFileProperty;)V
+ public final fun getFile ()Lorg/gradle/api/file/RegularFileProperty;
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$FilesValue : dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue {
+ public fun <init> (Lorg/gradle/api/file/ConfigurableFileCollection;)V
+ public final fun getFiles ()Lorg/gradle/api/file/ConfigurableFileCollection;
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$NumberValue : dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Primitive {
+ public fun <init> (Ljava/lang/Number;)V
+ public final fun getNumber ()Ljava/lang/Number;
+}
+
+public abstract interface class dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Primitive : dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue {
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Properties : dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue {
+ public fun <init> (Lorg/gradle/api/provider/MapProperty;)V
+ public final fun getValues ()Lorg/gradle/api/provider/MapProperty;
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$StringValue : dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Primitive {
+ public fun <init> (Ljava/lang/String;)V
+ public final fun getString ()Ljava/lang/String;
+}
+
+public final class dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue$Values : dev/adamko/dokkatoo/dokka/plugins/PluginConfigValue {
+ public fun <init> (Lorg/gradle/api/provider/ListProperty;)V
+ public final fun getValues ()Lorg/gradle/api/provider/ListProperty;
+}
+
+public abstract class dev/adamko/dokkatoo/formats/DokkatooFormatPlugin : org/gradle/api/Plugin {
+ public fun <init> (Ljava/lang/String;)V
+ public synthetic fun apply (Ljava/lang/Object;)V
+ public fun apply (Lorg/gradle/api/Project;)V
+ public fun configure (Ldev/adamko/dokkatoo/formats/DokkatooFormatPlugin$DokkatooFormatPluginContext;)V
+ public final fun getFormatName ()Ljava/lang/String;
+}
+
+public final class dev/adamko/dokkatoo/formats/DokkatooFormatTasks$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action {
+ public fun <init> (Lkotlin/jvm/functions/Function1;)V
+ public final synthetic fun execute (Ljava/lang/Object;)V
+}
+
+public abstract class dev/adamko/dokkatoo/formats/DokkatooGfmPlugin : dev/adamko/dokkatoo/formats/DokkatooFormatPlugin {
+ public fun configure (Ldev/adamko/dokkatoo/formats/DokkatooFormatPlugin$DokkatooFormatPluginContext;)V
+}
+
+public abstract class dev/adamko/dokkatoo/formats/DokkatooHtmlPlugin : dev/adamko/dokkatoo/formats/DokkatooFormatPlugin {
+ public fun configure (Ldev/adamko/dokkatoo/formats/DokkatooFormatPlugin$DokkatooFormatPluginContext;)V
+}
+
+public final class dev/adamko/dokkatoo/formats/DokkatooHtmlPlugin$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action {
+ public fun <init> (Lkotlin/jvm/functions/Function1;)V
+ public final synthetic fun execute (Ljava/lang/Object;)V
+}
+
+public abstract class dev/adamko/dokkatoo/formats/DokkatooJavadocPlugin : dev/adamko/dokkatoo/formats/DokkatooFormatPlugin {
+ public fun configure (Ldev/adamko/dokkatoo/formats/DokkatooFormatPlugin$DokkatooFormatPluginContext;)V
+}
+
+public abstract class dev/adamko/dokkatoo/formats/DokkatooJekyllPlugin : dev/adamko/dokkatoo/formats/DokkatooFormatPlugin {
+ public fun configure (Ldev/adamko/dokkatoo/formats/DokkatooFormatPlugin$DokkatooFormatPluginContext;)V
+}
+
+public abstract interface annotation class dev/adamko/dokkatoo/internal/DokkatooInternalApi : java/lang/annotation/Annotation {
+}
+
+public abstract class dev/adamko/dokkatoo/tasks/DokkatooGenerateTask : dev/adamko/dokkatoo/tasks/DokkatooTask {
+ public abstract fun getCacheDirectory ()Lorg/gradle/api/file/DirectoryProperty;
+ public abstract fun getGenerationType ()Lorg/gradle/api/provider/Property;
+ public final fun getGenerator ()Ldev/adamko/dokkatoo/dokka/parameters/DokkaGeneratorParametersSpec;
+ public abstract fun getOutputDirectory ()Lorg/gradle/api/file/DirectoryProperty;
+ public abstract fun getPublicationEnabled ()Lorg/gradle/api/provider/Property;
+ public abstract fun getRuntimeClasspath ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public abstract fun getWorkerDebugEnabled ()Lorg/gradle/api/provider/Property;
+ public abstract fun getWorkerJvmArgs ()Lorg/gradle/api/provider/ListProperty;
+ public abstract fun getWorkerLogFile ()Lorg/gradle/api/file/RegularFileProperty;
+ public abstract fun getWorkerMaxHeapSize ()Lorg/gradle/api/provider/Property;
+ public abstract fun getWorkerMinHeapSize ()Lorg/gradle/api/provider/Property;
+}
+
+public final class dev/adamko/dokkatoo/tasks/DokkatooGenerateTask$GenerationType : java/lang/Enum {
+ public static final field MODULE Ldev/adamko/dokkatoo/tasks/DokkatooGenerateTask$GenerationType;
+ public static final field PUBLICATION Ldev/adamko/dokkatoo/tasks/DokkatooGenerateTask$GenerationType;
+ public static fun valueOf (Ljava/lang/String;)Ldev/adamko/dokkatoo/tasks/DokkatooGenerateTask$GenerationType;
+ public static fun values ()[Ldev/adamko/dokkatoo/tasks/DokkatooGenerateTask$GenerationType;
+}
+
+public abstract class dev/adamko/dokkatoo/tasks/DokkatooPrepareModuleDescriptorTask : dev/adamko/dokkatoo/tasks/DokkatooTask {
+ public abstract fun getDokkaModuleDescriptorJson ()Lorg/gradle/api/file/RegularFileProperty;
+ public abstract fun getIncludes ()Lorg/gradle/api/file/ConfigurableFileCollection;
+ public abstract fun getModuleDirectory ()Lorg/gradle/api/file/DirectoryProperty;
+ public abstract fun getModuleName ()Lorg/gradle/api/provider/Property;
+ public abstract fun getModulePath ()Lorg/gradle/api/provider/Property;
+}
+
+public abstract class dev/adamko/dokkatoo/tasks/DokkatooTask : org/gradle/api/DefaultTask {
+ public abstract fun getObjects ()Lorg/gradle/api/model/ObjectFactory;
+}
+
+public abstract class dev/adamko/dokkatoo/tasks/LogHtmlPublicationLinkTask : dev/adamko/dokkatoo/tasks/DokkatooTask {
+ public static final field Companion Ldev/adamko/dokkatoo/tasks/LogHtmlPublicationLinkTask$Companion;
+ public static final field ENABLE_TASK_PROPERTY_NAME Ljava/lang/String;
+ public final fun exec ()V
+ public abstract fun getIndexHtmlPath ()Lorg/gradle/api/provider/Property;
+ public abstract fun getServerUri ()Lorg/gradle/api/provider/Property;
+}
+
+public final class dev/adamko/dokkatoo/tasks/LogHtmlPublicationLinkTask$Companion {
+}
+
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/build.gradle.kts b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/build.gradle.kts
new file mode 100644
index 00000000..8bb60f57
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/build.gradle.kts
@@ -0,0 +1,254 @@
+@file:Suppress("UnstableApiUsage") // jvm test suites & test report aggregation are incubating
+
+import buildsrc.utils.buildDir_
+import buildsrc.utils.skipTestFixturesPublications
+
+plugins {
+ buildsrc.conventions.`kotlin-gradle-plugin`
+ kotlin("plugin.serialization")
+
+ dev.adamko.kotlin.`binary-compatibility-validator`
+
+ dev.adamko.`dokkatoo-html`
+ buildsrc.conventions.`maven-publishing`
+
+ `java-test-fixtures`
+ `jvm-test-suite`
+ `test-report-aggregation`
+ buildsrc.conventions.`maven-publish-test`
+}
+
+description = "Generates documentation for Kotlin projects (using Dokka)"
+
+dependencies {
+ // ideally there should be a 'dokka-core-api' dependency (that is very thin and doesn't drag in loads of unnecessary code)
+ // that would be used as an implementation dependency, while dokka-core would be used as a compileOnly dependency
+ // https://github.com/Kotlin/dokka/issues/2933
+ implementation(libs.kotlin.dokkaCore)
+
+ compileOnly(libs.gradlePlugin.kotlin)
+ compileOnly(libs.gradlePlugin.kotlin.klibCommonizerApi)
+ compileOnly(libs.gradlePlugin.android)
+ compileOnly(libs.gradlePlugin.androidApi)
+
+ implementation(platform(libs.kotlinxSerialization.bom))
+ implementation(libs.kotlinxSerialization.json)
+
+ testFixturesImplementation(gradleApi())
+ testFixturesImplementation(gradleTestKit())
+
+ testFixturesCompileOnly(libs.kotlin.dokkaCore)
+ testFixturesImplementation(platform(libs.kotlinxSerialization.bom))
+ testFixturesImplementation(libs.kotlinxSerialization.json)
+
+ testFixturesCompileOnly(libs.kotlin.dokkaCore)
+
+ testFixturesApi(platform(libs.kotest.bom))
+ testFixturesApi(libs.kotest.junit5Runner)
+ testFixturesApi(libs.kotest.assertionsCore)
+ testFixturesApi(libs.kotest.assertionsJson)
+ testFixturesApi(libs.kotest.datatest)
+
+ // don't define test dependencies here, instead define them in the testing.suites {} configuration below
+}
+
+gradlePlugin {
+ isAutomatedPublishing = true
+
+ plugins.register("dokkatoo") {
+ id = "org.jetbrains.dokka.dokkatoo"
+ displayName = "Dokkatoo"
+ description = "Generates documentation for Kotlin projects (using Dokka)"
+ implementationClass = "org.jetbrains.dokka.dokkatoo.DokkatooPlugin"
+ }
+
+ fun registerDokkaPlugin(
+ pluginClass: String,
+ shortName: String,
+ longName: String = shortName,
+ ) {
+ plugins.register(pluginClass) {
+ id = "org.jetbrains.dokka.dokkatoo-${shortName.toLowerCase()}"
+ displayName = "Dokkatoo $shortName"
+ description = "Generates $longName documentation for Kotlin projects (using Dokka)"
+ implementationClass = "org.jetbrains.dokka.dokkatoo.formats.$pluginClass"
+ }
+ }
+ registerDokkaPlugin("DokkatooGfmPlugin", "GFM", longName = "GFM (GitHub Flavoured Markdown)")
+ registerDokkaPlugin("DokkatooHtmlPlugin", "HTML")
+ registerDokkaPlugin("DokkatooJavadocPlugin", "Javadoc")
+ registerDokkaPlugin("DokkatooJekyllPlugin", "Jekyll")
+
+ plugins.configureEach {
+ website.set("https://github.com/adamko-dev/dokkatoo/")
+ vcsUrl.set("https://github.com/adamko-dev/dokkatoo.git")
+ tags.addAll(
+ "dokka",
+ "dokkatoo",
+ "kotlin",
+ "kdoc",
+ "android",
+ "documentation",
+ "javadoc",
+ "html",
+ "markdown",
+ "gfm",
+ "website",
+ )
+ }
+}
+
+kotlin {
+ target {
+ compilations.configureEach {
+ // TODO Dokkatoo uses Gradle 8, while Dokka uses Gradle 7, which has an older version of Kotlin that
+ // doesn't include these options - so update them or update Gradle.
+// compilerOptions.configure {
+// freeCompilerArgs.addAll(
+// "-opt-in=org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi",
+// )
+// }
+ }
+ }
+}
+
+testing.suites {
+ withType<JvmTestSuite>().configureEach {
+ useJUnitJupiter()
+
+ dependencies {
+ implementation(project.dependencies.gradleTestKit())
+
+ implementation(project.dependencies.testFixtures(project()))
+
+ implementation(project.dependencies.platform(libs.kotlinxSerialization.bom))
+ implementation(libs.kotlinxSerialization.json)
+ }
+
+ targets.configureEach {
+ testTask.configure {
+ val projectTestTempDirPath = "$buildDir_/test-temp-dir"
+ inputs.property("projectTestTempDir", projectTestTempDirPath)
+ systemProperty("projectTestTempDir", projectTestTempDirPath)
+
+ when (testType.get()) {
+ TestSuiteType.FUNCTIONAL_TEST,
+ TestSuiteType.INTEGRATION_TEST -> {
+ dependsOn(tasks.matching { it.name == "publishAllPublicationsToTestRepository" })
+
+ systemProperties(
+ "testMavenRepoDir" to file(mavenPublishTest.testMavenRepo).canonicalPath,
+ )
+
+ // depend on the test-publication task, but not the test-maven repo
+ // (otherwise this task will never be up-to-date)
+ dependsOn(tasks.publishToTestMavenRepo)
+ }
+ }
+ }
+ }
+ }
+
+
+ /** Unit tests suite */
+ val test by getting(JvmTestSuite::class) {
+ description = "Standard unit tests"
+ }
+
+
+ /** Functional tests suite */
+ val testFunctional by registering(JvmTestSuite::class) {
+ description = "Tests that use Gradle TestKit to test functionality"
+ testType.set(TestSuiteType.FUNCTIONAL_TEST)
+
+ targets.all {
+ testTask.configure {
+ shouldRunAfter(test)
+ }
+ }
+ }
+
+ tasks.check { dependsOn(test, testFunctional) }
+}
+
+skipTestFixturesPublications()
+
+val aggregateTestReports by tasks.registering(TestReport::class) {
+ group = LifecycleBasePlugin.VERIFICATION_GROUP
+ destinationDirectory.set(layout.buildDirectory.dir("reports/tests/aggregated"))
+
+ dependsOn(tasks.withType<AbstractTestTask>())
+
+ // hardcoded dirs is a bit of a hack, but a fileTree just didn't work
+ testResults.from("$buildDir_/test-results/test/binary")
+ testResults.from("$buildDir_/test-results/testFunctional/binary")
+ testResults.from("$buildDir_/test-results/testIntegration/binary")
+
+ doLast {
+ logger.lifecycle("Aggregated test report: file://${destinationDirectory.asFile.get()}/index.html")
+ }
+}
+
+binaryCompatibilityValidator {
+ ignoredMarkers.add("org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi")
+}
+
+val dokkatooVersion = provider { project.version.toString() }
+
+val dokkatooConstantsProperties = objects.mapProperty<String, String>().apply {
+ put("DOKKATOO_VERSION", dokkatooVersion)
+ put("DOKKA_VERSION", libs.versions.kotlin.dokka)
+}
+
+val buildConfigFileContents: Provider<TextResource> =
+ dokkatooConstantsProperties.map { constants ->
+
+ val vals = constants.entries
+ .sortedBy { it.key }
+ .joinToString("\n") { (k, v) ->
+ """const val $k = "$v""""
+ }.prependIndent(" ")
+
+ resources.text.fromString(
+ """
+ |package org.jetbrains.dokka.dokkatoo.internal
+ |
+ |@DokkatooInternalApi
+ |object DokkatooConstants {
+ |$vals
+ |}
+ |
+ """.trimMargin()
+ )
+ }
+
+val generateDokkatooConstants by tasks.registering(Sync::class) {
+ group = project.name
+
+ val buildConfigFileContents = buildConfigFileContents
+
+ from(buildConfigFileContents) {
+ rename { "DokkatooConstants.kt" }
+ into("dev/adamko/dokkatoo/internal/")
+ }
+
+ into(layout.buildDirectory.dir("generated-source/main/kotlin/"))
+}
+
+kotlin.sourceSets.main {
+ kotlin.srcDir(generateDokkatooConstants.map { it.destinationDir })
+}
+
+dokkatoo {
+ dokkatooSourceSets.configureEach {
+ externalDocumentationLinks.register("gradle") {
+ // https://docs.gradle.org/current/javadoc/index.html
+ url("https://docs.gradle.org/${gradle.gradleVersion}/javadoc/")
+ }
+ sourceLink {
+ localDirectory.set(file("src/main/kotlin"))
+ val relativeProjectPath = projectDir.relativeToOrNull(rootDir)?.invariantSeparatorsPath ?: ""
+ remoteUrl("https://github.com/adamko-dev/dokkatoo/tree/main/$relativeProjectPath/src/main/kotlin")
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt
new file mode 100644
index 00000000..9d67471a
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooBasePlugin.kt
@@ -0,0 +1,355 @@
+package org.jetbrains.dokka.dokkatoo
+
+import org.jetbrains.dokka.dokkatoo.distributions.DokkatooConfigurationAttributes
+import org.jetbrains.dokka.dokkatoo.distributions.DokkatooConfigurationAttributes.Companion.DOKKATOO_BASE_ATTRIBUTE
+import org.jetbrains.dokka.dokkatoo.distributions.DokkatooConfigurationAttributes.Companion.DOKKATOO_CATEGORY_ATTRIBUTE
+import org.jetbrains.dokka.dokkatoo.distributions.DokkatooConfigurationAttributes.Companion.DOKKA_FORMAT_ATTRIBUTE
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetSpec
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.KotlinPlatform
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.VisibilityModifier
+import org.jetbrains.dokka.dokkatoo.internal.*
+import org.jetbrains.dokka.dokkatoo.tasks.DokkatooGenerateTask
+import org.jetbrains.dokka.dokkatoo.tasks.DokkatooPrepareModuleDescriptorTask
+import org.jetbrains.dokka.dokkatoo.tasks.DokkatooTask
+import java.io.File
+import javax.inject.Inject
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+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.*
+import org.gradle.language.base.plugins.LifecycleBasePlugin
+
+/**
+ * The base plugin for Dokkatoo. Sets up Dokkatoo and configures default values, but does not
+ * add any specific config (specifically, it does not create Dokka Publications).
+ */
+abstract class DokkatooBasePlugin
+@DokkatooInternalApi
+@Inject
+constructor(
+ private val providers: ProviderFactory,
+ private val layout: ProjectLayout,
+ private val objects: ObjectFactory,
+) : Plugin<Project> {
+
+ override fun apply(target: Project) {
+ // apply the lifecycle-base plugin so the clean task is available
+ target.pluginManager.apply(LifecycleBasePlugin::class)
+
+ val dokkatooExtension = createExtension(target)
+
+ target.tasks.createDokkaLifecycleTasks()
+
+ val configurationAttributes = objects.newInstance<DokkatooConfigurationAttributes>()
+
+ target.dependencies.attributesSchema {
+ attribute(DOKKATOO_BASE_ATTRIBUTE)
+ attribute(DOKKATOO_CATEGORY_ATTRIBUTE)
+ attribute(DOKKA_FORMAT_ATTRIBUTE)
+ }
+
+ target.configurations.register(dependencyContainerNames.dokkatoo) {
+ description = "Fetch all Dokkatoo files from all configurations in other subprojects"
+ asConsumer()
+ isVisible = false
+ attributes {
+ attribute(DOKKATOO_BASE_ATTRIBUTE, configurationAttributes.dokkatooBaseUsage)
+ }
+ }
+
+ configureDokkaPublicationsDefaults(dokkatooExtension)
+ dokkatooExtension.dokkatooSourceSets.configureDefaults(
+ sourceSetScopeConvention = dokkatooExtension.sourceSetScopeDefault
+ )
+
+ target.tasks.withType<DokkatooGenerateTask>().configureEach {
+ cacheDirectory.convention(dokkatooExtension.dokkatooCacheDirectory)
+ workerDebugEnabled.convention(false)
+ workerLogFile.convention(temporaryDir.resolve("dokka-worker.log"))
+ workerJvmArgs.set(
+ listOf(
+ //"-XX:MaxMetaspaceSize=512m",
+ "-XX:+HeapDumpOnOutOfMemoryError",
+ "-XX:+AlwaysPreTouch", // https://github.com/gradle/gradle/issues/3093#issuecomment-387259298
+ //"-XX:StartFlightRecording=disk=true,name={path.drop(1).map { if (it.isLetterOrDigit()) it else '-' }.joinToString("")},dumponexit=true,duration=30s",
+ //"-XX:FlightRecorderOptions=repository=$baseDir/jfr,stackdepth=512",
+ )
+ )
+ dokkaConfigurationJsonFile.convention(temporaryDir.resolve("dokka-configuration.json"))
+ }
+
+ target.tasks.withType<DokkatooPrepareModuleDescriptorTask>().configureEach {
+ moduleName.convention(dokkatooExtension.moduleName)
+ includes.from(providers.provider { dokkatooExtension.dokkatooSourceSets.flatMap { it.includes } })
+ modulePath.convention(dokkatooExtension.modulePath)
+ }
+
+ target.tasks.withType<DokkatooGenerateTask>().configureEach {
+
+ publicationEnabled.convention(true)
+ onlyIf("publication must be enabled") { publicationEnabled.getOrElse(true) }
+
+ 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
+ )
+ }
+
+ dokkatooExtension.dokkatooSourceSets.configureDefaults(
+ sourceSetScopeConvention = dokkatooExtension.sourceSetScopeDefault
+ )
+ }
+
+ private fun createExtension(project: Project): DokkatooExtension {
+ val dokkatooExtension = project.extensions.create<DokkatooExtension>(EXTENSION_NAME).apply {
+ 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"))
+ dokkatooModuleDirectory.convention(layout.buildDirectory.dir("dokka-module"))
+ dokkatooConfigurationsDirectory.convention(layout.buildDirectory.dir("dokka-config"))
+ }
+
+ dokkatooExtension.versions {
+ jetbrainsDokka.convention(DokkatooConstants.DOKKA_VERSION)
+ jetbrainsMarkdown.convention("0.3.1")
+ freemarker.convention("2.3.31")
+ kotlinxHtml.convention("0.8.0")
+ kotlinxCoroutines.convention("1.6.4")
+ }
+
+ return dokkatooExtension
+ }
+
+ /** Set defaults in all [DokkatooExtension.dokkatooPublications]s */
+ private fun configureDokkaPublicationsDefaults(
+ dokkatooExtension: DokkatooExtension,
+ ) {
+ dokkatooExtension.dokkatooPublications.all {
+ enabled.convention(true)
+ cacheRoot.convention(dokkatooExtension.dokkatooCacheDirectory)
+ delayTemplateSubstitution.convention(false)
+ failOnWarning.convention(false)
+ finalizeCoroutines.convention(false)
+ moduleName.convention(dokkatooExtension.moduleName)
+ moduleVersion.convention(dokkatooExtension.moduleVersion)
+ offlineMode.convention(false)
+ outputDir.convention(dokkatooExtension.dokkatooPublicationDirectory)
+ suppressInheritedMembers.convention(false)
+ suppressObviousFunctions.convention(true)
+ }
+ }
+
+ /** Set conventions for all [DokkaSourceSetSpec] properties */
+ private fun NamedDomainObjectContainer<DokkaSourceSetSpec>.configureDefaults(
+ sourceSetScopeConvention: Property<String>,
+ ) {
+ configureEach dss@{
+ analysisPlatform.convention(KotlinPlatform.DEFAULT)
+ displayName.convention(
+ analysisPlatform.map { platform ->
+ // Match existing Dokka naming conventions. (This should probably be simplified!)
+ when {
+ // Multiplatform source sets (e.g. commonMain, jvmMain, macosMain)
+ name.endsWith("Main") -> name.substringBeforeLast("Main")
+
+ // indeterminate source sets should be named by the Kotlin platform
+ else -> platform.displayName
+ }
+ }
+ )
+ documentedVisibilities.convention(setOf(VisibilityModifier.PUBLIC))
+ jdkVersion.convention(8)
+
+ enableKotlinStdLibDocumentationLink.convention(true)
+ enableJdkDocumentationLink.convention(true)
+ enableAndroidDocumentationLink.convention(
+ analysisPlatform.map { it == KotlinPlatform.AndroidJVM }
+ )
+
+ reportUndocumented.convention(false)
+ skipDeprecated.convention(false)
+ skipEmptyPackages.convention(true)
+ sourceSetScope.convention(sourceSetScopeConvention)
+
+ // Manually added sourceSets should not be suppressed by default. dokkatooSourceSets that are
+ // automatically added by DokkatooKotlinAdapter will have a sensible value for suppress.
+ suppress.convention(false)
+
+ suppressGeneratedFiles.convention(true)
+
+ sourceLinks.configureEach {
+ localDirectory.convention(layout.projectDirectory)
+ remoteLineSuffix.convention("#L")
+ }
+
+ perPackageOptions.configureEach {
+ matchingRegex.convention(".*")
+ suppress.convention(false)
+ skipDeprecated.convention(false)
+ reportUndocumented.convention(false)
+ }
+
+ externalDocumentationLinks {
+ configureEach {
+ enabled.convention(true)
+ packageListUrl.convention(url.map { it.appendPath("package-list") })
+ }
+
+ maybeCreate("jdk") {
+ enabled.convention(this@dss.enableJdkDocumentationLink)
+ url(this@dss.jdkVersion.map { jdkVersion ->
+ when {
+ jdkVersion < 11 -> "https://docs.oracle.com/javase/${jdkVersion}/docs/api/"
+ else -> "https://docs.oracle.com/en/java/javase/${jdkVersion}/docs/api/"
+ }
+ })
+ packageListUrl(this@dss.jdkVersion.map { jdkVersion ->
+ when {
+ jdkVersion < 11 -> "https://docs.oracle.com/javase/${jdkVersion}/docs/api/package-list"
+ else -> "https://docs.oracle.com/en/java/javase/${jdkVersion}/docs/api/element-list"
+ }
+ })
+ }
+
+ maybeCreate("kotlinStdlib") {
+ enabled.convention(this@dss.enableKotlinStdLibDocumentationLink)
+ url("https://kotlinlang.org/api/latest/jvm/stdlib/")
+ }
+
+ maybeCreate("androidSdk") {
+ enabled.convention(this@dss.enableAndroidDocumentationLink)
+ url("https://developer.android.com/reference/kotlin/")
+ }
+
+ maybeCreate("androidX") {
+ enabled.convention(this@dss.enableAndroidDocumentationLink)
+ url("https://developer.android.com/reference/kotlin/")
+ packageListUrl("https://developer.android.com/reference/kotlin/androidx/package-list")
+ }
+ }
+ }
+ }
+
+ private fun TaskContainer.createDokkaLifecycleTasks() {
+ register<DokkatooTask>(taskNames.generate) {
+ description = "Generates Dokkatoo publications for all formats"
+ dependsOn(withType<DokkatooGenerateTask>())
+ }
+ }
+
+ // workaround for https://github.com/gradle/gradle/issues/23708
+ 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<File>): RegularFileProperty =
+ convention(objects.fileProperty().fileProvider(file))
+
+ companion object {
+
+ const val EXTENSION_NAME = "dokkatoo"
+
+ /**
+ * The group of all Dokkatoo [Gradle tasks][org.gradle.api.Task].
+ *
+ * @see org.gradle.api.Task.getGroup
+ */
+ const val TASK_GROUP = "dokkatoo"
+
+ /** The names of [Gradle tasks][org.gradle.api.Task] created by Dokkatoo */
+ val taskNames = TaskNames(null)
+
+ /** The names of [Configuration]s created by Dokkatoo */
+ val dependencyContainerNames = DependencyContainerNames(null)
+
+ internal val jsonMapper = Json {
+ prettyPrint = true
+ @OptIn(ExperimentalSerializationApi::class)
+ prettyPrintIndent = " "
+ }
+ }
+
+ @DokkatooInternalApi
+ abstract class HasFormatName {
+ abstract val formatName: String?
+
+ /** Appends [formatName] to the end of the string, camelcase style, if [formatName] is not null */
+ protected fun String.appendFormat(): String =
+ when (val name = formatName) {
+ null -> this
+ else -> this + name.uppercaseFirstChar()
+ }
+ }
+
+ /**
+ * Names of the Gradle [Configuration]s used by the [Dokkatoo Plugin][DokkatooBasePlugin].
+ *
+ * Beware the confusing terminology:
+ * - [Gradle Configurations][org.gradle.api.artifacts.Configuration] - share files between subprojects. Each has a name.
+ * - [DokkaConfiguration][org.jetbrains.dokka.DokkaConfiguration] - parameters for executing the Dokka Generator
+ */
+ @DokkatooInternalApi
+ class DependencyContainerNames(override val formatName: String?) : HasFormatName() {
+
+ val dokkatoo = "dokkatoo".appendFormat()
+
+ /** Name of the [Configuration] that _consumes_ all [org.jetbrains.dokka.DokkaConfiguration.DokkaModuleDescription] files */
+ val dokkatooModuleFilesConsumer = "dokkatooModule".appendFormat()
+
+ /** Name of the [Configuration] that _provides_ all [org.jetbrains.dokka.DokkaConfiguration.DokkaModuleDescription] files to other projects */
+ val dokkatooModuleFilesProvider = "dokkatooModuleElements".appendFormat()
+
+ /**
+ * Classpath used to execute the Dokka Generator.
+ *
+ * Extends [dokkaPluginsClasspath], so Dokka plugins and their dependencies are included.
+ */
+ val dokkaGeneratorClasspath = "dokkatooGeneratorClasspath".appendFormat()
+
+ /** Dokka Plugins (including transitive dependencies, so this can be passed to the Dokka Generator Worker classpath) */
+ val dokkaPluginsClasspath = "dokkatooPlugin".appendFormat()
+
+ /**
+ * Dokka Plugins (excluding transitive dependencies) will be used to create Dokka Generator Parameters
+ *
+ * Generally, this configuration should not be invoked manually. Instead, use [dokkaPluginsClasspath].
+ */
+ val dokkaPluginsIntransitiveClasspath = "dokkatooPluginIntransitive".appendFormat()
+ }
+
+ @DokkatooInternalApi
+ class TaskNames(override val formatName: String?) : HasFormatName() {
+ val generate = "dokkatooGenerate".appendFormat()
+ val generatePublication = "dokkatooGeneratePublication".appendFormat()
+ val generateModule = "dokkatooGenerateModule".appendFormat()
+ val prepareModuleDescriptor = "prepareDokkatooModuleDescriptor".appendFormat()
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooExtension.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooExtension.kt
new file mode 100644
index 00000000..d7b91541
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooExtension.kt
@@ -0,0 +1,130 @@
+package org.jetbrains.dokka.dokkatoo
+
+import org.jetbrains.dokka.dokkatoo.dokka.DokkaPublication
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetSpec
+import org.jetbrains.dokka.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
+import org.gradle.kotlin.dsl.*
+
+/**
+ * Configure the behaviour of the [DokkatooBasePlugin].
+ */
+abstract class DokkatooExtension
+@DokkatooInternalApi
+constructor(
+ objects: ObjectFactory,
+) : ExtensionAware, Serializable {
+
+ /** Directory into which [DokkaPublication]s will be produced */
+ abstract val dokkatooPublicationDirectory: DirectoryProperty
+
+ /** Directory into which Dokka Modules will be produced */
+ abstract val dokkatooModuleDirectory: DirectoryProperty
+
+ abstract val dokkatooConfigurationsDirectory: DirectoryProperty
+
+ /** Default Dokkatoo cache directory */
+ abstract val dokkatooCacheDirectory: DirectoryProperty
+
+ abstract val moduleName: Property<String>
+ abstract val moduleVersion: Property<String>
+ abstract val modulePath: Property<String>
+
+ /**
+ * An arbitrary string used to group source sets that originate from different Gradle subprojects.
+ *
+ * This is primarily used by Kotlin Multiplatform projects, which can have multiple source sets
+ * per subproject.
+ *
+ * Defaults to [the path of the subproject][org.gradle.api.Project.getPath].
+ */
+ abstract val sourceSetScopeDefault: Property<String>
+
+ /**
+ * 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.
+ *
+ * Each publication will generate one Dokka site based on the included Dokka Source Sets.
+ *
+ * The type of site is determined by the Dokka Plugins. By default, an HTML site will be generated.
+ */
+ val dokkatooPublications: NamedDomainObjectContainer<DokkaPublication> =
+ extensions.adding(
+ "dokkatooPublications",
+ objects.domainObjectContainer { named -> objects.newInstance(named, pluginsConfiguration) }
+ )
+
+ /**
+ * Dokka Source Sets describe the source code that should be included in a Dokka Publication.
+ *
+ * Dokka will not generate documentation unless there is at least there is at least one Dokka Source Set.
+ *
+ * TODO make sure dokkatooSourceSets doc is up to date...
+ *
+ * Only source sets that are contained within _this project_ should be included here.
+ * To merge source sets from other projects, use the Gradle dependencies block.
+ *
+ * ```kotlin
+ * dependencies {
+ * // merge :other-project into this project's Dokka Configuration
+ * dokka(project(":other-project"))
+ * }
+ * ```
+ *
+ * Or, to include other Dokka Publications as a Dokka Module use
+ *
+ * ```kotlin
+ * dependencies {
+ * // include :other-project as a module in this project's Dokka Configuration
+ * dokkaModule(project(":other-project"))
+ * }
+ * ```
+ *
+ * Dokka will merge Dokka Source Sets from other subprojects if...
+ */
+ val dokkatooSourceSets: NamedDomainObjectContainer<DokkaSourceSetSpec> =
+ extensions.adding("dokkatooSourceSets", objects.domainObjectContainer())
+
+ /**
+ * Dokka Plugin are used to configure the way Dokka generates a format.
+ * Some plugins can be configured via parameters, and those parameters are stored in this
+ * container.
+ */
+ val pluginsConfiguration: DokkaPluginParametersContainer =
+ extensions.adding("pluginsConfiguration", objects.dokkaPluginParametersContainer())
+
+ /**
+ * Versions of dependencies that Dokkatoo will use to run Dokka Generator.
+ *
+ * These versions can be set to change the versions of dependencies that Dokkatoo uses defaults,
+ * or can be read to align versions.
+ */
+ val versions: Versions = extensions.adding("versions", objects.newInstance())
+
+ interface Versions : ExtensionAware {
+
+ /** Default version used for Dokka dependencies */
+ val jetbrainsDokka: Property<String>
+ val jetbrainsMarkdown: Property<String>
+ val freemarker: Property<String>
+ val kotlinxHtml: Property<String>
+ val kotlinxCoroutines: Property<String>
+
+ companion object
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooPlugin.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooPlugin.kt
new file mode 100644
index 00000000..0ace2ca6
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/DokkatooPlugin.kt
@@ -0,0 +1,32 @@
+package org.jetbrains.dokka.dokkatoo
+
+import org.jetbrains.dokka.dokkatoo.formats.DokkatooGfmPlugin
+import org.jetbrains.dokka.dokkatoo.formats.DokkatooHtmlPlugin
+import org.jetbrains.dokka.dokkatoo.formats.DokkatooJavadocPlugin
+import org.jetbrains.dokka.dokkatoo.formats.DokkatooJekyllPlugin
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.*
+
+/**
+ * Dokkatoo Gradle Plugin.
+ *
+ * Creates all necessary defaults to generate documentation for HTML, Jekyll, Markdown, and Javadoc formats.
+ */
+abstract class DokkatooPlugin
+@DokkatooInternalApi
+constructor() : Plugin<Project> {
+
+ override fun apply(target: Project) {
+ with(target.pluginManager) {
+ apply(type = DokkatooBasePlugin::class)
+
+ // auto-apply the custom format plugins
+ apply(type = DokkatooGfmPlugin::class)
+ apply(type = DokkatooHtmlPlugin::class)
+ apply(type = DokkatooJavadocPlugin::class)
+ apply(type = DokkatooJekyllPlugin::class)
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooAndroidAdapter.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooAndroidAdapter.kt
new file mode 100644
index 00000000..f5261bb4
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooAndroidAdapter.kt
@@ -0,0 +1,214 @@
+package org.jetbrains.dokka.dokkatoo.adapters
+
+import com.android.build.api.dsl.CommonExtension
+import com.android.build.gradle.AppExtension
+import com.android.build.gradle.BaseExtension
+import com.android.build.gradle.LibraryExtension
+import com.android.build.gradle.TestExtension
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.internal.dependency.VariantDependencies
+import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.CLASSES_JAR
+import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.PROCESSED_JAR
+import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin
+import org.jetbrains.dokka.dokkatoo.DokkatooExtension
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.KotlinPlatform
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.collectIncomingFiles
+import javax.inject.Inject
+import org.gradle.api.DomainObjectSet
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE
+import org.gradle.api.file.FileCollection
+import org.gradle.api.logging.Logging
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.provider.ProviderFactory
+import org.gradle.kotlin.dsl.*
+
+@DokkatooInternalApi
+abstract class DokkatooAndroidAdapter @Inject constructor(
+ private val objects: ObjectFactory,
+) : Plugin<Project> {
+
+ override fun apply(project: Project) {
+ logger.info("applied DokkatooAndroidAdapter to ${project.path}")
+
+ project.plugins.withType<DokkatooBasePlugin>().configureEach {
+ project.pluginManager.apply {
+ withPlugin("com.android.base") { configure(project) }
+ withPlugin("com.android.application") { configure(project) }
+ withPlugin("com.android.library") { configure(project) }
+ }
+ }
+ }
+
+ protected fun configure(project: Project) {
+ val dokkatooExtension = project.extensions.getByType<DokkatooExtension>()
+
+ val androidExt = AndroidExtensionWrapper(project)
+
+ if (androidExt == null) {
+ logger.warn("DokkatooAndroidAdapter could not get Android Extension for project ${project.path}")
+ return
+ }
+
+ dokkatooExtension.dokkatooSourceSets.configureEach {
+
+ classpath.from(
+ analysisPlatform.map { analysisPlatform ->
+ when (analysisPlatform) {
+ KotlinPlatform.AndroidJVM ->
+ AndroidClasspathCollector(
+ androidExt = androidExt,
+ configurations = project.configurations,
+ objects = objects,
+ )
+
+ else ->
+ objects.fileCollection()
+ }
+ }
+ )
+ }
+ }
+
+ @DokkatooInternalApi
+ companion object {
+ private val logger = Logging.getLogger(DokkatooAndroidAdapter::class.java)
+ }
+}
+
+private fun AndroidExtensionWrapper(
+ project: Project
+): AndroidExtensionWrapper? {
+
+// fetching _all_ configuration names is very brute force and should probably be refined to
+// only fetch those that match a specific DokkaSourceSetSpec
+
+ return runCatching {
+ val androidExt = project.extensions.getByType<BaseExtension>()
+ AndroidExtensionWrapper.forBaseExtension(
+ androidExt = androidExt,
+ providers = project.providers,
+ objects = project.objects
+ )
+ }.recoverCatching {
+ val androidExt = project.extensions.getByType(CommonExtension::class)
+ AndroidExtensionWrapper.forCommonExtension(androidExt)
+ }.getOrNull()
+}
+
+/**
+ * Android Gradle Plugin is having a refactor. Try to wrap the Android extension so that Dokkatoo
+ * can still access the configuration names without caring about which AGP version is in use.
+ */
+private interface AndroidExtensionWrapper {
+ fun variantConfigurationNames(): Set<String>
+
+ companion object {
+
+ @Suppress("DEPRECATION")
+ fun forBaseExtension(
+ androidExt: BaseExtension,
+ providers: ProviderFactory,
+ objects: ObjectFactory,
+ ): AndroidExtensionWrapper {
+ return object : AndroidExtensionWrapper {
+ /** Fetch all configuration names used by all variants. */
+ override fun variantConfigurationNames(): Set<String> {
+ val collector = objects.domainObjectSet(BaseVariant::class)
+
+ val variants: DomainObjectSet<BaseVariant> =
+ collector.apply {
+ addAllLater(providers.provider {
+ when (androidExt) {
+ is LibraryExtension -> androidExt.libraryVariants
+ is AppExtension -> androidExt.applicationVariants
+ is TestExtension -> androidExt.applicationVariants
+ else -> emptyList()
+ }
+ })
+ }
+
+ return buildSet {
+ variants.forEach {
+ add(it.compileConfiguration.name)
+ add(it.runtimeConfiguration.name)
+ add(it.annotationProcessorConfiguration.name)
+ }
+ }
+ }
+ }
+ }
+
+ fun forCommonExtension(
+ androidExt: CommonExtension<*, *, *, *>
+ ): AndroidExtensionWrapper {
+ return object : AndroidExtensionWrapper {
+ /** Fetch all configuration names used by all variants. */
+ override fun variantConfigurationNames(): Set<String> {
+ return buildSet {
+ @Suppress("UnstableApiUsage")
+ androidExt.sourceSets.forEach {
+ add(it.apiConfigurationName)
+ add(it.compileOnlyConfigurationName)
+ add(it.implementationConfigurationName)
+ add(it.runtimeOnlyConfigurationName)
+ add(it.wearAppConfigurationName)
+ add(it.annotationProcessorConfigurationName)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * A utility for determining the classpath of an Android compilation.
+ *
+ * It's important that this class is separate from [DokkatooAndroidAdapter]. It must be separate
+ * because it uses Android Gradle Plugin classes (like [BaseExtension]). Were it not separate, and
+ * these classes were present in the function signatures of [DokkatooAndroidAdapter], then when
+ * Gradle tries to create a decorated instance of [DokkatooAndroidAdapter] it will if the project
+ * does not have the Android Gradle Plugin applied, because the classes will be missing.
+ */
+private object AndroidClasspathCollector {
+
+ operator fun invoke(
+ androidExt: AndroidExtensionWrapper,
+ configurations: ConfigurationContainer,
+ objects: ObjectFactory,
+ ): FileCollection {
+ val compilationClasspath = objects.fileCollection()
+
+ fun collectConfiguration(named: String) {
+ listOf(
+ // need to fetch multiple different types of files, because AGP is weird and doesn't seem
+ // to have a 'just give me normal JVM classes' option
+ ARTIFACT_TYPE_ATTRIBUTE to PROCESSED_JAR.type,
+ ARTIFACT_TYPE_ATTRIBUTE to CLASSES_JAR.type,
+ ).forEach { (attribute, attributeValue) ->
+ configurations.collectIncomingFiles(named, collector = compilationClasspath) {
+ attributes {
+ attribute(attribute, attributeValue)
+ }
+ lenient(true)
+ }
+ }
+ }
+
+ // fetch android.jar
+ collectConfiguration(named = VariantDependencies.CONFIG_NAME_ANDROID_APIS)
+
+ val variantConfigurations = androidExt.variantConfigurationNames()
+
+ for (variantConfig in variantConfigurations) {
+ collectConfiguration(named = variantConfig)
+ }
+
+ return compilationClasspath
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooJavaAdapter.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooJavaAdapter.kt
new file mode 100644
index 00000000..0f834363
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooJavaAdapter.kt
@@ -0,0 +1,40 @@
+package org.jetbrains.dokka.dokkatoo.adapters
+
+import org.jetbrains.dokka.dokkatoo.DokkatooExtension
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import javax.inject.Inject
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.logging.Logging
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.kotlin.dsl.*
+
+/**
+ * Apply Java specific configuration to the Dokkatoo plugin.
+ *
+ * **Must be applied *after* [org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin]**
+ */
+@DokkatooInternalApi
+abstract class DokkatooJavaAdapter @Inject constructor() : Plugin<Project> {
+
+ private val logger = Logging.getLogger(this::class.java)
+
+ override fun apply(project: Project) {
+ logger.info("applied DokkatooJavaAdapter to ${project.path}")
+
+ // wait for the Java plugin to be applied
+ project.plugins.withType<JavaBasePlugin>().configureEach {
+
+ // fetch the toolchain, and use the language version as Dokka's jdkVersion
+ val toolchainLanguageVersion = project.extensions.getByType<JavaPluginExtension>()
+ .toolchain
+ .languageVersion
+
+ val dokka = project.extensions.getByType<DokkatooExtension>()
+ dokka.dokkatooSourceSets.configureEach {
+ jdkVersion.set(toolchainLanguageVersion.map { it.asInt() }.orElse(8))
+ }
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt
new file mode 100644
index 00000000..82df651d
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/adapters/DokkatooKotlinAdapter.kt
@@ -0,0 +1,459 @@
+package org.jetbrains.dokka.dokkatoo.adapters
+
+import com.android.build.gradle.api.ApplicationVariant
+import com.android.build.gradle.api.LibraryVariant
+import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin
+import org.jetbrains.dokka.dokkatoo.DokkatooExtension
+import org.jetbrains.dokka.dokkatoo.adapters.DokkatooKotlinAdapter.Companion.currentKotlinToolingVersion
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetIdSpec
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetIdSpec.Companion.dokkaSourceSetIdSpec
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetSpec
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.KotlinPlatform
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.not
+import java.io.File
+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.file.ConfigurableFileCollection
+import org.gradle.api.file.FileCollection
+import org.gradle.api.logging.Logging
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.plugins.ExtensionContainer
+import org.gradle.api.provider.ListProperty
+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
+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
+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.
+ *
+ * This is not a standalone plugin, it requires [org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin] is also applied.
+ */
+@DokkatooInternalApi
+abstract class DokkatooKotlinAdapter @Inject constructor(
+ private val objects: ObjectFactory,
+ private val providers: ProviderFactory,
+) : Plugin<Project> {
+
+ override fun apply(project: Project) {
+ logger.info("applied DokkatooKotlinAdapter to ${project.path}")
+
+ project.plugins.withType<DokkatooBasePlugin>().configureEach {
+ project.pluginManager.apply {
+ withPlugin("org.jetbrains.kotlin.android") { exec(project) }
+ withPlugin("org.jetbrains.kotlin.js") { exec(project) }
+ withPlugin("org.jetbrains.kotlin.jvm") { exec(project) }
+ withPlugin("org.jetbrains.kotlin.multiplatform") { exec(project) }
+ }
+ }
+ }
+
+ private fun exec(project: Project) {
+ val kotlinExtension = project.extensions.findKotlinExtension() ?: run {
+ logger.info("could not find Kotlin Extension")
+ return
+ }
+ logger.info("Configuring Dokkatoo in Gradle Kotlin Project ${project.path}")
+
+ val dokkatooExtension = project.extensions.getByType<DokkatooExtension>()
+
+ // first fetch the relevant properties of all KotlinCompilations
+ val compilationDetailsBuilder = KotlinCompilationDetailsBuilder(
+ providers = providers,
+ objects = objects,
+ konanHome = dokkatooExtension.konanHome.asFile,
+ )
+ val allKotlinCompilationDetails: ListProperty<KotlinCompilationDetails> =
+ compilationDetailsBuilder.createCompilationDetails(
+ kotlinProjectExtension = kotlinExtension,
+ )
+
+ // second, fetch the relevant properties of the Kotlin source sets
+ val sourceSetDetailsBuilder = KotlinSourceSetDetailsBuilder(
+ providers = providers,
+ objects = objects,
+ sourceSetScopeDefault = dokkatooExtension.sourceSetScopeDefault,
+ projectPath = project.path,
+ )
+ val sourceSetDetails: NamedDomainObjectContainer<KotlinSourceSetDetails> =
+ sourceSetDetailsBuilder.createSourceSetDetails(
+ kotlinSourceSets = kotlinExtension.sourceSets,
+ allKotlinCompilationDetails = allKotlinCompilationDetails,
+ )
+
+ // for each Kotlin source set, register a Dokkatoo source set
+ registerDokkatooSourceSets(
+ dokkatooExtension = dokkatooExtension,
+ sourceSetDetails = sourceSetDetails,
+ )
+ }
+
+ /** Register a [DokkaSourceSetSpec] for each element in [sourceSetDetails] */
+ private fun registerDokkatooSourceSets(
+ dokkatooExtension: DokkatooExtension,
+ sourceSetDetails: NamedDomainObjectContainer<KotlinSourceSetDetails>,
+ ) {
+ // proactively use 'all' so source sets will be available in users' build files if they use `named("...")`
+ sourceSetDetails.all details@{
+ dokkatooExtension.dokkatooSourceSets.register(details = this@details)
+ }
+ }
+
+ /** Register a single [DokkaSourceSetSpec] for [details] */
+ private fun NamedDomainObjectContainer<DokkaSourceSetSpec>.register(
+ details: KotlinSourceSetDetails
+ ) {
+ val kssPlatform = details.compilations.map { values: List<KotlinCompilationDetails> ->
+ values.map { it.kotlinPlatform }
+ .distinct()
+ .singleOrNull() ?: KotlinPlatform.Common
+ }
+
+ val kssClasspath = determineClasspath(details)
+
+ register(details.name) dss@{
+ suppress.set(!details.isPublishedSourceSet())
+ sourceRoots.from(details.sourceDirectories)
+ classpath.from(kssClasspath)
+ analysisPlatform.set(kssPlatform)
+ dependentSourceSets.addAllLater(details.dependentSourceSetIds)
+ }
+ }
+
+ private fun determineClasspath(
+ details: KotlinSourceSetDetails
+ ): Provider<FileCollection> {
+ return details.compilations.map { compilations: List<KotlinCompilationDetails> ->
+ val classpath = objects.fileCollection()
+
+ if (compilations.isNotEmpty()) {
+ compilations.fold(classpath) { acc, compilation ->
+ acc.from(compilation.compilationClasspath)
+ // can't use compileDependencyFiles, it causes weird dependency resolution errors in Android projects
+ //acc.from(providers.provider { compilation.compileDependencyFiles })
+ }
+ } else {
+ classpath
+ .from(details.sourceDirectories)
+ .from(details.sourceDirectoriesOfDependents)
+ }
+ }
+ }
+
+ @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()
+ // fallback to trying to get the JVM extension
+ // (not sure why I did this... maybe to be compatible with really old versions?)
+ ?: findByType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension>()
+ } catch (e: Throwable) {
+ when (e) {
+ is TypeNotPresentException,
+ is ClassNotFoundException,
+ is NoClassDefFoundError -> null
+
+ 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)
+ }
+ }
+}
+
+
+/**
+ * Store the details of all [KotlinCompilation]s in a configuration cache compatible way.
+ *
+ * The compilation details may come from a multiplatform project ([KotlinMultiplatformExtension])
+ * or a single-platform project ([KotlinSingleTargetExtension]).
+ */
+@DokkatooInternalApi
+private data class KotlinCompilationDetails(
+ val target: String,
+ val kotlinPlatform: KotlinPlatform,
+ val allKotlinSourceSetsNames: Set<String>,
+ val publishedCompilation: Boolean,
+ val dependentSourceSetNames: Set<String>,
+ 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 konanHome: Provider<File>,
+) {
+
+ fun createCompilationDetails(
+ kotlinProjectExtension: KotlinProjectExtension,
+ ): ListProperty<KotlinCompilationDetails> {
+
+ val details = objects.listProperty<KotlinCompilationDetails>()
+
+ details.addAll(
+ providers.provider {
+ kotlinProjectExtension
+ .allKotlinCompilations()
+ .map { compilation ->
+ createCompilationDetails(compilation = compilation)
+ }
+ })
+
+ return details
+ }
+
+ /** Create a single [KotlinCompilationDetails] for [compilation] */
+ private fun createCompilationDetails(
+ compilation: KotlinCompilation<*>,
+ ): KotlinCompilationDetails {
+ val allKotlinSourceSetsNames =
+ compilation.allKotlinSourceSets.map { it.name } + compilation.defaultSourceSet.name
+
+ val dependentSourceSetNames =
+ compilation.defaultSourceSet.dependsOn.map { it.name }
+
+ val compilationClasspath: FileCollection =
+ collectKotlinCompilationClasspath(compilation = compilation)
+
+ return KotlinCompilationDetails(
+ target = compilation.target.name,
+ kotlinPlatform = KotlinPlatform.fromString(compilation.platformType.name),
+ allKotlinSourceSetsNames = allKotlinSourceSetsNames.toSet(),
+ publishedCompilation = compilation.isPublished(),
+ dependentSourceSetNames = dependentSourceSetNames.toSet(),
+ compilationClasspath = compilationClasspath,
+ defaultSourceSetName = compilation.defaultSourceSet.name
+ )
+ }
+
+ private fun KotlinProjectExtension.allKotlinCompilations(): Collection<KotlinCompilation<*>> =
+ when (this) {
+ is KotlinMultiplatformExtension -> targets.flatMap { it.compilations }
+ is KotlinSingleTargetExtension<*> -> target.compilations
+ else -> emptyList() // shouldn't happen?
+ }
+
+ /**
+ * Get the [Configuration][org.gradle.api.artifacts.Configuration] names of all configurations
+ * used to build this [KotlinCompilation] and
+ * [its source sets][KotlinCompilation.kotlinSourceSets].
+ */
+ private fun collectKotlinCompilationClasspath(
+ compilation: KotlinCompilation<*>,
+ ): FileCollection {
+ val compilationClasspath = objects.fileCollection()
+
+ // 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 {
+
+ /**
+ * 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) {
+ is KotlinMetadataCompilation<*> -> true
+
+ is KotlinJvmAndroidCompilation ->
+ androidVariant is LibraryVariant || androidVariant is ApplicationVariant
+
+ else ->
+ name == MAIN_COMPILATION_NAME
+ }
+ }
+ }
+}
+
+
+/**
+ * Store the details of all [KotlinSourceSet]s in a configuration cache compatible way.
+ *
+ * @param[named] Should be [KotlinSourceSet.getName]
+ */
+@DokkatooInternalApi
+private abstract class KotlinSourceSetDetails @Inject constructor(
+ private val named: String,
+) : Named {
+
+ /** Direct source sets that this source set depends on */
+ abstract val dependentSourceSetIds: SetProperty<DokkaSourceSetIdSpec>
+ abstract val sourceDirectories: ConfigurableFileCollection
+ /** _All_ source directories from any (recursively) dependant source set */
+ abstract val sourceDirectoriesOfDependents: ConfigurableFileCollection
+ /** The specific compilations used to build this source set */
+ abstract val compilations: ListProperty<KotlinCompilationDetails>
+
+ /** Estimate if this Kotlin source set contains 'published' sources */
+ fun isPublishedSourceSet(): Provider<Boolean> =
+ compilations.map { values ->
+ values.any { it.publishedCompilation }
+ }
+
+ override fun getName(): String = named
+}
+
+/** Utility class, encapsulating logic for building [KotlinCompilationDetails] */
+private class KotlinSourceSetDetailsBuilder(
+ private val sourceSetScopeDefault: Provider<String>,
+ private val objects: ObjectFactory,
+ private val providers: ProviderFactory,
+ /** Used for logging */
+ private val projectPath: String,
+) {
+
+ private val logger = Logging.getLogger(KotlinSourceSetDetails::class.java)
+
+ fun createSourceSetDetails(
+ kotlinSourceSets: NamedDomainObjectContainer<KotlinSourceSet>,
+ allKotlinCompilationDetails: ListProperty<KotlinCompilationDetails>,
+ ): NamedDomainObjectContainer<KotlinSourceSetDetails> {
+
+ val sourceSetDetails = objects.domainObjectContainer(KotlinSourceSetDetails::class)
+
+ kotlinSourceSets.configureEach kss@{
+ sourceSetDetails.register(
+ kotlinSourceSet = this,
+ allKotlinCompilationDetails = allKotlinCompilationDetails,
+ )
+ }
+
+ return sourceSetDetails
+ }
+
+ private fun NamedDomainObjectContainer<KotlinSourceSetDetails>.register(
+ kotlinSourceSet: KotlinSourceSet,
+ allKotlinCompilationDetails: ListProperty<KotlinCompilationDetails>,
+ ) {
+
+ // TODO: Needs to respect filters.
+ // We probably need to change from "sourceRoots" to support "sourceFiles"
+ // https://github.com/Kotlin/dokka/issues/1215
+ val extantSourceDirectories = providers.provider {
+ kotlinSourceSet.kotlin.sourceDirectories.filter { it.exists() }
+ }
+
+ val compilations = allKotlinCompilationDetails.map { allCompilations ->
+ allCompilations.filter { compilation ->
+ kotlinSourceSet.name in compilation.allKotlinSourceSetsNames
+ }
+ }
+
+ // determine the source sets IDs of _other_ source sets that _this_ source depends on.
+ val dependentSourceSets = providers.provider { kotlinSourceSet.dependsOn }
+ val dependentSourceSetIds =
+ providers.zip(
+ dependentSourceSets,
+ sourceSetScopeDefault,
+ ) { sourceSets, sourceSetScope ->
+ logger.info("[$projectPath] source set ${kotlinSourceSet.name} has ${sourceSets.size} dependents ${sourceSets.joinToString { it.name }}")
+ sourceSets.map { dependedKss ->
+ objects.dokkaSourceSetIdSpec(sourceSetScope, dependedKss.name)
+ }
+ }
+
+ val sourceDirectoriesOfDependents = providers.provider {
+ kotlinSourceSet
+ .allDependentSourceSets()
+ .fold(objects.fileCollection()) { acc, sourceSet ->
+ acc.from(sourceSet.kotlin.sourceDirectories)
+ }
+ }
+
+ register(kotlinSourceSet.name) {
+ this.dependentSourceSetIds.addAll(dependentSourceSetIds)
+ this.sourceDirectories.from(extantSourceDirectories)
+ this.sourceDirectoriesOfDependents.from(sourceDirectoriesOfDependents)
+ this.compilations.addAll(compilations)
+ }
+ }
+
+ /**
+ * Return a list containing _all_ source sets that this source set depends on,
+ * searching recursively.
+ *
+ * @see KotlinSourceSet.dependsOn
+ */
+ private tailrec fun KotlinSourceSet.allDependentSourceSets(
+ queue: Set<KotlinSourceSet> = dependsOn.toSet(),
+ allDependents: List<KotlinSourceSet> = emptyList(),
+ ): List<KotlinSourceSet> {
+ val next = queue.firstOrNull() ?: return allDependents
+ return next.allDependentSourceSets(
+ queue = (queue - next) union next.dependsOn,
+ allDependents = allDependents + next,
+ )
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/distributions/DokkatooConfigurationAttributes.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/distributions/DokkatooConfigurationAttributes.kt
new file mode 100644
index 00000000..57ca5ef9
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/distributions/DokkatooConfigurationAttributes.kt
@@ -0,0 +1,59 @@
+package org.jetbrains.dokka.dokkatoo.distributions
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import javax.inject.Inject
+import org.gradle.api.Named
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.attributes.Attribute
+import org.gradle.api.attributes.Usage
+import org.gradle.api.model.ObjectFactory
+import org.gradle.kotlin.dsl.*
+
+/**
+ * Gradle Configuration Attributes for sharing Dokkatoo files across subprojects.
+ *
+ * These attributes are used to tag [Configuration]s, so files can be shared between subprojects.
+ */
+@DokkatooInternalApi
+abstract class DokkatooConfigurationAttributes
+@Inject
+constructor(
+ objects: ObjectFactory,
+) {
+
+ /** A general attribute for all [Configuration]s that are used by the Dokka Gradle plugin */
+ val dokkatooBaseUsage: DokkatooBaseAttribute = objects.named("dokkatoo")
+
+ /** for [Configuration]s that provide or consume Dokka parameter files */
+ val dokkaParameters: DokkatooCategoryAttribute = objects.named("generator-parameters")
+
+ /** for [Configuration]s that provide or consume Dokka Module files */
+ val dokkaModuleFiles: DokkatooCategoryAttribute = objects.named("module-files")
+// val dokkaModuleSource: DokkatooCategoryAttribute = objects.named("module-source")
+
+ val dokkaGeneratorClasspath: DokkatooCategoryAttribute = objects.named("generator-classpath")
+
+ val dokkaPluginsClasspath: DokkatooCategoryAttribute = objects.named("plugins-classpath")
+
+ @DokkatooInternalApi
+ interface DokkatooBaseAttribute : Usage
+
+ @DokkatooInternalApi
+ interface DokkatooCategoryAttribute : Named
+
+ @DokkatooInternalApi
+ interface DokkaFormatAttribute : Named
+
+ @DokkatooInternalApi
+ companion object {
+ val DOKKATOO_BASE_ATTRIBUTE =
+ Attribute<DokkatooBaseAttribute>("org.jetbrains.dokka.dokkatoo.base")
+ val DOKKATOO_CATEGORY_ATTRIBUTE =
+ Attribute<DokkatooCategoryAttribute>("org.jetbrains.dokka.dokkatoo.category")
+ val DOKKA_FORMAT_ATTRIBUTE =
+ Attribute<DokkaFormatAttribute>("org.jetbrains.dokka.dokkatoo.format")
+
+ private inline fun <reified T> Attribute(name: String): Attribute<T> =
+ Attribute.of(name, T::class.java)
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/DokkaPublication.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/DokkaPublication.kt
new file mode 100644
index 00000000..50c26415
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/DokkaPublication.kt
@@ -0,0 +1,122 @@
+package org.jetbrains.dokka.dokkatoo.dokka
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkaPluginParametersContainer
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.adding
+import java.io.Serializable
+import javax.inject.Inject
+import org.gradle.api.Named
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.*
+import org.gradle.api.tasks.PathSensitivity.RELATIVE
+import org.gradle.kotlin.dsl.*
+
+/**
+ * A [DokkaPublication] describes a single Dokka output.
+ *
+ * Each Publication has its own set of Gradle tasks and [org.gradle.api.artifacts.Configuration]s.
+ *
+ * The type of site is determined by the Dokka Plugins. By default, an HTML site will be generated.
+ * By default, Dokka will create publications for HTML, Jekyll, and GitHub Flavoured Markdown.
+ */
+abstract class DokkaPublication
+@DokkatooInternalApi
+@Inject
+constructor(
+ @get:Internal
+ val formatName: String,
+
+ /**
+ * Configurations for Dokka Generator Plugins. Must be provided from
+ * [org.jetbrains.dokka.dokkatoo.DokkatooExtension.pluginsConfiguration].
+ */
+ pluginsConfiguration: DokkaPluginParametersContainer,
+) : Named, Serializable, ExtensionAware {
+
+ /** Configurations for Dokka Generator Plugins. */
+ @get:Nested
+ val pluginsConfiguration: DokkaPluginParametersContainer =
+ extensions.adding("pluginsConfiguration", pluginsConfiguration)
+
+ @Internal
+ override fun getName(): String = formatName
+
+ @get:Input
+ abstract val enabled: Property<Boolean>
+
+ @get:Input
+ abstract val moduleName: Property<String>
+
+ @get:Input
+ @get:Optional
+ abstract val moduleVersion: Property<String>
+
+ @get:Internal
+ // marked as Internal because this task does not use the directory contents, only the location
+ abstract val outputDir: DirectoryProperty
+
+ /**
+ * Because [outputDir] must be [Internal] (so Gradle doesn't check the directory contents),
+ * [outputDirPath] is required so Gradle can determine if the task is up-to-date.
+ */
+ @get:Input
+ // marked as an Input because a DokkaPublication is used to configure the appropriate
+ // DokkatooTasks, which will then
+ @DokkatooInternalApi
+ protected val outputDirPath: Provider<String>
+ get() = outputDir.map { it.asFile.invariantSeparatorsPath }
+
+ @get:Internal
+ // Marked as Internal because this task does not use the directory contents, only the location.
+ // Note that `cacheRoot` is not used by Dokka, and will probably be deprecated.
+ abstract val cacheRoot: DirectoryProperty
+
+ /**
+ * Because [cacheRoot] must be [Internal] (so Gradle doesn't check the directory contents),
+ * [cacheRootPath] is required so Gradle can determine if the task is up-to-date.
+ */
+ @get:Input
+ @get:Optional
+ @DokkatooInternalApi
+ protected val cacheRootPath: Provider<String>
+ get() = cacheRoot.map { it.asFile.invariantSeparatorsPath }
+
+ @get:Input
+ abstract val offlineMode: Property<Boolean>
+
+// /** Dokka Configuration files from other subprojects that will be merged into this Dokka Configuration */
+// @get:InputFiles
+// @get:NormalizeLineEndings
+// @get:PathSensitive(PathSensitivity.NAME_ONLY)
+// abstract val dokkaSubprojectConfigurations: ConfigurableFileCollection
+
+// /** Dokka Module Configuration from other subprojects. */
+// @get:InputFiles
+// @get:NormalizeLineEndings
+// @get:PathSensitive(PathSensitivity.NAME_ONLY)
+// abstract val dokkaModuleDescriptorFiles: ConfigurableFileCollection
+
+ @get:Input
+ abstract val failOnWarning: Property<Boolean>
+
+ @get:Input
+ abstract val delayTemplateSubstitution: Property<Boolean>
+
+ @get:Input
+ abstract val suppressObviousFunctions: Property<Boolean>
+
+ @get:InputFiles
+ @get:PathSensitive(RELATIVE)
+ abstract val includes: ConfigurableFileCollection
+
+ @get:Input
+ abstract val suppressInheritedMembers: Property<Boolean>
+
+ @get:Input
+ // TODO probably not needed any more, since Dokka Generator now runs in an isolated JVM process
+ abstract val finalizeCoroutines: Property<Boolean>
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaExternalDocumentationLinkSpec.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaExternalDocumentationLinkSpec.kt
new file mode 100644
index 00000000..e91721aa
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaExternalDocumentationLinkSpec.kt
@@ -0,0 +1,120 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import java.io.Serializable
+import java.net.URI
+import javax.inject.Inject
+import org.gradle.api.Named
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.intellij.lang.annotations.Language
+
+/**
+ * Configuration builder that allows creating links leading to externally hosted
+ * documentation of your dependencies.
+ *
+ * For instance, if you are using types from `kotlinx.serialization`, by default
+ * they will be unclickable in your documentation, as if unresolved. However,
+ * since API reference for `kotlinx.serialization` is also built by Dokka and is
+ * [published on kotlinlang.org](https://kotlinlang.org/api/kotlinx.serialization/),
+ * you can configure external documentation links for it, allowing Dokka to generate
+ * documentation links for used types, making them clickable and appear resolved.
+ *
+ * Example in Gradle Kotlin DSL:
+ *
+ * ```kotlin
+ * externalDocumentationLink {
+ * url.set(URI("https://kotlinlang.org/api/kotlinx.serialization/"))
+ * packageListUrl.set(
+ * rootProject.projectDir.resolve("serialization.package.list").toURI()
+ * )
+ * }
+ * ```
+ */
+abstract class DokkaExternalDocumentationLinkSpec
+@DokkatooInternalApi
+@Inject
+constructor(
+ private val name: String
+) : Serializable, Named {
+
+ /**
+ * Root URL of documentation to link with.
+ *
+ * Dokka will do its best to automatically find `package-list` for the given URL, and link
+ * declarations together.
+ *
+ * It automatic resolution fails or if you want to use locally cached files instead,
+ * consider providing [packageListUrl].
+ *
+ * Example:
+ *
+ * ```kotlin
+ * java.net.URI("https://kotlinlang.org/api/kotlinx.serialization/")
+ * ```
+ */
+ @get:Input
+ abstract val url: Property<URI>
+
+ /**
+ * Set the value of [url].
+ *
+ * @param[value] will be converted to a [URI]
+ */
+ fun url(@Language("http-url-reference") value: String): Unit =
+ url.set(URI(value))
+
+ /**
+ * Set the value of [url].
+ *
+ * @param[value] will be converted to a [URI]
+ */
+ fun url(value: Provider<String>): Unit =
+ url.set(value.map(::URI))
+
+ /**
+ * Specifies the exact location of a `package-list` instead of relying on Dokka
+ * automatically resolving it. Can also be a locally cached file to avoid network calls.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * rootProject.projectDir.resolve("serialization.package.list").toURL()
+ * ```
+ */
+ @get:Input
+ abstract val packageListUrl: Property<URI>
+
+ /**
+ * Set the value of [packageListUrl].
+ *
+ * @param[value] will be converted to a [URI]
+ */
+ fun packageListUrl(@Language("http-url-reference") value: String): Unit =
+ packageListUrl.set(URI(value))
+
+ /**
+ * Set the value of [packageListUrl].
+ *
+ * @param[value] will be converted to a [URI]
+ */
+ fun packageListUrl(value: Provider<String>): Unit =
+ packageListUrl.set(value.map(::URI))
+
+ /**
+ * If enabled this link will be passed to the Dokka Generator.
+ *
+ * Defaults to `true`.
+ *
+ * @see org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetSpec.enableKotlinStdLibDocumentationLink
+ * @see org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetSpec.enableJdkDocumentationLink
+ * @see org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetSpec.enableAndroidDocumentationLink
+ */
+ @get:Input
+ abstract val enabled: Property<Boolean>
+
+ @Internal
+ override fun getName(): String = name
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaGeneratorParametersSpec.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaGeneratorParametersSpec.kt
new file mode 100644
index 00000000..41090e65
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaGeneratorParametersSpec.kt
@@ -0,0 +1,93 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkaPluginParametersContainer
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.adding
+import org.jetbrains.dokka.dokkatoo.internal.domainObjectContainer
+import javax.inject.Inject
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.*
+import org.gradle.api.tasks.PathSensitivity.RELATIVE
+import org.gradle.work.NormalizeLineEndings
+
+/**
+ * Parameters used to run Dokka Generator to produce either a Publication or a Module.
+ *
+ *
+ */
+abstract class DokkaGeneratorParametersSpec
+@DokkatooInternalApi
+@Inject
+constructor(
+ objects: ObjectFactory,
+ /**
+ * Configurations for Dokka Generator Plugins. Must be provided from
+ * [org.jetbrains.dokka.dokkatoo.dokka.DokkaPublication.pluginsConfiguration].
+ */
+ @get:Nested
+ val pluginsConfiguration: DokkaPluginParametersContainer,
+) : ExtensionAware {
+
+// /** Dokka Configuration files from other subprojects that will be merged into this Dokka Configuration */
+// @get:InputFiles
+// //@get:NormalizeLineEndings
+// @get:PathSensitive(PathSensitivity.RELATIVE)
+// @get:Optional
+// abstract val dokkaSubprojectParameters: ConfigurableFileCollection
+
+ @get:Input
+ abstract val failOnWarning: Property<Boolean>
+
+ @get:Input
+ abstract val finalizeCoroutines: Property<Boolean>
+
+ @get:Input
+ abstract val moduleName: Property<String>
+
+ @get:Input
+ @get:Optional
+ abstract val moduleVersion: Property<String>
+
+ @get:Input
+ abstract val offlineMode: Property<Boolean>
+
+ @get:Input
+ abstract val suppressObviousFunctions: Property<Boolean>
+
+ @get:Input
+ abstract val suppressInheritedMembers: Property<Boolean>
+
+ @get:InputFiles
+ @get:PathSensitive(RELATIVE)
+ abstract val includes: ConfigurableFileCollection
+
+ /**
+ * Classpath that contains the Dokka Generator Plugins used to modify this publication.
+ *
+ * The plugins should be configured in [org.jetbrains.dokka.dokkatoo.dokka.DokkaPublication.pluginsConfiguration].
+ */
+ @get:InputFiles
+ @get:Classpath
+ abstract val pluginsClasspath: ConfigurableFileCollection
+
+ /**
+ * Source sets used to generate a Dokka Module.
+ *
+ * The values are not used directly in this task, but they are required to be registered as a
+ * task input for up-to-date checks
+ */
+ @get:Nested
+ val dokkaSourceSets: NamedDomainObjectContainer<DokkaSourceSetSpec> =
+ extensions.adding("dokkaSourceSets", objects.domainObjectContainer())
+
+ /** Dokka Module files from other subprojects. */
+ @get:InputFiles
+ @get:NormalizeLineEndings
+ @get:PathSensitive(RELATIVE)
+ @get:Optional
+ abstract val dokkaModuleFiles: ConfigurableFileCollection
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaModuleDescriptionSpec.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaModuleDescriptionSpec.kt
new file mode 100644
index 00000000..af3e13b0
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaModuleDescriptionSpec.kt
@@ -0,0 +1,49 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import javax.inject.Inject
+import org.gradle.api.Named
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.jetbrains.dokka.DokkaConfiguration
+
+/**
+ * Properties that describe a Dokka Module.
+ *
+ * These values are passed into Dokka Generator, which will aggregate all provided Modules into a
+ * single publication.
+ */
+@DokkatooInternalApi
+abstract class DokkaModuleDescriptionSpec
+@DokkatooInternalApi
+@Inject constructor(
+ @get:Input
+ val moduleName: String,
+) : Named {
+
+ /**
+ * @see DokkaConfiguration.DokkaModuleDescription.sourceOutputDirectory
+ */
+ @get:Input
+ abstract val sourceOutputDirectory: RegularFileProperty
+
+ /**
+ * @see DokkaConfiguration.DokkaModuleDescription.includes
+ */
+ @get:Input
+ abstract val includes: ConfigurableFileCollection
+
+ /**
+ * File path of the subproject that determines where the Dokka Module will be placed within an
+ * assembled Dokka Publication.
+ *
+ * This must be a relative path, and will be appended to the root Dokka Publication directory.
+ *
+ * The Gradle project path will also be accepted ([org.gradle.api.Project.getPath]), and the
+ * colons `:` will be replaced with file separators `/`.
+ */
+ @get:Input
+ abstract val projectPath: Property<String>
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaPackageOptionsSpec.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaPackageOptionsSpec.kt
new file mode 100644
index 00000000..44e55a74
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaPackageOptionsSpec.kt
@@ -0,0 +1,84 @@
+@file:Suppress("FunctionName")
+
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import java.io.Serializable
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.SetProperty
+import org.gradle.api.tasks.Input
+
+/**
+ * Configuration builder that allows setting some options for specific packages
+ * matched by [matchingRegex].
+ *
+ * Example in Gradle Kotlin DSL:
+ *
+ * ```kotlin
+ * tasks.dokkaHtml {
+ * dokkaSourceSets.configureEach {
+ * perPackageOption {
+ * matchingRegex.set(".*internal.*")
+ * suppress.set(true)
+ * }
+ * }
+ * }
+ * ```
+ */
+abstract class DokkaPackageOptionsSpec
+@DokkatooInternalApi
+constructor() :
+ HasConfigurableVisibilityModifiers,
+ Serializable {
+
+ /**
+ * Regular expression that is used to match the package.
+ *
+ * Default is any string: `.*`.
+ */
+ @get:Input
+ abstract val matchingRegex: Property<String>
+
+ /**
+ * Whether this package should be skipped when generating documentation.
+ *
+ * Default is `false`.
+ */
+ @get:Input
+ abstract val suppress: Property<Boolean>
+
+ /**
+ * Set of visibility modifiers that should be documented.
+ *
+ * This can be used if you want to document protected/internal/private declarations within a
+ * specific package, as well as if you want to exclude public declarations and only document internal API.
+ *
+ * Can be configured for a whole source set, see [DokkaSourceSetSpec.documentedVisibilities].
+ *
+ * Default is [VisibilityModifier.PUBLIC].
+ */
+ @get:Input
+ abstract override val documentedVisibilities: SetProperty<VisibilityModifier>
+
+ /**
+ * Whether to document declarations annotated with [Deprecated].
+ *
+ * Can be overridden on source set level by setting [DokkaSourceSetSpec.skipDeprecated].
+ *
+ * Default is `false`.
+ */
+ @get:Input
+ abstract val skipDeprecated: Property<Boolean>
+
+ /**
+ * Whether to emit warnings about visible undocumented declarations, that is declarations from
+ * this package and without KDocs, after they have been filtered by [documentedVisibilities].
+ *
+ *
+ * Can be overridden on source set level by setting [DokkaSourceSetSpec.reportUndocumented].
+ *
+ * Default is `false`.
+ */
+ @get:Input
+ abstract val reportUndocumented: Property<Boolean>
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaParametersKxs.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaParametersKxs.kt
new file mode 100644
index 00000000..df790bcb
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaParametersKxs.kt
@@ -0,0 +1,78 @@
+@file:UseSerializers(
+ FileAsPathStringSerializer::class,
+)
+
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import java.io.File
+import java.nio.file.Paths
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.UseSerializers
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import org.gradle.kotlin.dsl.*
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaModuleDescriptionImpl
+
+
+// Implementations of DokkaConfiguration interfaces that can be serialized to files.
+// Serialization is required because Gradle tasks can only pass data to one-another via files.
+
+
+/**
+ * Any subproject can be merged into a single Dokka Publication. To do this, first it must create
+ * a Dokka Module. A [DokkaModuleDescriptionKxs] describes a config file for the Dokka Module that
+ * describes its content. This config file will be used by any aggregating project to produce
+ * a Dokka Publication with multiple modules.
+ *
+ * Note: this class implements [java.io.Serializable] because it is used as a
+ * [Gradle Property][org.gradle.api.provider.Property], and Gradle must be able to fingerprint
+ * property values classes using Java Serialization.
+ *
+ * All other configuration data classes also implement [java.io.Serializable] via their parent interfaces.
+ */
+@Serializable
+@DokkatooInternalApi
+data class DokkaModuleDescriptionKxs(
+ /** @see DokkaConfiguration.DokkaModuleDescription.name */
+ val name: String,
+ /**
+ * Location of the Dokka Module directory for a subproject.
+ *
+ * @see DokkaConfiguration.DokkaModuleDescription.sourceOutputDirectory
+ */
+ val sourceOutputDirectory: File,
+ /** @see DokkaConfiguration.DokkaModuleDescription.includes */
+ val includes: Set<File>,
+ /** @see [org.gradle.api.Project.getPath] */
+ val modulePath: String,
+) {
+ internal fun convert() =
+ DokkaModuleDescriptionImpl(
+ name = name,
+ relativePathToOutputDirectory = File(modulePath.removePrefix(":").replace(':', '/')),
+ includes = includes,
+ sourceOutputDirectory = sourceOutputDirectory,
+ )
+}
+
+
+/**
+ * Serialize a [File] as an absolute, canonical file path, with
+ * [invariant path separators][invariantSeparatorsPath]
+ */
+private object FileAsPathStringSerializer : KSerializer<File> {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("java.io.File", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): File =
+ Paths.get(decoder.decodeString()).toFile()
+
+ override fun serialize(encoder: Encoder, value: File): Unit =
+ encoder.encodeString(value.invariantSeparatorsPath)
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceLinkSpec.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceLinkSpec.kt
new file mode 100644
index 00000000..c89b8b24
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceLinkSpec.kt
@@ -0,0 +1,106 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import java.io.Serializable
+import java.net.URI
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.Optional
+import org.intellij.lang.annotations.Language
+
+/**
+ * Configuration builder that allows adding a `source` link to each signature
+ * which leads to [remoteUrl] with a specific line number (configurable by setting [remoteLineSuffix]),
+ * letting documentation readers find source code for each declaration.
+ *
+ * Example in Gradle Kotlin DSL:
+ *
+ * ```kotlin
+ * sourceLink {
+ * localDirectory.set(projectDir.resolve("src"))
+ * remoteUrl.set(URI("https://github.com/kotlin/dokka/tree/master/src"))
+ * remoteLineSuffix.set("#L")
+ * }
+ * ```
+ */
+abstract class DokkaSourceLinkSpec
+@DokkatooInternalApi
+constructor() : Serializable {
+
+ /**
+ * Path to the local source directory. The path must be relative to the root of current project.
+ *
+ * This path is used to find relative paths of the source files from which the documentation is built.
+ * These relative paths are then combined with the base url of a source code hosting service specified with
+ * the [remoteUrl] property to create source links for each declaration.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * projectDir.resolve("src")
+ * ```
+ */
+ @get:Internal // changing contents of the directory should not invalidate the task
+ abstract val localDirectory: DirectoryProperty
+
+ /**
+ * The relative path to [localDirectory] from the project directory. Declared as an input to invalidate the task if that path changes.
+ * Should not be used anywhere directly.
+ */
+ @get:Input
+ @DokkatooInternalApi
+ protected val localDirectoryPath: Provider<String>
+ get() = localDirectory.map { it.asFile.invariantSeparatorsPath }
+
+ /**
+ * URL of source code hosting service that can be accessed by documentation readers,
+ * like GitHub, GitLab, Bitbucket, etc. This URL will be used to generate
+ * source code links of declarations.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * java.net.URI("https://github.com/username/projectname/tree/master/src"))
+ * ```
+ */
+ @get:Input
+ abstract val remoteUrl: Property<URI>
+
+ /**
+ * Set the value of [remoteUrl].
+ *
+ * @param[value] will be converted to a [URI]
+ */
+ fun remoteUrl(@Language("http-url-reference") value: String): Unit =
+ remoteUrl.set(URI(value))
+
+ /**
+ * Set the value of [remoteUrl].
+ *
+ * @param[value] will be converted to a [URI]
+ */
+ fun remoteUrl(value: Provider<String>): Unit =
+ remoteUrl.set(value.map(::URI))
+
+ /**
+ * Suffix used to append source code line number to the URL. This will help readers navigate
+ * not only to the file, but to the specific line number of the declaration.
+ *
+ * The number itself will be appended to the specified suffix. For instance,
+ * if this property is set to `#L` and the line number is 10, resulting URL suffix
+ * will be `#L10`
+ *
+ * Suffixes used by popular services:
+ * - GitHub: `#L`
+ * - GitLab: `#L`
+ * - Bitbucket: `#lines-`
+ *
+ * Default is `#L`.
+ */
+ @get:Optional
+ @get:Input
+ abstract val remoteLineSuffix: Property<String>
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceSetIdSpec.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceSetIdSpec.kt
new file mode 100644
index 00000000..0248e387
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceSetIdSpec.kt
@@ -0,0 +1,61 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import java.io.Serializable
+import javax.inject.Inject
+import org.gradle.api.Named
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.kotlin.dsl.*
+
+abstract class DokkaSourceSetIdSpec
+@DokkatooInternalApi
+@Inject
+constructor(
+ /**
+ * Unique identifier of the scope that this source set is placed in.
+ * Each scope provide only unique source set names.
+ *
+ * TODO update this doc - DokkaTask doesn't represent one source set scope anymore
+ *
+ * E.g. One DokkaTask inside the Gradle plugin represents one source set scope, since there cannot be multiple
+ * source sets with the same name. However, a Gradle project will not be a proper scope, since there can be
+ * multiple DokkaTasks that contain source sets with the same name (but different configuration)
+ */
+ @get:Input
+ val scopeId: String,
+
+ @get:Input
+ val sourceSetName: String,
+) : Named, Serializable {
+
+ @Internal
+ override fun getName(): String = "$scopeId/$sourceSetName"
+
+ override fun toString(): String = "DokkaSourceSetIdSpec($scopeId/$sourceSetName)"
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is DokkaSourceSetIdSpec) return false
+
+ if (scopeId != other.scopeId) return false
+ return sourceSetName == other.sourceSetName
+ }
+
+ override fun hashCode(): Int {
+ var result = scopeId.hashCode()
+ result = 31 * result + sourceSetName.hashCode()
+ return result
+ }
+
+ companion object {
+
+ /** Utility for creating a new [DokkaSourceSetIdSpec] instance using [ObjectFactory.newInstance] */
+ @DokkatooInternalApi
+ fun ObjectFactory.dokkaSourceSetIdSpec(
+ scopeId: String,
+ sourceSetName: String,
+ ): DokkaSourceSetIdSpec = newInstance<DokkaSourceSetIdSpec>(scopeId, sourceSetName)
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceSetSpec.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceSetSpec.kt
new file mode 100644
index 00000000..9481885b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/DokkaSourceSetSpec.kt
@@ -0,0 +1,366 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetIdSpec.Companion.dokkaSourceSetIdSpec
+import org.jetbrains.dokka.dokkatoo.internal.*
+import java.io.Serializable
+import javax.inject.Inject
+import org.gradle.api.*
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.provider.*
+import org.gradle.api.tasks.*
+import org.gradle.kotlin.dsl.*
+
+/**
+ * [Source set](https://kotlinlang.org/docs/multiplatform-discover-project.html#source-sets) level configuration.
+ *
+ * Can be configured in the following way with Gradle Kotlin DSL:
+ *
+ * ```kotlin
+ * // build.gradle.kts
+ *
+ * dokkatoo {
+ * dokkatooSourceSets {
+ * // configure individual source set by name
+ * named("customSourceSet") {
+ * suppress.set(true)
+ * }
+ *
+ * // configure all source sets at once
+ * configureEach {
+ * reportUndocumented.set(true)
+ * }
+ * }
+ * }
+ * ```
+ */
+abstract class DokkaSourceSetSpec
+@DokkatooInternalApi
+@Inject
+constructor(
+ private val name: String,
+ private val objects: ObjectFactory,
+) :
+ HasConfigurableVisibilityModifiers,
+ Named,
+ Serializable,
+ ExtensionAware {
+
+ @Internal // will be tracked by sourceSetId
+ override fun getName(): String = name
+
+ /**
+ * An arbitrary string used to group source sets that originate from different Gradle subprojects.
+ * This is primarily used by Kotlin Multiplatform projects, which can have multiple source sets
+ * per subproject.
+ *
+ * The default is set from [DokkatooExtension.sourceSetScopeDefault][org.jetbrains.dokka.dokkatoo.DokkatooExtension.sourceSetScopeDefault]
+ *
+ * It's unlikely that this value needs to be changed.
+ */
+ @get:Internal // will be tracked by sourceSetId
+ abstract val sourceSetScope: Property<String>
+
+ /**
+ * The identifier for this source set, across all Gradle subprojects.
+ *
+ * @see sourceSetScope
+ * @see getName
+ */
+ @get:Input
+ val sourceSetId: Provider<DokkaSourceSetIdSpec>
+ get() = sourceSetScope.map { scope -> objects.dokkaSourceSetIdSpec(scope, getName()) }
+
+ /**
+ * Whether this source set should be skipped when generating documentation.
+ *
+ * Default is `false`.
+ */
+ @get:Input
+ abstract val suppress: Property<Boolean>
+
+ /**
+ * Display name used to refer to the source set.
+ *
+ * The name will be used both externally (for example, source set name visible to documentation readers) and
+ * internally (for example, for logging messages of [reportUndocumented]).
+ *
+ * By default, the value is deduced from information provided by the Kotlin Gradle plugin.
+ */
+ @get:Input
+ abstract val displayName: Property<String>
+
+ /**
+ * List of Markdown files that contain
+ * [module and package documentation](https://kotlinlang.org/docs/reference/dokka-module-and-package-docs.html).
+ *
+ * Contents of specified files will be parsed and embedded into documentation as module and package descriptions.
+ *
+ * Example of such a file:
+ *
+ * ```markdown
+ * # Module kotlin-demo
+ *
+ * The module shows the Dokka usage.
+ *
+ * # Package org.jetbrains.kotlin.demo
+ *
+ * Contains assorted useful stuff.
+ *
+ * ## Level 2 heading
+ *
+ * Text after this heading is also part of documentation for `org.jetbrains.kotlin.demo`
+ *
+ * # Package org.jetbrains.kotlin.demo2
+ *
+ * Useful stuff in another package.
+ * ```
+ */
+ @get:InputFiles
+ @get:Optional
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val includes: ConfigurableFileCollection
+
+ /**
+ * Set of visibility modifiers that should be documented.
+ *
+ * This can be used if you want to document protected/internal/private declarations,
+ * as well as if you want to exclude public declarations and only document internal API.
+ *
+ * Can be configured on per-package basis, see [DokkaPackageOptionsSpec.documentedVisibilities].
+ *
+ * Default is [VisibilityModifier.PUBLIC].
+ */
+ @get:Input
+ abstract override val documentedVisibilities: SetProperty<VisibilityModifier>
+
+ /**
+ * Specifies source sets that current source set depends on.
+ *
+ * Among other things, this information is needed to resolve
+ * [expect/actual](https://kotlinlang.org/docs/multiplatform-connect-to-apis.html) declarations.
+ *
+ * By default, the values are deduced from information provided by the Kotlin Gradle plugin.
+ */
+ @get:Nested
+ val dependentSourceSets: NamedDomainObjectContainer<DokkaSourceSetIdSpec> =
+ extensions.adding("dependentSourceSets", objects.domainObjectContainer())
+
+ /**
+ * Classpath for analysis and interactive samples.
+ *
+ * Useful if some types that come from dependencies are not resolved/picked up automatically.
+ * Property accepts both `.jar` and `.klib` files.
+ *
+ * By default, classpath is deduced from information provided by the Kotlin Gradle plugin.
+ */
+ @get:Classpath
+ @get:Optional
+ abstract val classpath: ConfigurableFileCollection
+
+ /**
+ * Source code roots to be analyzed and documented.
+ * Accepts directories and individual `.kt` / `.java` files.
+ *
+ * By default, source roots are deduced from information provided by the Kotlin Gradle plugin.
+ */
+ @get:InputFiles
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val sourceRoots: ConfigurableFileCollection
+
+ /**
+ * List of directories or files that contain sample functions which are referenced via
+ * [`@sample`](https://kotlinlang.org/docs/kotlin-doc.html#sample-identifier) KDoc tag.
+ */
+ @get:InputFiles
+ @get:Optional
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val samples: ConfigurableFileCollection
+
+ /**
+ * Whether to emit warnings about visible undocumented declarations, that is declarations without KDocs
+ * after they have been filtered by [documentedVisibilities].
+ *
+ * Can be overridden for a specific package by setting [DokkaPackageOptionsSpec.reportUndocumented].
+ *
+ * Default is `false`.
+ */
+ @get:Input
+ abstract val reportUndocumented: Property<Boolean>
+
+ /**
+ * Specifies the location of the project source code on the Web. If provided, Dokka generates
+ * "source" links for each declaration. See [DokkaSourceLinkSpec] for more details.
+ *
+ * Prefer using [sourceLink] action/closure for adding source links.
+ *
+ * @see sourceLink
+ */
+ @get:Nested
+ abstract val sourceLinks: DomainObjectSet<DokkaSourceLinkSpec>
+
+ /**
+ * Allows to customize documentation generation options on a per-package basis.
+ *
+ * @see DokkaPackageOptionsSpec for details
+ */
+ @get:Nested
+ abstract val perPackageOptions: DomainObjectSet<DokkaPackageOptionsSpec>
+
+ /**
+ * Allows linking to Dokka/Javadoc documentation of the project's dependencies.
+ */
+ @get:Nested
+ val externalDocumentationLinks: NamedDomainObjectContainer<DokkaExternalDocumentationLinkSpec> =
+ extensions.adding("externalDocumentationLinks", objects.domainObjectContainer())
+
+ /**
+ * Platform to be used for setting up code analysis and samples.
+ *
+ * The default value is deduced from information provided by the Kotlin Gradle plugin.
+ */
+ @get:Input
+ abstract val analysisPlatform: Property<KotlinPlatform>
+
+ /**
+ * Whether to skip packages that contain no visible declarations after
+ * various filters have been applied.
+ *
+ * For instance, if [skipDeprecated] is set to `true` and your package contains only
+ * deprecated declarations, it will be considered to be empty.
+ *
+ * Default is `true`.
+ */
+ @get:Input
+ abstract val skipEmptyPackages: Property<Boolean>
+
+ /**
+ * Whether to document declarations annotated with [Deprecated].
+ *
+ * Can be overridden on package level by setting [DokkaPackageOptionsSpec.skipDeprecated].
+ *
+ * Default is `false`.
+ */
+ @get:Input
+ abstract val skipDeprecated: Property<Boolean>
+
+ /**
+ * Directories or individual files that should be suppressed, meaning declarations from them
+ * will be not documented.
+ *
+ * Will be concatenated with generated files if [suppressGeneratedFiles] is set to `false`.
+ */
+ @get:InputFiles
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val suppressedFiles: ConfigurableFileCollection
+
+ /**
+ * Whether to document/analyze generated files.
+ *
+ * Generated files are expected to be present under `{project}/{buildDir}/generated` directory.
+ * If set to `true`, it effectively adds all files from that directory to [suppressedFiles], so
+ * you can configure it manually.
+ *
+ * Default is `true`.
+ */
+ @get:Input
+ abstract val suppressGeneratedFiles: Property<Boolean>
+
+ /**
+ * Whether to generate external documentation links that lead to API reference documentation for
+ * Kotlin's standard library when declarations from it are used.
+ *
+ * Default is `true`, meaning links will be generated.
+ *
+ * @see externalDocumentationLinks
+ */
+ @get:Input
+ abstract val enableKotlinStdLibDocumentationLink: Property<Boolean>
+
+ /**
+ * Whether to generate external documentation links to JDK's Javadocs when declarations from it
+ * are used.
+ *
+ * The version of JDK Javadocs is determined by [jdkVersion] property.
+ *
+ * Default is `true`, meaning links will be generated.
+ *
+ * @see externalDocumentationLinks
+ */
+ @get:Input
+ abstract val enableJdkDocumentationLink: Property<Boolean>
+
+ /**
+ * Whether to generate external documentation links for Android SDK API reference when
+ * declarations from it are used.
+ *
+ * Only relevant in Android projects, ignored otherwise.
+ *
+ * Default is `false`, meaning links will not be generated.
+ *
+ * @see externalDocumentationLinks
+ */
+ @get:Input
+ abstract val enableAndroidDocumentationLink: Property<Boolean>
+
+ /**
+ * [Kotlin language version](https://kotlinlang.org/docs/compatibility-modes.html)
+ * used for setting up analysis and [`@sample`](https://kotlinlang.org/docs/kotlin-doc.html#sample-identifier)
+ * environment.
+ *
+ * By default, the latest language version available to Dokka's embedded compiler will be used.
+ */
+ @get:Input
+ @get:Optional
+ abstract val languageVersion: Property<String?>
+
+ /**
+ * [Kotlin API version](https://kotlinlang.org/docs/compatibility-modes.html)
+ * used for setting up analysis and [`@sample`](https://kotlinlang.org/docs/kotlin-doc.html#sample-identifier)
+ * environment.
+ *
+ * By default, it will be deduced from [languageVersion].
+ */
+ @get:Input
+ @get:Optional
+ abstract val apiVersion: Property<String?>
+
+ /**
+ * JDK version to use when generating external documentation links for Java types.
+ *
+ * For instance, if you use [java.util.UUID] from JDK in some public declaration signature,
+ * and this property is set to `8`, Dokka will generate an external documentation link
+ * to [JDK 8 Javadocs](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) for it.
+ *
+ * Default is JDK 8.
+ */
+ @get:Input
+ abstract val jdkVersion: Property<Int>
+
+ /**
+ * Configure and add a new source link to [sourceLinks].
+ *
+ * @see DokkaSourceLinkSpec
+ */
+ fun sourceLink(action: Action<in DokkaSourceLinkSpec>) {
+ sourceLinks.add(
+ objects.newInstance(DokkaSourceLinkSpec::class).also {
+ action.execute(it)
+ }
+ )
+ }
+
+ /**
+ * Action for configuring package options, appending to [perPackageOptions].
+ *
+ * @see DokkaPackageOptionsSpec
+ */
+ fun perPackageOption(action: Action<in DokkaPackageOptionsSpec>) {
+ perPackageOptions.add(
+ objects.newInstance(DokkaPackageOptionsSpec::class).also {
+ action.execute(it)
+ }
+ )
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/HasConfigurableVisibilityModifiers.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/HasConfigurableVisibilityModifiers.kt
new file mode 100644
index 00000000..2ed5ddd9
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/HasConfigurableVisibilityModifiers.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.gradle.api.provider.SetProperty
+import org.gradle.api.tasks.Input
+
+internal interface HasConfigurableVisibilityModifiers {
+
+ @get:Input
+ val documentedVisibilities: SetProperty<VisibilityModifier>
+
+ /** Sets [documentedVisibilities] (overrides any previously set values). */
+ fun documentedVisibilities(vararg visibilities: VisibilityModifier): Unit =
+ documentedVisibilities.set(visibilities.asList())
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/KotlinPlatform.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/KotlinPlatform.kt
new file mode 100644
index 00000000..c950fbbe
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/KotlinPlatform.kt
@@ -0,0 +1,54 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.Platform
+
+
+/**
+ * The Kotlin
+ *
+ * @see org.jetbrains.dokka.Platform
+ * @param[displayName] The display name, eventually used in the rendered Dokka publication.
+ */
+enum class KotlinPlatform(
+ internal val displayName: String
+) {
+ AndroidJVM("androidJvm"),
+ Common("common"),
+ JS("js"),
+ JVM("jvm"),
+ Native("native"),
+ WASM("wasm"),
+ ;
+
+ companion object {
+ internal val values: Set<KotlinPlatform> = values().toSet()
+
+ val DEFAULT: KotlinPlatform = JVM
+
+ fun fromString(key: String): KotlinPlatform {
+ val keyMatch = values.firstOrNull {
+ it.name.equals(key, ignoreCase = true) || it.displayName.equals(key, ignoreCase = true)
+ }
+ if (keyMatch != null) {
+ return keyMatch
+ }
+
+ return when (key.lowercase()) {
+ "android" -> AndroidJVM
+ "metadata" -> Common
+ else -> error("Unrecognized platform: $key")
+ }
+ }
+
+ // Not defined as a property to try and minimize the dependency on Dokka Core types
+ internal val KotlinPlatform.dokkaType: Platform
+ get() =
+ when (this) {
+ AndroidJVM, JVM -> Platform.jvm
+ JS -> Platform.js
+ WASM -> Platform.wasm
+ Native -> Platform.native
+ Common -> Platform.common
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/VisibilityModifier.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/VisibilityModifier.kt
new file mode 100644
index 00000000..de61f97b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/VisibilityModifier.kt
@@ -0,0 +1,42 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.DokkaConfiguration
+
+/**
+ * Denotes the
+ * [visibility modifier](https://kotlinlang.org/docs/visibility-modifiers.html)
+ * of a source code elements.
+ *
+ * @see org.jetbrains.dokka.DokkaConfiguration.Visibility
+ */
+enum class VisibilityModifier {
+ /** `public` modifier for Java, default visibility for Kotlin */
+ PUBLIC,
+
+ /** `private` modifier for both Kotlin and Java */
+ PRIVATE,
+
+ /** `protected` modifier for both Kotlin and Java */
+ PROTECTED,
+
+ /** Kotlin-specific `internal` modifier */
+ INTERNAL,
+
+ /** Java-specific package-private visibility (no modifier) */
+ PACKAGE,
+ ;
+
+ companion object {
+ internal val entries: Set<VisibilityModifier> = values().toSet()
+
+ // Not defined as a property to try and minimize the dependency on Dokka Core types
+ internal val VisibilityModifier.dokkaType: DokkaConfiguration.Visibility
+ get() = when (this) {
+ PUBLIC -> DokkaConfiguration.Visibility.PUBLIC
+ PRIVATE -> DokkaConfiguration.Visibility.PRIVATE
+ PROTECTED -> DokkaConfiguration.Visibility.PROTECTED
+ INTERNAL -> DokkaConfiguration.Visibility.INTERNAL
+ PACKAGE -> DokkaConfiguration.Visibility.PACKAGE
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaModuleDescriptionBuilder.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaModuleDescriptionBuilder.kt
new file mode 100644
index 00000000..c6ff8891
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaModuleDescriptionBuilder.kt
@@ -0,0 +1,33 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters.builders
+
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaModuleDescriptionSpec
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import java.io.File
+import org.jetbrains.dokka.DokkaModuleDescriptionImpl
+import org.jetbrains.dokka.DokkaSourceSetImpl
+
+/**
+ * Convert the Gradle-focused [DokkaModuleDescriptionSpec] into a [DokkaSourceSetImpl] instance,
+ * which will be passed to Dokka Generator.
+ *
+ * The conversion is defined in a separate class to try and prevent classes from Dokka Generator
+ * leaking into the public API.
+ */
+// to be used to fix https://github.com/adamko-dev/dokkatoo/issues/67
+@DokkatooInternalApi
+internal object DokkaModuleDescriptionBuilder {
+
+ fun build(
+ spec: DokkaModuleDescriptionSpec,
+ includes: Set<File>,
+ sourceOutputDirectory: File,
+ ): DokkaModuleDescriptionImpl =
+ DokkaModuleDescriptionImpl(
+ name = spec.name,
+ relativePathToOutputDirectory = File(
+ spec.projectPath.get().removePrefix(":").replace(':', '/')
+ ),
+ includes = includes,
+ sourceOutputDirectory = sourceOutputDirectory,
+ )
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaParametersBuilder.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaParametersBuilder.kt
new file mode 100644
index 00000000..d39969a2
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaParametersBuilder.kt
@@ -0,0 +1,77 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters.builders
+
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaGeneratorParametersSpec
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaModuleDescriptionKxs
+import org.jetbrains.dokka.dokkatoo.dokka.plugins.DokkaPluginParametersBaseSpec
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.mapNotNullToSet
+import java.io.File
+import org.gradle.api.logging.Logging
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.DokkaSourceSetImpl
+import org.jetbrains.dokka.PluginConfigurationImpl
+
+/**
+ * Convert the Gradle-focused [DokkaGeneratorParametersSpec] into a [DokkaSourceSetImpl] instance,
+ * which will be passed to Dokka Generator.
+ *
+ * The conversion is defined in a separate class to try and prevent classes from Dokka Generator
+ * leaking into the public API.
+ */
+@DokkatooInternalApi
+internal object DokkaParametersBuilder {
+
+ fun build(
+ spec: DokkaGeneratorParametersSpec,
+ delayTemplateSubstitution: Boolean,
+ modules: List<DokkaModuleDescriptionKxs>,
+ outputDirectory: File,
+ cacheDirectory: File? = null,
+ ): DokkaConfiguration {
+ val moduleName = spec.moduleName.get()
+ val moduleVersion = spec.moduleVersion.orNull?.takeIf { it != "unspecified" }
+ val offlineMode = spec.offlineMode.get()
+ val sourceSets = DokkaSourceSetBuilder.buildAll(spec.dokkaSourceSets)
+ val failOnWarning = spec.failOnWarning.get()
+ val suppressObviousFunctions = spec.suppressObviousFunctions.get()
+ val suppressInheritedMembers = spec.suppressInheritedMembers.get()
+ val finalizeCoroutines = spec.finalizeCoroutines.get()
+ val pluginsConfiguration = spec.pluginsConfiguration.toSet()
+
+ val pluginsClasspath = spec.pluginsClasspath.files.toList()
+ val includes = spec.includes.files
+
+ return DokkaConfigurationImpl(
+ moduleName = moduleName,
+ moduleVersion = moduleVersion,
+ outputDir = outputDirectory,
+ cacheRoot = cacheDirectory,
+ offlineMode = offlineMode,
+ sourceSets = sourceSets,
+ pluginsClasspath = pluginsClasspath,
+ pluginsConfiguration = pluginsConfiguration.map(::build),
+ modules = modules.map(DokkaModuleDescriptionKxs::convert),
+// modules = modules.map {
+// it.convert(
+// moduleDescriptionFiles.get(it.name)
+// ?: error("missing module description files for ${it.name}")
+// )
+// },
+ failOnWarning = failOnWarning,
+ delayTemplateSubstitution = delayTemplateSubstitution,
+ suppressObviousFunctions = suppressObviousFunctions,
+ includes = includes,
+ suppressInheritedMembers = suppressInheritedMembers,
+ finalizeCoroutines = finalizeCoroutines,
+ )
+ }
+
+ private fun build(spec: DokkaPluginParametersBaseSpec): PluginConfigurationImpl {
+ return PluginConfigurationImpl(
+ fqPluginName = spec.pluginFqn,
+ serializationFormat = DokkaConfiguration.SerializationFormat.JSON,
+ values = spec.jsonEncode(),
+ )
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaSourceSetBuilder.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaSourceSetBuilder.kt
new file mode 100644
index 00000000..77935d8c
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/parameters/builders/DokkaSourceSetBuilder.kt
@@ -0,0 +1,112 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters.builders
+
+
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.*
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.KotlinPlatform.Companion.dokkaType
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.VisibilityModifier.Companion.dokkaType
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.mapNotNullToSet
+import org.jetbrains.dokka.dokkatoo.internal.mapToSet
+import org.gradle.api.logging.Logging
+import org.jetbrains.dokka.*
+
+
+/**
+ * Convert the Gradle-focused [DokkaSourceSetSpec] into a [DokkaSourceSetImpl] instance, which
+ * will be passed to Dokka Generator.
+ *
+ * The conversion is defined in a separate class to try and prevent classes from Dokka Generator
+ * leaking into the public API.
+ */
+@DokkatooInternalApi
+internal object DokkaSourceSetBuilder {
+
+ private val logger = Logging.getLogger(DokkaParametersBuilder::class.java)
+
+ fun buildAll(sourceSets: Set<DokkaSourceSetSpec>): List<DokkaSourceSetImpl> {
+
+ val suppressedSourceSetIds = sourceSets.mapNotNullToSet {
+ val suppressed = it.suppress.get()
+ val sourceSetId = it.sourceSetId.get()
+ if (suppressed) {
+ logger.info("Dokka source set $sourceSetId is suppressed")
+ sourceSetId
+ } else {
+ logger.info("Dokka source set $sourceSetId isn't suppressed")
+ null
+ }
+ }
+
+ val enabledSourceSets = sourceSets.filter { it.sourceSetId.get() !in suppressedSourceSetIds }
+
+ return enabledSourceSets.map { build(it, suppressedSourceSetIds) }
+ }
+
+ private fun build(
+ spec: DokkaSourceSetSpec,
+ suppressedSourceSetIds: Set<DokkaSourceSetIdSpec>,
+ ): DokkaSourceSetImpl {
+
+ val dependentSourceSets =
+ (spec.dependentSourceSets subtract suppressedSourceSetIds).mapToSet(::build)
+
+ return DokkaSourceSetImpl(
+ // properties
+ analysisPlatform = spec.analysisPlatform.get().dokkaType,
+ apiVersion = spec.apiVersion.orNull,
+ dependentSourceSets = dependentSourceSets,
+ displayName = spec.displayName.get(),
+ documentedVisibilities = spec.documentedVisibilities.get().mapToSet { it.dokkaType },
+ externalDocumentationLinks = spec.externalDocumentationLinks.mapNotNullToSet(::build),
+ jdkVersion = spec.jdkVersion.get(),
+ languageVersion = spec.languageVersion.orNull,
+ noJdkLink = !spec.enableJdkDocumentationLink.get(),
+ noStdlibLink = !spec.enableKotlinStdLibDocumentationLink.get(),
+ perPackageOptions = spec.perPackageOptions.map(::build),
+ reportUndocumented = spec.reportUndocumented.get(),
+ skipDeprecated = spec.skipDeprecated.get(),
+ skipEmptyPackages = spec.skipEmptyPackages.get(),
+ sourceLinks = spec.sourceLinks.mapToSet { build(it) },
+ sourceSetID = build(spec.sourceSetId.get()),
+
+ // files
+ classpath = spec.classpath.files.toList(),
+ includes = spec.includes.files,
+ samples = spec.samples.files,
+ sourceRoots = spec.sourceRoots.files,
+ suppressedFiles = spec.suppressedFiles.files,
+ )
+ }
+
+ private fun build(spec: DokkaExternalDocumentationLinkSpec): ExternalDocumentationLinkImpl? {
+ if (!spec.enabled.getOrElse(true)) return null
+
+ return ExternalDocumentationLinkImpl(
+ url = spec.url.get().toURL(),
+ packageListUrl = spec.packageListUrl.get().toURL(),
+ )
+ }
+
+ private fun build(spec: DokkaPackageOptionsSpec): PackageOptionsImpl =
+ PackageOptionsImpl(
+ matchingRegex = spec.matchingRegex.get(),
+ documentedVisibilities = spec.documentedVisibilities.get().mapToSet { it.dokkaType },
+ reportUndocumented = spec.reportUndocumented.get(),
+ skipDeprecated = spec.skipDeprecated.get(),
+ suppress = spec.suppress.get(),
+ includeNonPublic = DokkaDefaults.includeNonPublic,
+ )
+
+ private fun build(spec: DokkaSourceSetIdSpec): DokkaSourceSetID =
+ DokkaSourceSetID(
+ scopeId = spec.scopeId,
+ sourceSetName = spec.sourceSetName
+ )
+
+ private fun build(spec: DokkaSourceLinkSpec): SourceLinkDefinitionImpl =
+ SourceLinkDefinitionImpl(
+ localDirectory = spec.localDirectory.asFile.get().invariantSeparatorsPath,
+ remoteUrl = spec.remoteUrl.get().toURL(),
+ remoteLineSuffix = spec.remoteLineSuffix.orNull,
+ )
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaHtmlPluginParameters.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaHtmlPluginParameters.kt
new file mode 100644
index 00000000..a3252b51
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaHtmlPluginParameters.kt
@@ -0,0 +1,129 @@
+package org.jetbrains.dokka.dokkatoo.dokka.plugins
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.addAll
+import org.jetbrains.dokka.dokkatoo.internal.putIfNotNull
+import javax.inject.Inject
+import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.putJsonArray
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.*
+import org.gradle.api.tasks.PathSensitivity.RELATIVE
+
+
+/**
+ * Configuration for Dokka's base HTML format
+ *
+ * [More information is available in the Dokka docs.](https://kotlinlang.org/docs/dokka-html.html#configuration)
+ */
+abstract class DokkaHtmlPluginParameters
+@DokkatooInternalApi
+@Inject
+constructor(
+ name: String
+) : DokkaPluginParametersBaseSpec(
+ name,
+ DOKKA_HTML_PLUGIN_FQN,
+) {
+
+ /**
+ * List of paths for image assets to be bundled with documentation.
+ * The image assets can have any file extension.
+ *
+ * For more information, see
+ * [Customizing assets](https://kotlinlang.org/docs/dokka-html.html#customize-assets).
+ *
+ * Be aware that files will be copied as-is to a specific directory inside the assembled Dokka
+ * publication. This means that any relative paths must be written in such a way that they will
+ * work _after_ the files are moved into the publication.
+ *
+ * It's best to try and mirror Dokka's directory structure in the source files, which can help
+ * IDE inspections.
+ */
+ @get:InputFiles
+ @get:PathSensitive(RELATIVE)
+ @get:Optional
+ abstract val customAssets: ConfigurableFileCollection
+
+ /**
+ * List of paths for `.css` stylesheets to be bundled with documentation and used for rendering.
+ *
+ * For more information, see
+ * [Customizing assets](https://kotlinlang.org/docs/dokka-html.html#customize-assets).
+ *
+ * Be aware that files will be copied as-is to a specific directory inside the assembled Dokka
+ * publication. This means that any relative paths must be written in such a way that they will
+ * work _after_ the files are moved into the publication.
+ *
+ * It's best to try and mirror Dokka's directory structure in the source files, which can help
+ * IDE inspections.
+ */
+ @get:InputFiles
+ @get:PathSensitive(RELATIVE)
+ @get:Optional
+ abstract val customStyleSheets: ConfigurableFileCollection
+
+ /**
+ * This is a boolean option. If set to `true`, Dokka renders properties/functions and inherited
+ * properties/inherited functions separately.
+ *
+ * This is disabled by default.
+ */
+ @get:Input
+ @get:Optional
+ abstract val separateInheritedMembers: Property<Boolean>
+
+ /**
+ * This is a boolean option. If set to `true`, Dokka merges declarations that are not declared as
+ * [expect/actual](https://kotlinlang.org/docs/multiplatform-connect-to-apis.html), but have the
+ * same fully qualified name. This can be useful for legacy codebases.
+ *
+ * This is disabled by default.
+ */
+ @get:Input
+ @get:Optional
+ abstract val mergeImplicitExpectActualDeclarations: Property<Boolean>
+
+ /** The text displayed in the footer. */
+ @get:Input
+ @get:Optional
+ abstract val footerMessage: Property<String>
+
+ /**
+ * Path to the directory containing custom HTML templates.
+ *
+ * For more information, see [Templates](https://kotlinlang.org/docs/dokka-html.html#templates).
+ */
+ @get:InputDirectory
+ @get:PathSensitive(RELATIVE)
+ @get:Optional
+ abstract val templatesDir: DirectoryProperty
+
+ override fun jsonEncode(): String =
+ buildJsonObject {
+ putJsonArray("customAssets") {
+ addAll(customAssets.files)
+ }
+ putJsonArray("customStyleSheets") {
+ addAll(customStyleSheets.files)
+ }
+ putIfNotNull("separateInheritedMembers", separateInheritedMembers.orNull)
+ putIfNotNull(
+ "mergeImplicitExpectActualDeclarations",
+ mergeImplicitExpectActualDeclarations.orNull
+ )
+ putIfNotNull("footerMessage", footerMessage.orNull)
+ putIfNotNull("footerMessage", footerMessage.orNull)
+ putIfNotNull(
+ "templatesDir",
+ templatesDir.orNull?.asFile?.canonicalFile?.invariantSeparatorsPath
+ )
+ }.toString()
+
+ companion object {
+ const val DOKKA_HTML_PARAMETERS_NAME = "html"
+ const val DOKKA_HTML_PLUGIN_FQN = "org.jetbrains.dokka.base.DokkaBase"
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaPluginParametersBaseSpec.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaPluginParametersBaseSpec.kt
new file mode 100644
index 00000000..486bb80e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaPluginParametersBaseSpec.kt
@@ -0,0 +1,32 @@
+package org.jetbrains.dokka.dokkatoo.dokka.plugins
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import java.io.Serializable
+import javax.inject.Inject
+import org.gradle.api.Named
+import org.gradle.api.tasks.Input
+
+/**
+ * Base class for defining Dokka Plugin configuration.
+ *
+ * This class should not be instantiated directly. Instead, use a subclass, or create plugin
+ * parameters dynamically using [DokkaPluginParametersBuilder].
+ *
+ * [More information about Dokka Plugins is available in the Dokka docs.](https://kotlinlang.org/docs/dokka-plugins.html)
+ *
+ * @param[pluginFqn] Fully qualified classname of the Dokka Plugin
+ */
+abstract class DokkaPluginParametersBaseSpec
+@DokkatooInternalApi
+@Inject
+constructor(
+ private val name: String,
+ @get:Input
+ open val pluginFqn: String,
+) : Serializable, Named {
+
+ abstract fun jsonEncode(): String // to be implemented by subclasses
+
+ @Input
+ override fun getName(): String = name
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaPluginParametersBuilder.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaPluginParametersBuilder.kt
new file mode 100644
index 00000000..a29b94c2
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaPluginParametersBuilder.kt
@@ -0,0 +1,232 @@
+package org.jetbrains.dokka.dokkatoo.dokka.plugins
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkaPluginParametersContainer
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import java.io.File
+import javax.inject.Inject
+import kotlinx.serialization.json.*
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.MapProperty
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.*
+import org.gradle.api.tasks.PathSensitivity.RELATIVE
+import org.gradle.kotlin.dsl.*
+
+
+/**
+ * Dynamically create some configuration to control the behaviour of a Dokka Plugin.
+ *
+ * @param[pluginFqn] The fully-qualified name of a Dokka Plugin. For example, the FQN of the
+ * [Dokka Base plugin](https://github.com/Kotlin/dokka/tree/master/plugins/base#readme)
+ * is `org.jetbrains.dokka.base.DokkaBase`
+ */
+fun DokkaPluginParametersContainer.pluginParameters(
+ pluginFqn: String,
+ configure: DokkaPluginParametersBuilder.() -> Unit
+) {
+ containerWithType(DokkaPluginParametersBuilder::class)
+ .maybeCreate(pluginFqn)
+ .configure()
+}
+
+
+/**
+ * Dynamically create some configuration to control the behaviour of a Dokka Plugin.
+ *
+ * This type of builder is necessary to respect
+ * [Gradle incremental build annotations](https://docs.gradle.org/current/userguide/incremental_build.html#sec:task_input_output_annotations).
+ *
+ * @param[pluginFqn] The fully-qualified name of a Dokka Plugin. For example, the Dokka Base plugin's FQN is `org.jetbrains.dokka.base.DokkaBase`
+ */
+abstract class DokkaPluginParametersBuilder
+@Inject
+@DokkatooInternalApi
+constructor(
+ name: String,
+ @get:Input
+ override val pluginFqn: String,
+
+ @Internal
+ internal val objects: ObjectFactory,
+) : DokkaPluginParametersBaseSpec(name, pluginFqn) {
+
+ @get:Nested
+ internal val properties = PluginConfigValue.Properties(objects.mapProperty())
+
+ @Internal
+ override fun jsonEncode(): String = properties.convertToJson().toString()
+
+ companion object {
+ private fun PluginConfigValue.convertToJson(): JsonElement =
+ when (this) {
+ is PluginConfigValue.DirectoryValue -> directory.asFile.orNull.convertToJson()
+ is PluginConfigValue.FileValue -> file.asFile.orNull.convertToJson()
+ is PluginConfigValue.FilesValue -> JsonArray(files.files.map { it.convertToJson() })
+
+ is PluginConfigValue.BooleanValue -> JsonPrimitive(boolean)
+ is PluginConfigValue.NumberValue -> JsonPrimitive(number)
+ is PluginConfigValue.StringValue -> JsonPrimitive(string)
+
+ is PluginConfigValue.Properties ->
+ JsonObject(values.get().mapValues { (_, value) -> value.convertToJson() })
+
+ is PluginConfigValue.Values ->
+ JsonArray(values.get().map { it.convertToJson() })
+ }
+
+ /** Creates a [JsonPrimitive] from the given [File]. */
+ private fun File?.convertToJson(): JsonPrimitive =
+ JsonPrimitive(this?.canonicalFile?.invariantSeparatorsPath)
+ }
+}
+
+
+fun DokkaPluginParametersBuilder.files(
+ propertyName: String,
+ filesConfig: ConfigurableFileCollection.() -> Unit
+) {
+ val files = objects.fileCollection()
+ files.filesConfig()
+ properties.values.put(propertyName, PluginConfigValue.FilesValue(files))
+}
+
+//region Primitive Properties
+fun DokkaPluginParametersBuilder.property(propertyName: String, value: String) {
+ properties.values.put(propertyName, PluginConfigValue(value))
+}
+
+fun DokkaPluginParametersBuilder.property(propertyName: String, value: Number) {
+ properties.values.put(propertyName, PluginConfigValue(value))
+}
+
+fun DokkaPluginParametersBuilder.property(propertyName: String, value: Boolean) {
+ properties.values.put(propertyName, PluginConfigValue(value))
+}
+
+@JvmName("stringProperty")
+fun DokkaPluginParametersBuilder.property(propertyName: String, provider: Provider<String>) {
+ properties.values.put(propertyName, provider.map { PluginConfigValue(it) })
+}
+
+@JvmName("numberProperty")
+fun DokkaPluginParametersBuilder.property(propertyName: String, provider: Provider<Number>) {
+ properties.values.put(propertyName, provider.map { PluginConfigValue(it) })
+}
+
+@JvmName("booleanProperty")
+fun DokkaPluginParametersBuilder.property(
+ propertyName: String,
+ provider: Provider<Boolean>
+) {
+ properties.values.put(propertyName, provider.map { PluginConfigValue(it) })
+}
+//endregion
+
+
+//region List Properties
+fun DokkaPluginParametersBuilder.properties(
+ propertyName: String,
+ build: PluginConfigValue.Values.() -> Unit
+) {
+ val values = PluginConfigValue.Values(objects.listProperty())
+ values.build()
+ properties.values.put(propertyName, values)
+}
+
+fun PluginConfigValue.Values.add(value: String) =
+ values.add(PluginConfigValue(value))
+
+fun PluginConfigValue.Values.add(value: Number) =
+ values.add(PluginConfigValue(value))
+
+fun PluginConfigValue.Values.add(value: Boolean) =
+ values.add(PluginConfigValue(value))
+
+@JvmName("addString")
+fun PluginConfigValue.Values.add(value: Provider<String>) =
+ values.add(PluginConfigValue(value))
+
+@JvmName("addNumber")
+fun PluginConfigValue.Values.add(value: Provider<Number>) =
+ values.add(PluginConfigValue(value))
+
+@JvmName("addBoolean")
+fun PluginConfigValue.Values.add(value: Provider<Boolean>) =
+ values.add(PluginConfigValue(value))
+//endregion
+
+
+sealed interface PluginConfigValue {
+
+ /** An input file */
+ class FileValue(
+ @InputFile
+ @PathSensitive(RELATIVE)
+ val file: RegularFileProperty,
+ ) : PluginConfigValue
+
+ /** Input files and directories */
+ class FilesValue(
+ @InputFiles
+ @PathSensitive(RELATIVE)
+ val files: ConfigurableFileCollection,
+ ) : PluginConfigValue
+
+ /** An input directory */
+ class DirectoryValue(
+ @InputDirectory
+ @PathSensitive(RELATIVE)
+ val directory: DirectoryProperty,
+ ) : PluginConfigValue
+
+ /** Key-value properties. Analogous to a [JsonObject]. */
+ class Properties(
+ @Nested
+ val values: MapProperty<String, PluginConfigValue>
+ ) : PluginConfigValue
+
+ /** Multiple values. Analogous to a [JsonArray]. */
+ class Values(
+ @Nested
+ val values: ListProperty<PluginConfigValue>
+ ) : PluginConfigValue
+
+ sealed interface Primitive : PluginConfigValue
+
+ /** A basic [String] value */
+ class StringValue(@Input val string: String) : Primitive
+
+ /** A basic [Number] value */
+ class NumberValue(@Input val number: Number) : Primitive
+
+ /** A basic [Boolean] value */
+ class BooleanValue(@Input val boolean: Boolean) : Primitive
+}
+
+fun PluginConfigValue(value: String) =
+ PluginConfigValue.StringValue(value)
+
+fun PluginConfigValue(value: Number) =
+ PluginConfigValue.NumberValue(value)
+
+fun PluginConfigValue(value: Boolean) =
+ PluginConfigValue.BooleanValue(value)
+
+@Suppress("FunctionName")
+@JvmName("PluginConfigStringValue")
+fun PluginConfigValue(value: Provider<String>): Provider<PluginConfigValue.StringValue> =
+ value.map { PluginConfigValue(it) }
+
+@Suppress("FunctionName")
+@JvmName("PluginConfigNumberValue")
+fun PluginConfigValue(value: Provider<Number>): Provider<PluginConfigValue.NumberValue> =
+ value.map { PluginConfigValue(it) }
+
+@Suppress("FunctionName")
+@JvmName("PluginConfigBooleanValue")
+fun PluginConfigValue(value: Provider<Boolean>): Provider<PluginConfigValue.BooleanValue> =
+ value.map { PluginConfigValue(it) }
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaVersioningPluginParameters.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaVersioningPluginParameters.kt
new file mode 100644
index 00000000..1a4d75f2
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/dokka/plugins/DokkaVersioningPluginParameters.kt
@@ -0,0 +1,101 @@
+package org.jetbrains.dokka.dokkatoo.dokka.plugins
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.addAll
+import org.jetbrains.dokka.dokkatoo.internal.addAllIfNotNull
+import org.jetbrains.dokka.dokkatoo.internal.putIfNotNull
+import javax.inject.Inject
+import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.putJsonArray
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.*
+import org.gradle.api.tasks.PathSensitivity.RELATIVE
+
+
+/**
+ * Configuration for
+ * [Dokka's Versioning plugin](https://github.com/Kotlin/dokka/tree/master/plugins/versioning#readme).
+ *
+ * The versioning plugin provides the ability to host documentation for multiple versions of your
+ * library/application with seamless switching between them. This, in turn, provides a better
+ * experience for your users.
+ *
+ * Note: The versioning plugin only works with Dokka's HTML format.
+ */
+abstract class DokkaVersioningPluginParameters
+@DokkatooInternalApi
+@Inject
+constructor(
+ name: String,
+) : DokkaPluginParametersBaseSpec(
+ name,
+ DOKKA_VERSIONING_PLUGIN_FQN,
+) {
+
+ /**
+ * The version of your application/library that documentation is going to be generated for.
+ * This will be the version shown in the dropdown menu.
+ */
+ @get:Input
+ @get:Optional
+ abstract val version: Property<String>
+
+ /**
+ * An optional list of strings that represents the order that versions should appear in the
+ * dropdown menu.
+ *
+ * Must match [version] string exactly. The first item in the list is at the top of the dropdown.
+ */
+ @get:Input
+ @get:Optional
+ abstract val versionsOrdering: ListProperty<String>
+
+ /**
+ * An optional path to a parent folder that contains other documentation versions.
+ * It requires a specific directory structure.
+ *
+ * For more information, see
+ * [Directory structure](https://github.com/Kotlin/dokka/blob/master/plugins/versioning/README.md#directory-structure).
+ */
+ @get:InputDirectory
+ @get:PathSensitive(RELATIVE)
+ @get:Optional
+ abstract val olderVersionsDir: DirectoryProperty
+
+ /**
+ * An optional list of paths to other documentation versions. It must point to Dokka's outputs
+ * directly. This is useful if different versions can't all be in the same directory.
+ */
+ @get:InputFiles
+ @get:PathSensitive(RELATIVE)
+ @get:Optional
+ abstract val olderVersions: ConfigurableFileCollection
+
+ /**
+ * An optional boolean value indicating whether to render the navigation dropdown on all pages.
+ *
+ * Set to `true` by default.
+ */
+ @get:Input
+ @get:Optional
+ abstract val renderVersionsNavigationOnAllPages: Property<Boolean>
+
+ override fun jsonEncode(): String =
+ buildJsonObject {
+ putIfNotNull("version", version.orNull)
+ putJsonArray("versionsOrdering") { addAllIfNotNull(versionsOrdering.orNull) }
+ putIfNotNull("olderVersionsDir", olderVersionsDir.orNull?.asFile)
+ putJsonArray("olderVersions") {
+ addAll(olderVersions.files)
+ }
+ putIfNotNull("renderVersionsNavigationOnAllPages", renderVersionsNavigationOnAllPages.orNull)
+ }.toString()
+
+ companion object {
+ const val DOKKA_VERSIONING_PLUGIN_PARAMETERS_NAME = "versioning"
+ const val DOKKA_VERSIONING_PLUGIN_FQN = "org.jetbrains.dokka.versioning.VersioningPlugin"
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatDependencyContainers.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatDependencyContainers.kt
new file mode 100644
index 00000000..08eece77
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatDependencyContainers.kt
@@ -0,0 +1,152 @@
+package org.jetbrains.dokka.dokkatoo.formats
+
+import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin
+import org.jetbrains.dokka.dokkatoo.distributions.DokkatooConfigurationAttributes
+import org.jetbrains.dokka.dokkatoo.distributions.DokkatooConfigurationAttributes.Companion.DOKKATOO_BASE_ATTRIBUTE
+import org.jetbrains.dokka.dokkatoo.distributions.DokkatooConfigurationAttributes.Companion.DOKKATOO_CATEGORY_ATTRIBUTE
+import org.jetbrains.dokka.dokkatoo.distributions.DokkatooConfigurationAttributes.Companion.DOKKA_FORMAT_ATTRIBUTE
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.asConsumer
+import org.jetbrains.dokka.dokkatoo.internal.asProvider
+import org.gradle.api.NamedDomainObjectProvider
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.attributes.AttributeContainer
+import org.gradle.api.attributes.Bundling.BUNDLING_ATTRIBUTE
+import org.gradle.api.attributes.Bundling.EXTERNAL
+import org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE
+import org.gradle.api.attributes.Category.LIBRARY
+import org.gradle.api.attributes.LibraryElements.JAR
+import org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE
+import org.gradle.api.attributes.Usage.JAVA_RUNTIME
+import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE
+import org.gradle.api.attributes.java.TargetJvmEnvironment.STANDARD_JVM
+import org.gradle.api.attributes.java.TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE
+import org.gradle.api.model.ObjectFactory
+import org.gradle.kotlin.dsl.*
+
+/**
+ * The Dokka-specific Gradle [Configuration]s used to produce and consume files from external sources
+ * (example: Maven Central), or between subprojects.
+ *
+ * (Be careful of the confusing names: Gradle [Configuration]s are used to transfer files,
+ * [DokkaConfiguration][org.jetbrains.dokka.DokkaConfiguration]
+ * is used to configure Dokka behaviour.)
+ */
+@DokkatooInternalApi
+class DokkatooFormatDependencyContainers(
+ private val formatName: String,
+ dokkatooConsumer: NamedDomainObjectProvider<Configuration>,
+ project: Project,
+) {
+
+ private val objects: ObjectFactory = project.objects
+
+ private val dependencyContainerNames = DokkatooBasePlugin.DependencyContainerNames(formatName)
+
+ private val dokkatooAttributes: DokkatooConfigurationAttributes = objects.newInstance()
+
+ private fun AttributeContainer.dokkaCategory(category: DokkatooConfigurationAttributes.DokkatooCategoryAttribute) {
+ attribute(DOKKATOO_BASE_ATTRIBUTE, dokkatooAttributes.dokkatooBaseUsage)
+ attribute(DOKKA_FORMAT_ATTRIBUTE, objects.named(formatName))
+ attribute(DOKKATOO_CATEGORY_ATTRIBUTE, category)
+ }
+
+ private fun AttributeContainer.jvmJar() {
+ attribute(USAGE_ATTRIBUTE, objects.named(JAVA_RUNTIME))
+ attribute(CATEGORY_ATTRIBUTE, objects.named(LIBRARY))
+ attribute(BUNDLING_ATTRIBUTE, objects.named(EXTERNAL))
+ attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objects.named(STANDARD_JVM))
+ attribute(LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(JAR))
+ }
+
+ //<editor-fold desc="Dokka Module files">
+ /** Fetch Dokka Module files from other subprojects */
+ val dokkaModuleConsumer: NamedDomainObjectProvider<Configuration> =
+ project.configurations.register(dependencyContainerNames.dokkatooModuleFilesConsumer) {
+ description = "Fetch Dokka Module files for $formatName from other subprojects"
+ asConsumer()
+ extendsFrom(dokkatooConsumer.get())
+ attributes {
+ dokkaCategory(dokkatooAttributes.dokkaModuleFiles)
+ }
+ }
+ /** Provide Dokka Module files to other subprojects */
+ val dokkaModuleOutgoing: NamedDomainObjectProvider<Configuration> =
+ project.configurations.register(dependencyContainerNames.dokkatooModuleFilesProvider) {
+ description = "Provide Dokka Module files for $formatName to other subprojects"
+ asProvider()
+ // extend from dokkaConfigurationsConsumer, so Dokka Module Configs propagate api() style
+ extendsFrom(dokkaModuleConsumer.get())
+ attributes {
+ dokkaCategory(dokkatooAttributes.dokkaModuleFiles)
+ }
+ }
+ //</editor-fold>
+
+ //<editor-fold desc="Dokka Generator Plugins">
+ /**
+ * Dokka plugins.
+ *
+ * Users can add plugins to this dependency.
+ *
+ * Should not contain runtime dependencies.
+ */
+ val dokkaPluginsClasspath: NamedDomainObjectProvider<Configuration> =
+ project.configurations.register(dependencyContainerNames.dokkaPluginsClasspath) {
+ description = "Dokka Plugins classpath for $formatName"
+ asConsumer()
+ attributes {
+ jvmJar()
+ dokkaCategory(dokkatooAttributes.dokkaPluginsClasspath)
+ }
+ }
+
+ /**
+ * Dokka Plugins, without transitive dependencies.
+ *
+ * It extends [dokkaPluginsClasspath], so do not add dependencies to this configuration -
+ * the dependencies are computed automatically.
+ */
+ val dokkaPluginsIntransitiveClasspath: NamedDomainObjectProvider<Configuration> =
+ project.configurations.register(dependencyContainerNames.dokkaPluginsIntransitiveClasspath) {
+ description =
+ "Dokka Plugins classpath for $formatName - for internal use. Fetch only the plugins (no transitive dependencies) for use in the Dokka JSON Configuration."
+ asConsumer()
+ extendsFrom(dokkaPluginsClasspath.get())
+ isTransitive = false
+ attributes {
+ jvmJar()
+ dokkaCategory(dokkatooAttributes.dokkaPluginsClasspath)
+ }
+ }
+ //</editor-fold>
+
+ //<editor-fold desc="Dokka Generator Classpath">
+ /**
+ * Runtime classpath used to execute Dokka Worker.
+ *
+ * This configuration is not exposed to other subprojects.
+ *
+ * Extends [dokkaPluginsClasspath].
+ *
+ * @see org.jetbrains.dokka.dokkatoo.workers.DokkaGeneratorWorker
+ * @see org.jetbrains.dokka.dokkatoo.tasks.DokkatooGenerateTask
+ */
+ val dokkaGeneratorClasspath: NamedDomainObjectProvider<Configuration> =
+ project.configurations.register(dependencyContainerNames.dokkaGeneratorClasspath) {
+ description =
+ "Dokka Generator runtime classpath for $formatName - will be used in Dokka Worker. Should contain all transitive dependencies, plugins (and their transitive dependencies), so Dokka Worker can run."
+ asConsumer()
+
+ // extend from plugins classpath, so Dokka Worker can run the plugins
+ extendsFrom(dokkaPluginsClasspath.get())
+
+ isTransitive = true
+ attributes {
+ jvmJar()
+ dokkaCategory(dokkatooAttributes.dokkaGeneratorClasspath)
+ }
+ }
+ //</editor-fold>
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatPlugin.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatPlugin.kt
new file mode 100644
index 00000000..c8f601a6
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatPlugin.kt
@@ -0,0 +1,174 @@
+package org.jetbrains.dokka.dokkatoo.formats
+
+import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin
+import org.jetbrains.dokka.dokkatoo.DokkatooExtension
+import org.jetbrains.dokka.dokkatoo.adapters.DokkatooAndroidAdapter
+import org.jetbrains.dokka.dokkatoo.adapters.DokkatooJavaAdapter
+import org.jetbrains.dokka.dokkatoo.adapters.DokkatooKotlinAdapter
+import org.jetbrains.dokka.dokkatoo.internal.*
+import javax.inject.Inject
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.api.file.FileSystemOperations
+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.kotlin.dsl.*
+
+/**
+ * Base Gradle Plugin for setting up a Dokka Publication for a specific format.
+ *
+ * [DokkatooBasePlugin] must be applied for this plugin (or any subclass) to have an effect.
+ *
+ * Anyone can use this class as a basis for a generating a Dokka Publication in a custom format.
+ */
+abstract class DokkatooFormatPlugin(
+ val formatName: String,
+) : Plugin<Project> {
+
+ @get:Inject
+ @DokkatooInternalApi
+ protected abstract val objects: ObjectFactory
+ @get:Inject
+ @DokkatooInternalApi
+ protected abstract val providers: ProviderFactory
+ @get:Inject
+ @DokkatooInternalApi
+ protected abstract val files: FileSystemOperations
+
+
+ override fun apply(target: Project) {
+
+ // apply DokkatooBasePlugin
+ target.pluginManager.apply(DokkatooBasePlugin::class)
+
+ // apply the plugin that will autoconfigure Dokkatoo to use the sources of a Kotlin project
+ target.pluginManager.apply(type = DokkatooKotlinAdapter::class)
+ target.pluginManager.apply(type = DokkatooJavaAdapter::class)
+ target.pluginManager.apply(type = DokkatooAndroidAdapter::class)
+
+ target.plugins.withType<DokkatooBasePlugin>().configureEach {
+ val dokkatooExtension = target.extensions.getByType(DokkatooExtension::class)
+
+ val publication = dokkatooExtension.dokkatooPublications.create(formatName)
+
+ val dokkatooConsumer =
+ target.configurations.named(DokkatooBasePlugin.dependencyContainerNames.dokkatoo)
+
+ val dependencyContainers = DokkatooFormatDependencyContainers(
+ formatName = formatName,
+ dokkatooConsumer = dokkatooConsumer,
+ project = target,
+ )
+
+ val dokkatooTasks = DokkatooFormatTasks(
+ project = target,
+ publication = publication,
+ dokkatooExtension = dokkatooExtension,
+ dependencyContainers = dependencyContainers,
+ providers = providers,
+ )
+
+ dependencyContainers.dokkaModuleOutgoing.configure {
+ outgoing {
+ artifact(dokkatooTasks.prepareModuleDescriptor.flatMap { it.dokkaModuleDescriptorJson })
+ }
+ outgoing {
+ artifact(dokkatooTasks.generateModule.flatMap { it.outputDirectory }) {
+ type = "directory"
+ }
+ }
+ }
+
+ // TODO DokkaCollect replacement - share raw files without first generating a Dokka Module
+ //dependencyCollections.dokkaParametersOutgoing.configure {
+ // outgoing {
+ // artifact(dokkatooTasks.prepareParametersTask.flatMap { it.dokkaConfigurationJson })
+ // }
+ //}
+
+ val context = DokkatooFormatPluginContext(
+ project = target,
+ dokkatooExtension = dokkatooExtension,
+ dokkatooTasks = dokkatooTasks,
+ formatName = formatName,
+ )
+
+ context.configure()
+
+ if (context.addDefaultDokkaDependencies) {
+ with(context) {
+ addDefaultDokkaDependencies()
+ }
+ }
+ }
+ }
+
+
+ /** Format specific configuration - to be implemented by subclasses */
+ open fun DokkatooFormatPluginContext.configure() {}
+
+
+ @DokkatooInternalApi
+ class DokkatooFormatPluginContext(
+ val project: Project,
+ val dokkatooExtension: DokkatooExtension,
+ val dokkatooTasks: DokkatooFormatTasks,
+ formatName: String,
+ ) {
+ private val dependencyContainerNames = DokkatooBasePlugin.DependencyContainerNames(formatName)
+
+ var addDefaultDokkaDependencies = true
+
+ /** Create a [Dependency] for a Dokka module */
+ fun DependencyHandler.dokka(module: String): Provider<Dependency> =
+ dokkatooExtension.versions.jetbrainsDokka.map { version -> create("org.jetbrains.dokka:$module:$version") }
+
+ /** Add a dependency to the Dokka plugins classpath */
+ fun DependencyHandler.dokkaPlugin(dependency: Provider<Dependency>): Unit =
+ addProvider(dependencyContainerNames.dokkaPluginsClasspath, dependency)
+
+ /** Add a dependency to the Dokka plugins classpath */
+ fun DependencyHandler.dokkaPlugin(dependency: String) {
+ add(dependencyContainerNames.dokkaPluginsClasspath, dependency)
+ }
+
+ /** Add a dependency to the Dokka Generator classpath */
+ fun DependencyHandler.dokkaGenerator(dependency: Provider<Dependency>) {
+ addProvider(dependencyContainerNames.dokkaGeneratorClasspath, dependency)
+ }
+
+ /** Add a dependency to the Dokka Generator classpath */
+ fun DependencyHandler.dokkaGenerator(dependency: String) {
+ add(dependencyContainerNames.dokkaGeneratorClasspath, dependency)
+ }
+ }
+
+
+ private fun DokkatooFormatPluginContext.addDefaultDokkaDependencies() {
+ project.dependencies {
+ /** lazily create a [Dependency] with the provided [version] */
+ infix fun String.version(version: Property<String>): Provider<Dependency> =
+ version.map { v -> create("$this:$v") }
+
+ with(dokkatooExtension.versions) {
+ dokkaPlugin(dokka("analysis-kotlin-descriptors"))
+ dokkaPlugin(dokka("templating-plugin"))
+ dokkaPlugin(dokka("dokka-base"))
+// dokkaPlugin(dokka("all-modules-page-plugin"))
+
+ dokkaPlugin("org.jetbrains.kotlinx:kotlinx-html" version kotlinxHtml)
+ dokkaPlugin("org.freemarker:freemarker" version freemarker)
+
+ dokkaGenerator(dokka("dokka-core"))
+ // TODO why does org.jetbrains:markdown need a -jvm suffix?
+ dokkaGenerator("org.jetbrains:markdown-jvm" version jetbrainsMarkdown)
+ dokkaGenerator("org.jetbrains.kotlinx:kotlinx-coroutines-core" version kotlinxCoroutines)
+ }
+ }
+ }
+
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatTasks.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatTasks.kt
new file mode 100644
index 00000000..ab3639bc
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatTasks.kt
@@ -0,0 +1,105 @@
+package org.jetbrains.dokka.dokkatoo.formats
+
+import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin
+import org.jetbrains.dokka.dokkatoo.DokkatooExtension
+import org.jetbrains.dokka.dokkatoo.dokka.DokkaPublication
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.LocalProjectOnlyFilter
+import org.jetbrains.dokka.dokkatoo.internal.configuring
+import org.jetbrains.dokka.dokkatoo.tasks.DokkatooGenerateTask
+import org.jetbrains.dokka.dokkatoo.tasks.DokkatooPrepareModuleDescriptorTask
+import org.gradle.api.Project
+import org.gradle.api.provider.ProviderFactory
+import org.gradle.kotlin.dsl.*
+
+/** Tasks for generating a Dokkatoo Publication in a specific format. */
+@DokkatooInternalApi
+class DokkatooFormatTasks(
+ project: Project,
+ private val publication: DokkaPublication,
+ private val dokkatooExtension: DokkatooExtension,
+ private val dependencyContainers: DokkatooFormatDependencyContainers,
+
+ private val providers: ProviderFactory,
+) {
+ private val formatName: String get() = publication.formatName
+
+ private val taskNames = DokkatooBasePlugin.TaskNames(formatName)
+
+ private fun DokkatooGenerateTask.applyFormatSpecificConfiguration() {
+ runtimeClasspath.from(
+ dependencyContainers.dokkaGeneratorClasspath.map { classpath ->
+ classpath.incoming.artifacts.artifactFiles
+ }
+ )
+ generator.apply {
+ publicationEnabled.convention(publication.enabled)
+
+ failOnWarning.convention(publication.failOnWarning)
+ finalizeCoroutines.convention(publication.finalizeCoroutines)
+ includes.from(publication.includes)
+ moduleName.convention(publication.moduleName)
+ moduleVersion.convention(publication.moduleVersion)
+ offlineMode.convention(publication.offlineMode)
+ pluginsConfiguration.addAllLater(providers.provider { publication.pluginsConfiguration })
+ pluginsClasspath.from(
+ dependencyContainers.dokkaPluginsIntransitiveClasspath.map { classpath ->
+ classpath.incoming.artifacts.artifactFiles
+ }
+ )
+ suppressInheritedMembers.convention(publication.suppressInheritedMembers)
+ suppressObviousFunctions.convention(publication.suppressObviousFunctions)
+ }
+ }
+
+ val generatePublication = project.tasks.register<DokkatooGenerateTask>(
+ taskNames.generatePublication,
+ publication.pluginsConfiguration,
+ ).configuring task@{
+ description = "Executes the Dokka Generator, generating the $formatName publication"
+ generationType.set(DokkatooGenerateTask.GenerationType.PUBLICATION)
+
+ outputDirectory.convention(dokkatooExtension.dokkatooPublicationDirectory.dir(formatName))
+
+ generator.apply {
+ // depend on Dokka Module Descriptors from other subprojects
+ dokkaModuleFiles.from(
+ dependencyContainers.dokkaModuleConsumer.map { modules ->
+ modules.incoming
+ .artifactView { componentFilter(LocalProjectOnlyFilter) }
+ .artifacts.artifactFiles
+ }
+ )
+ }
+
+ applyFormatSpecificConfiguration()
+ }
+
+ val generateModule = project.tasks.register<DokkatooGenerateTask>(
+ taskNames.generateModule,
+ publication.pluginsConfiguration,
+ ).configuring task@{
+ description = "Executes the Dokka Generator, generating a $formatName module"
+ generationType.set(DokkatooGenerateTask.GenerationType.MODULE)
+
+ outputDirectory.convention(dokkatooExtension.dokkatooModuleDirectory.dir(formatName))
+
+ applyFormatSpecificConfiguration()
+ }
+
+ val prepareModuleDescriptor = project.tasks.register<DokkatooPrepareModuleDescriptorTask>(
+ taskNames.prepareModuleDescriptor
+ ) task@{
+ description = "Prepares the Dokka Module Descriptor for $formatName"
+ includes.from(publication.includes)
+ dokkaModuleDescriptorJson.convention(
+ dokkatooExtension.dokkatooConfigurationsDirectory.file("$formatName/module_descriptor.json")
+ )
+ moduleDirectory.set(generateModule.flatMap { it.outputDirectory })
+
+// dokkaSourceSets.addAllLater(providers.provider { dokkatooExtension.dokkatooSourceSets })
+// dokkaSourceSets.configureEach {
+// sourceSetScope.convention(this@task.path)
+// }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooGfmPlugin.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooGfmPlugin.kt
new file mode 100644
index 00000000..79df47df
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooGfmPlugin.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.dokka.dokkatoo.formats
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.gradle.kotlin.dsl.*
+
+abstract class DokkatooGfmPlugin
+@DokkatooInternalApi
+constructor() : DokkatooFormatPlugin(formatName = "gfm") {
+ override fun DokkatooFormatPluginContext.configure() {
+ project.dependencies {
+ dokkaPlugin(dokka("gfm-plugin"))
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooHtmlPlugin.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooHtmlPlugin.kt
new file mode 100644
index 00000000..5748f7d1
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooHtmlPlugin.kt
@@ -0,0 +1,72 @@
+package org.jetbrains.dokka.dokkatoo.formats
+
+import org.jetbrains.dokka.dokkatoo.dokka.plugins.DokkaHtmlPluginParameters
+import org.jetbrains.dokka.dokkatoo.dokka.plugins.DokkaHtmlPluginParameters.Companion.DOKKA_HTML_PARAMETERS_NAME
+import org.jetbrains.dokka.dokkatoo.dokka.plugins.DokkaVersioningPluginParameters
+import org.jetbrains.dokka.dokkatoo.dokka.plugins.DokkaVersioningPluginParameters.Companion.DOKKA_VERSIONING_PLUGIN_PARAMETERS_NAME
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.uppercaseFirstChar
+import org.jetbrains.dokka.dokkatoo.tasks.LogHtmlPublicationLinkTask
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.kotlin.dsl.*
+
+abstract class DokkatooHtmlPlugin
+@DokkatooInternalApi
+constructor() : DokkatooFormatPlugin(formatName = "html") {
+
+ override fun DokkatooFormatPluginContext.configure() {
+ registerDokkaBasePluginConfiguration()
+ registerDokkaVersioningPlugin()
+
+ val logHtmlUrlTask = registerLogHtmlUrlTask()
+
+ dokkatooTasks.generatePublication.configure {
+ finalizedBy(logHtmlUrlTask)
+ }
+ }
+
+ private fun DokkatooFormatPluginContext.registerDokkaBasePluginConfiguration() {
+ with(dokkatooExtension.pluginsConfiguration) {
+ registerBinding(DokkaHtmlPluginParameters::class, DokkaHtmlPluginParameters::class)
+ register<DokkaHtmlPluginParameters>(DOKKA_HTML_PARAMETERS_NAME)
+ withType<DokkaHtmlPluginParameters>().configureEach {
+ separateInheritedMembers.convention(false)
+ mergeImplicitExpectActualDeclarations.convention(false)
+ }
+ }
+ }
+
+ private fun DokkatooFormatPluginContext.registerDokkaVersioningPlugin() {
+ // register and configure Dokka Versioning Plugin
+ with(dokkatooExtension.pluginsConfiguration) {
+ registerBinding(
+ DokkaVersioningPluginParameters::class,
+ DokkaVersioningPluginParameters::class,
+ )
+ register<DokkaVersioningPluginParameters>(DOKKA_VERSIONING_PLUGIN_PARAMETERS_NAME)
+ withType<DokkaVersioningPluginParameters>().configureEach {
+ renderVersionsNavigationOnAllPages.convention(true)
+ }
+ }
+ }
+
+ private fun DokkatooFormatPluginContext.registerLogHtmlUrlTask():
+ TaskProvider<LogHtmlPublicationLinkTask> {
+
+ val indexHtmlFile = dokkatooTasks.generatePublication
+ .flatMap { it.outputDirectory.file("index.html") }
+
+ val indexHtmlPath = indexHtmlFile.map { indexHtml ->
+ indexHtml.asFile
+ .relativeTo(project.rootDir.parentFile)
+ .invariantSeparatorsPath
+ }
+
+ return project.tasks.register<LogHtmlPublicationLinkTask>(
+ "logLink" + dokkatooTasks.generatePublication.name.uppercaseFirstChar()
+ ) {
+ serverUri.convention("http://localhost:63342")
+ this.indexHtmlPath.convention(indexHtmlPath)
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooJavadocPlugin.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooJavadocPlugin.kt
new file mode 100644
index 00000000..90f024df
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooJavadocPlugin.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.dokka.dokkatoo.formats
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.gradle.kotlin.dsl.*
+
+abstract class DokkatooJavadocPlugin
+@DokkatooInternalApi
+constructor() : DokkatooFormatPlugin(formatName = "javadoc") {
+ override fun DokkatooFormatPluginContext.configure() {
+ project.dependencies {
+ dokkaPlugin(dokka("javadoc-plugin"))
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooJekyllPlugin.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooJekyllPlugin.kt
new file mode 100644
index 00000000..d8434732
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooJekyllPlugin.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.dokka.dokkatoo.formats
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.gradle.kotlin.dsl.*
+
+abstract class DokkatooJekyllPlugin
+@DokkatooInternalApi
+constructor() : DokkatooFormatPlugin(formatName = "jekyll") {
+ override fun DokkatooFormatPluginContext.configure() {
+ project.dependencies {
+ dokkaPlugin(dokka("jekyll-plugin"))
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/DokkatooInternalApi.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/DokkatooInternalApi.kt
new file mode 100644
index 00000000..e3e63753
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/DokkatooInternalApi.kt
@@ -0,0 +1,37 @@
+package org.jetbrains.dokka.dokkatoo.internal
+
+import kotlin.RequiresOptIn.Level.WARNING
+import kotlin.annotation.AnnotationRetention.BINARY
+import kotlin.annotation.AnnotationTarget.*
+
+
+/**
+ * Functionality that is annotated with this API is intended only for use by Dokkatoo internal code,
+ * but it has been given
+ * [`public` visibility](https://kotlinlang.org/docs/visibility-modifiers.html)
+ * for technical reasons.
+ *
+ * Any code that is annotated with this may be used
+ *
+ * Anyone is welcome to
+ * [opt in](https://kotlinlang.org/docs/opt-in-requirements.html#opt-in-to-using-api)
+ * to use this API, but be aware that it might change unexpectedly and without warning or migration
+ * hints.
+ *
+ * If you find yourself needing to opt in, then please report your use-case on
+ * [the Dokkatoo issue tracker](https://github.com/adamko-dev/dokkatoo/issues).
+ */
+@RequiresOptIn(
+ "Internal API - may change at any time without notice",
+ level = WARNING
+)
+@Retention(BINARY)
+@Target(
+ CLASS,
+ FUNCTION,
+ CONSTRUCTOR,
+ PROPERTY,
+ PROPERTY_GETTER,
+)
+@MustBeDocumented
+annotation class DokkatooInternalApi
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/LoggerAdapter.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/LoggerAdapter.kt
new file mode 100644
index 00000000..0a1b94fc
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/LoggerAdapter.kt
@@ -0,0 +1,65 @@
+package org.jetbrains.dokka.dokkatoo.internal
+
+import java.io.File
+import java.io.Writer
+import java.util.concurrent.atomic.AtomicInteger
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.dokka.utilities.LoggingLevel
+
+/**
+ * Logs all Dokka messages to a file.
+ *
+ * @see org.jetbrains.dokka.DokkaGenerator
+ */
+// Gradle causes OOM errors when there is a lot of console output. Logging to file is a workaround.
+// https://github.com/gradle/gradle/issues/23965
+// https://github.com/gradle/gradle/issues/15621
+internal class LoggerAdapter(
+ outputFile: File
+) : DokkaLogger, AutoCloseable {
+
+ private val logWriter: Writer
+
+ init {
+ if (!outputFile.exists()) {
+ outputFile.parentFile.mkdirs()
+ outputFile.createNewFile()
+ }
+
+ logWriter = outputFile.bufferedWriter()
+ }
+
+ private val warningsCounter = AtomicInteger()
+ private val errorsCounter = AtomicInteger()
+
+ override var warningsCount: Int
+ get() = warningsCounter.get()
+ set(value) = warningsCounter.set(value)
+
+ override var errorsCount: Int
+ get() = errorsCounter.get()
+ set(value) = errorsCounter.set(value)
+
+ override fun debug(message: String) = log(LoggingLevel.DEBUG, message)
+ override fun progress(message: String) = log(LoggingLevel.PROGRESS, message)
+ override fun info(message: String) = log(LoggingLevel.INFO, message)
+
+ override fun warn(message: String) {
+ warningsCount++
+ log(LoggingLevel.WARN, message)
+ }
+
+ override fun error(message: String) {
+ errorsCount++
+ log(LoggingLevel.ERROR, message)
+ }
+
+ @Synchronized
+ private fun log(level: LoggingLevel, message: String) {
+ logWriter.appendLine("[${level.name}] $message")
+ }
+
+ override fun close() {
+ logWriter.close()
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/collectionsUtils.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/collectionsUtils.kt
new file mode 100644
index 00000000..80b66f4b
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/collectionsUtils.kt
@@ -0,0 +1,7 @@
+package org.jetbrains.dokka.dokkatoo.internal
+
+internal fun <T, R> Set<T>.mapToSet(transform: (T) -> R): Set<R> =
+ mapTo(mutableSetOf(), transform)
+
+internal fun <T, R : Any> Set<T>.mapNotNullToSet(transform: (T) -> R?): Set<R> =
+ mapNotNullTo(mutableSetOf(), transform)
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleExtensionAccessors.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleExtensionAccessors.kt
new file mode 100644
index 00000000..85208897
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleExtensionAccessors.kt
@@ -0,0 +1,9 @@
+package org.jetbrains.dokka.dokkatoo.internal
+
+import org.jetbrains.dokka.dokkatoo.DokkatooExtension
+
+// When Dokkatoo is applied to a build script Gradle will auto-generate these accessors
+
+internal fun DokkatooExtension.versions(configure: DokkatooExtension.Versions.() -> Unit) {
+ versions.apply(configure)
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleTypealiases.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleTypealiases.kt
new file mode 100644
index 00000000..7f59db86
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleTypealiases.kt
@@ -0,0 +1,20 @@
+package org.jetbrains.dokka.dokkatoo.internal
+
+import org.jetbrains.dokka.dokkatoo.dokka.plugins.DokkaPluginParametersBaseSpec
+import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer
+
+/** Container for all [Dokka Plugin parameters][DokkaPluginParametersBaseSpec]. */
+typealias DokkaPluginParametersContainer =
+ ExtensiblePolymorphicDomainObjectContainer<DokkaPluginParametersBaseSpec>
+
+
+/**
+ * The path of a Gradle [Project][org.gradle.api.Project]. This is unique per subproject.
+ * This is _not_ the file path, which
+ * [can be configured to be different to the project path](https://docs.gradle.org/current/userguide/fine_tuning_project_layout.html#sub:modifying_element_of_the_project_tree).
+ *
+ * Example: `:modules:tests:alpha-project`.
+ *
+ * @see org.gradle.api.Project.getPath
+ */
+internal typealias GradleProjectPath = org.gradle.util.Path
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt
new file mode 100644
index 00000000..53ba49b9
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt
@@ -0,0 +1,187 @@
+package org.jetbrains.dokka.dokkatoo.internal
+
+import org.jetbrains.dokka.dokkatoo.dokka.plugins.DokkaPluginParametersBaseSpec
+import org.gradle.api.*
+import org.gradle.api.artifacts.ArtifactView
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.component.ComponentIdentifier
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.plugins.ExtensionContainer
+import org.gradle.api.provider.Provider
+import org.gradle.api.specs.Spec
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.kotlin.dsl.*
+
+
+/**
+ * Mark this [Configuration] as one that will be consumed by other subprojects.
+ *
+ * ```
+ * isCanBeResolved = false
+ * isCanBeConsumed = true
+ * ```
+ */
+internal fun Configuration.asProvider(
+ visible: Boolean = true,
+) {
+ isCanBeResolved = false
+ isCanBeConsumed = true
+ isVisible = visible
+}
+
+/**
+ * Mark this [Configuration] as one that will consume artifacts from other subprojects (also known as 'resolving')
+ *
+ * ```
+ * isCanBeResolved = true
+ * isCanBeConsumed = false
+ * ```
+ * */
+internal fun Configuration.asConsumer(
+ visible: Boolean = false,
+) {
+ isCanBeResolved = true
+ isCanBeConsumed = false
+ isVisible = visible
+}
+
+
+/** Invert a boolean [Provider] */
+internal operator fun Provider<Boolean>.not(): Provider<Boolean> = map { !it }
+
+
+/** Only matches components that come from subprojects */
+internal object LocalProjectOnlyFilter : Spec<ComponentIdentifier> {
+ override fun isSatisfiedBy(element: ComponentIdentifier?): Boolean =
+ element is ProjectComponentIdentifier
+}
+
+
+/** Invert the result of a [Spec] predicate */
+internal operator fun <T> Spec<T>.not(): Spec<T> = Spec<T> { !this@not.isSatisfiedBy(it) }
+
+
+internal fun Project.pathAsFilePath() = path
+ .removePrefix(GradleProjectPath.SEPARATOR)
+ .replace(GradleProjectPath.SEPARATOR, "/")
+
+
+/**
+ * Apply some configuration to a [Task] using
+ * [configure][org.gradle.api.tasks.TaskContainer.configure],
+ * and return the same [TaskProvider].
+ */
+internal fun <T : Task> TaskProvider<T>.configuring(
+ block: Action<T>
+): TaskProvider<T> = apply { configure(block) }
+
+
+internal fun <T> NamedDomainObjectContainer<T>.maybeCreate(
+ name: String,
+ configure: T.() -> Unit,
+): T = maybeCreate(name).apply(configure)
+
+
+/**
+ * Aggregate the incoming files from a [Configuration] (with name [named]) into [collector].
+ *
+ * Configurations that do not exist or cannot be
+ * [resolved][org.gradle.api.artifacts.Configuration.isCanBeResolved]
+ * will be ignored.
+ *
+ * @param[builtBy] An optional [TaskProvider], used to set [ConfigurableFileCollection.builtBy].
+ * This should not typically be used, and is only necessary in rare cases where a Gradle Plugin is
+ * misconfigured.
+ */
+internal fun ConfigurationContainer.collectIncomingFiles(
+ named: String,
+ collector: ConfigurableFileCollection,
+ builtBy: TaskProvider<*>? = null,
+ artifactViewConfiguration: ArtifactView.ViewConfiguration.() -> Unit = {
+ // ignore failures: it's usually okay if fetching files is best-effort because
+ // maybe Dokka doesn't need _all_ dependencies
+ lenient(true)
+ },
+) {
+ val conf = findByName(named)
+ if (conf != null && conf.isCanBeResolved) {
+ val incomingFiles = conf.incoming
+ .artifactView(artifactViewConfiguration)
+ .artifacts
+ .resolvedArtifacts // using 'resolved' might help with triggering artifact transforms?
+ .map { artifacts -> artifacts.map { it.file } }
+
+ collector.from(incomingFiles)
+
+ if (builtBy != null) {
+ collector.builtBy(builtBy)
+ }
+ }
+}
+
+
+/**
+ * Create a new [NamedDomainObjectContainer], using
+ * [org.gradle.kotlin.dsl.domainObjectContainer]
+ * (but [T] is `reified`).
+ *
+ * @param[factory] an optional factory for creating elements
+ * @see org.gradle.kotlin.dsl.domainObjectContainer
+ */
+internal inline fun <reified T : Any> ObjectFactory.domainObjectContainer(
+ factory: NamedDomainObjectFactory<T>? = null
+): NamedDomainObjectContainer<T> =
+ if (factory == null) {
+ domainObjectContainer(T::class)
+ } else {
+ domainObjectContainer(T::class, factory)
+ }
+
+
+/**
+ * Create a new [ExtensiblePolymorphicDomainObjectContainer], using
+ * [org.gradle.kotlin.dsl.polymorphicDomainObjectContainer]
+ * (but [T] is `reified`).
+ *
+ * @see org.gradle.kotlin.dsl.polymorphicDomainObjectContainer
+ */
+internal inline fun <reified T : Any> ObjectFactory.polymorphicDomainObjectContainer()
+ : ExtensiblePolymorphicDomainObjectContainer<T> =
+ polymorphicDomainObjectContainer(T::class)
+
+
+/**
+ * Add an extension to the [ExtensionContainer], and return the value.
+ *
+ * Adding an extension is especially useful for improving the DSL in build scripts when [T] is a
+ * [NamedDomainObjectContainer].
+ * Using an extension will allow Gradle to generate
+ * [type-safe model accessors](https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:accessor_applicability)
+ * for added types.
+ *
+ * ([name] should match the property name. This has to be done manually. I tried using a
+ * delegated-property provider but then Gradle can't introspect the types properly, so it fails to
+ * create accessors).
+ */
+internal inline fun <reified T : Any> ExtensionContainer.adding(
+ name: String,
+ value: T,
+): T {
+ add<T>(name, value)
+ return value
+}
+
+
+/** Create a new [DokkaPluginParametersContainer] instance. */
+internal fun ObjectFactory.dokkaPluginParametersContainer(): DokkaPluginParametersContainer {
+ val container = polymorphicDomainObjectContainer<DokkaPluginParametersBaseSpec>()
+ container.whenObjectAdded {
+ // workaround for https://github.com/gradle/gradle/issues/24972
+ (container as ExtensionAware).extensions.add(name, this)
+ }
+ return container
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/kotlinxSerializationUtils.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/kotlinxSerializationUtils.kt
new file mode 100644
index 00000000..d4f98004
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/kotlinxSerializationUtils.kt
@@ -0,0 +1,36 @@
+package org.jetbrains.dokka.dokkatoo.internal
+
+import java.io.File
+import kotlinx.serialization.json.JsonArrayBuilder
+import kotlinx.serialization.json.JsonObjectBuilder
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.add
+
+
+@JvmName("addAllFiles")
+internal fun JsonArrayBuilder.addAll(files: Iterable<File>) {
+ files
+ .map { it.canonicalFile.invariantSeparatorsPath }
+ .forEach { path -> add(path) }
+}
+
+@JvmName("addAllStrings")
+internal fun JsonArrayBuilder.addAll(values: Iterable<String>) {
+ values.forEach { add(it) }
+}
+
+internal fun JsonArrayBuilder.addAllIfNotNull(values: Iterable<String>?) {
+ if (values != null) addAll(values)
+}
+
+internal fun JsonObjectBuilder.putIfNotNull(key: String, value: Boolean?) {
+ if (value != null) put(key, JsonPrimitive(value))
+}
+
+internal fun JsonObjectBuilder.putIfNotNull(key: String, value: String?) {
+ if (value != null) put(key, JsonPrimitive(value))
+}
+
+internal fun JsonObjectBuilder.putIfNotNull(key: String, value: File?) {
+ if (value != null) put(key, JsonPrimitive(value.canonicalFile.invariantSeparatorsPath))
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/stringUtils.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/stringUtils.kt
new file mode 100644
index 00000000..75b3b8ec
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/stringUtils.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.dokka.dokkatoo.internal
+
+
+/**
+ * Title case the first char of a string.
+ *
+ * (Custom implementation because [uppercase] is deprecated, and Dokkatoo should try and be as
+ * stable as possible.)
+ */
+internal fun String.uppercaseFirstChar(): String =
+ if (isNotEmpty()) Character.toTitleCase(this[0]) + substring(1) else this
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/uriUtils.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/uriUtils.kt
new file mode 100644
index 00000000..942551c4
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/internal/uriUtils.kt
@@ -0,0 +1,9 @@
+package org.jetbrains.dokka.dokkatoo.internal
+
+import java.net.URI
+
+internal fun URI.appendPath(addition: String): URI {
+ val currentPath = path.removeSuffix("/")
+ val newPath = "$currentPath/$addition"
+ return resolve(newPath).normalize()
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooGenerateTask.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooGenerateTask.kt
new file mode 100644
index 00000000..b27acbc5
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooGenerateTask.kt
@@ -0,0 +1,187 @@
+package org.jetbrains.dokka.dokkatoo.tasks
+
+import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin.Companion.jsonMapper
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaGeneratorParametersSpec
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaModuleDescriptionKxs
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.builders.DokkaParametersBuilder
+import org.jetbrains.dokka.dokkatoo.internal.DokkaPluginParametersContainer
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.workers.DokkaGeneratorWorker
+import java.io.IOException
+import javax.inject.Inject
+import kotlinx.serialization.json.JsonElement
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.model.ReplacedBy
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.*
+import org.gradle.kotlin.dsl.*
+import org.gradle.process.JavaForkOptions
+import org.gradle.workers.WorkerExecutor
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.toPrettyJsonString
+
+/**
+ * Executes the Dokka Generator, and produces documentation.
+ *
+ * The type of documentation generated is determined by the supplied Dokka Plugins in [generator].
+ */
+@CacheableTask
+abstract class DokkatooGenerateTask
+@DokkatooInternalApi
+@Inject
+constructor(
+ objects: ObjectFactory,
+ private val workers: WorkerExecutor,
+
+ /**
+ * Configurations for Dokka Generator Plugins. Must be provided from
+ * [org.jetbrains.dokka.dokkatoo.dokka.DokkaPublication.pluginsConfiguration].
+ */
+ pluginsConfiguration: DokkaPluginParametersContainer,
+) : DokkatooTask() {
+
+ @get:OutputDirectory
+ abstract val outputDirectory: DirectoryProperty
+
+ /**
+ * Classpath required to run Dokka Generator.
+ *
+ * Contains the Dokka Generator, Dokka plugins, and any transitive dependencies.
+ */
+ @get:Classpath
+ abstract val runtimeClasspath: ConfigurableFileCollection
+
+ @get:LocalState
+ abstract val cacheDirectory: DirectoryProperty
+
+ /**
+ * Generating a Dokka Module? Set this to [GenerationType.MODULE].
+ *
+ * Generating a Dokka Publication? [GenerationType.PUBLICATION].
+ */
+ @get:Input
+ abstract val generationType: Property<GenerationType>
+
+ /** @see org.jetbrains.dokka.dokkatoo.dokka.DokkaPublication.enabled */
+ @get:Input
+ abstract val publicationEnabled: Property<Boolean>
+
+ @get:Nested
+ val generator: DokkaGeneratorParametersSpec = objects.newInstance(pluginsConfiguration)
+
+ /** @see JavaForkOptions.getDebug */
+ @get:Input
+ abstract val workerDebugEnabled: Property<Boolean>
+ /** @see JavaForkOptions.getMinHeapSize */
+ @get:Input
+ @get:Optional
+ abstract val workerMinHeapSize: Property<String>
+ /** @see JavaForkOptions.getMaxHeapSize */
+ @get:Input
+ @get:Optional
+ abstract val workerMaxHeapSize: Property<String>
+ /** @see JavaForkOptions.jvmArgs */
+ @get:Input
+ abstract val workerJvmArgs: ListProperty<String>
+ @get:Internal
+ 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`.
+ */
+ @DokkatooInternalApi
+ @get:Internal
+ abstract val dokkaConfigurationJsonFile: RegularFileProperty
+
+ enum class GenerationType {
+ MODULE,
+ PUBLICATION,
+ }
+
+ @TaskAction
+ internal fun generateDocumentation() {
+ val dokkaConfiguration = createDokkaConfiguration()
+ logger.info("dokkaConfiguration: $dokkaConfiguration")
+ dumpDokkaConfigurationJson(dokkaConfiguration)
+
+ logger.info("DokkaGeneratorWorker runtimeClasspath: ${runtimeClasspath.asPath}")
+
+ val workQueue = workers.processIsolation {
+ classpath.from(runtimeClasspath)
+ forkOptions {
+ defaultCharacterEncoding = "UTF-8"
+ minHeapSize = workerMinHeapSize.orNull
+ maxHeapSize = workerMaxHeapSize.orNull
+ enableAssertions = true
+ debug = workerDebugEnabled.get()
+ jvmArgs = workerJvmArgs.get()
+ }
+ }
+
+ workQueue.submit(DokkaGeneratorWorker::class) {
+ this.dokkaParameters.set(dokkaConfiguration)
+ this.logFile.set(workerLogFile)
+ }
+ }
+
+ /**
+ * Dump the [DokkaConfiguration] JSON to a file ([dokkaConfigurationJsonFile]) for debugging
+ * purposes.
+ */
+ private fun dumpDokkaConfigurationJson(
+ dokkaConfiguration: DokkaConfiguration,
+ ) {
+ val destFile = dokkaConfigurationJsonFile.asFile.orNull ?: return
+ destFile.parentFile.mkdirs()
+ destFile.createNewFile()
+
+ val compactJson = dokkaConfiguration.toPrettyJsonString()
+ val json = jsonMapper.decodeFromString(JsonElement.serializer(), compactJson)
+ val prettyJson = jsonMapper.encodeToString(JsonElement.serializer(), json)
+
+ destFile.writeText(prettyJson)
+
+ logger.info("[$path] Dokka Generator configuration JSON: ${destFile.toURI()}")
+ }
+
+ private fun createDokkaConfiguration(): DokkaConfiguration {
+ val outputDirectory = outputDirectory.get().asFile
+
+ val delayTemplateSubstitution = when (generationType.orNull) {
+ GenerationType.MODULE -> true
+ GenerationType.PUBLICATION -> false
+ null -> error("missing GenerationType")
+ }
+
+ val dokkaModuleDescriptors = dokkaModuleDescriptors()
+
+ return DokkaParametersBuilder.build(
+ spec = generator,
+ delayTemplateSubstitution = delayTemplateSubstitution,
+ outputDirectory = outputDirectory,
+ modules = dokkaModuleDescriptors,
+ cacheDirectory = cacheDirectory.asFile.orNull,
+ )
+ }
+
+ private fun dokkaModuleDescriptors(): List<DokkaModuleDescriptionKxs> {
+ return generator.dokkaModuleFiles.asFileTree
+ .matching { include("**/module_descriptor.json") }
+ .files.map { file ->
+ try {
+ val fileContent = file.readText()
+ jsonMapper.decodeFromString(
+ DokkaModuleDescriptionKxs.serializer(),
+ fileContent,
+ )
+ } catch (ex: Exception) {
+ throw IOException("Could not parse DokkaModuleDescriptionKxs from $file", ex)
+ }
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooPrepareModuleDescriptorTask.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooPrepareModuleDescriptorTask.kt
new file mode 100644
index 00000000..1247ebc7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooPrepareModuleDescriptorTask.kt
@@ -0,0 +1,62 @@
+package org.jetbrains.dokka.dokkatoo.tasks
+
+import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin.Companion.jsonMapper
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaModuleDescriptionKxs
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import javax.inject.Inject
+import kotlinx.serialization.encodeToString
+import org.gradle.api.file.*
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.*
+import org.gradle.api.tasks.PathSensitivity.RELATIVE
+
+/**
+ * Produces a Dokka Configuration that describes a single module of a multimodule Dokka configuration.
+ *
+ * @see org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaModuleDescriptionKxs
+ */
+@CacheableTask
+abstract class DokkatooPrepareModuleDescriptorTask
+@DokkatooInternalApi
+@Inject
+constructor() : DokkatooTask() {
+
+ @get:OutputFile
+ abstract val dokkaModuleDescriptorJson: RegularFileProperty
+
+ @get:Input
+ abstract val moduleName: Property<String>
+
+ @get:Input
+ abstract val modulePath: Property<String>
+
+ @get:InputDirectory
+ @get:PathSensitive(RELATIVE)
+ abstract val moduleDirectory: DirectoryProperty
+
+ @get:InputFiles
+ @get:Optional
+ @get:PathSensitive(RELATIVE)
+ abstract val includes: ConfigurableFileCollection
+
+ @TaskAction
+ internal fun generateModuleConfiguration() {
+ val moduleName = moduleName.get()
+ val moduleDirectory = moduleDirectory.asFile.get()
+ val includes = includes.files
+ val modulePath = modulePath.get()
+
+ val moduleDesc = DokkaModuleDescriptionKxs(
+ name = moduleName,
+ sourceOutputDirectory = moduleDirectory,
+ includes = includes,
+ modulePath = modulePath,
+ )
+
+ val encodedModuleDesc = jsonMapper.encodeToString(moduleDesc)
+
+ logger.info("encodedModuleDesc: $encodedModuleDesc")
+
+ dokkaModuleDescriptorJson.get().asFile.writeText(encodedModuleDesc)
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooTask.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooTask.kt
new file mode 100644
index 00000000..c125a64e
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooTask.kt
@@ -0,0 +1,22 @@
+package org.jetbrains.dokka.dokkatoo.tasks
+
+import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.tasks.CacheableTask
+
+/** Base Dokkatoo task */
+@CacheableTask
+abstract class DokkatooTask
+@DokkatooInternalApi
+constructor() : DefaultTask() {
+
+ @get:Inject
+ abstract val objects: ObjectFactory
+
+ init {
+ group = DokkatooBasePlugin.TASK_GROUP
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/LogHtmlPublicationLinkTask.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/LogHtmlPublicationLinkTask.kt
new file mode 100644
index 00000000..c281ce56
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/LogHtmlPublicationLinkTask.kt
@@ -0,0 +1,156 @@
+package org.jetbrains.dokka.dokkatoo.tasks
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.appendPath
+import org.jetbrains.dokka.dokkatoo.tasks.LogHtmlPublicationLinkTask.Companion.ENABLE_TASK_PROPERTY_NAME
+import java.net.URI
+import java.net.http.HttpClient
+import java.net.http.HttpRequest
+import java.net.http.HttpResponse
+import java.time.Duration
+import javax.inject.Inject
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.ProviderFactory
+import org.gradle.api.provider.ValueSource
+import org.gradle.api.provider.ValueSourceParameters
+import org.gradle.api.tasks.Console
+import org.gradle.api.tasks.TaskAction
+import org.gradle.kotlin.dsl.*
+import org.gradle.work.DisableCachingByDefault
+
+/**
+ * Prints an HTTP link in the console when the HTML publication is generated.
+ *
+ * The HTML publication requires a web server, since it loads resources via javascript.
+ *
+ * By default, it uses
+ * [IntelliJ's built-in server](https://www.jetbrains.com/help/idea/php-built-in-web-server.html)
+ * to host the file.
+ *
+ * This task can be disabled using the [ENABLE_TASK_PROPERTY_NAME] project property.
+ */
+@DisableCachingByDefault(because = "logging-only task")
+abstract class LogHtmlPublicationLinkTask
+@Inject
+@DokkatooInternalApi
+constructor(
+ providers: ProviderFactory
+) : DokkatooTask() {
+
+ @get:Console
+ abstract val serverUri: Property<String>
+
+ /**
+ * Path to the `index.html` of the publication. Will be appended to [serverUri].
+ *
+ * The IntelliJ built-in server requires a relative path originating from the _parent_ directory
+ * of the IntelliJ project.
+ *
+ * For example,
+ *
+ * * given an IntelliJ project path of
+ * ```
+ * /Users/rachel/projects/my-project/
+ * ```
+ * * and the publication is generated with an index file
+ * ```
+ * /Users/rachel/projects/my-project/docs/build/dokka/html/index.html
+ * ````
+ * * then IntelliJ requires the [indexHtmlPath] is
+ * ```
+ * my-project/docs/build/dokka/html/index.html
+ * ```
+ * * so that (assuming [serverUri] is `http://localhost:63342`) the logged URL is
+ * ```
+ * http://localhost:63342/my-project/docs/build/dokka/html/index.html
+ * ```
+ */
+ @get:Console
+ abstract val indexHtmlPath: Property<String>
+
+ init {
+ // don't assign a group. This task is a 'finalizer' util task, so it doesn't make sense
+ // to display this task prominently.
+ group = "other"
+
+ val serverActive = providers.of(ServerActiveCheck::class) {
+ parameters.uri.convention(serverUri)
+ }
+ super.onlyIf("server URL is reachable") { serverActive.get() }
+
+ val logHtmlPublicationLinkTaskEnabled = providers
+ .gradleProperty(ENABLE_TASK_PROPERTY_NAME)
+ .orElse("true")
+ .map(String::toBoolean)
+ super.onlyIf("task is enabled via property") {
+ logHtmlPublicationLinkTaskEnabled.get()
+ }
+ }
+
+ @TaskAction
+ fun exec() {
+ val serverUri = serverUri.orNull
+ val filePath = indexHtmlPath.orNull
+
+ if (serverUri != null && !filePath.isNullOrBlank()) {
+ val link = URI(serverUri).appendPath(filePath).toString()
+
+ logger.lifecycle("Generated Dokka HTML publication: $link")
+ }
+ }
+
+ /**
+ * Check if the server URI that can host the generated Dokka HTML publication is accessible.
+ *
+ * Use the [HttpClient] included with Java 11 to avoid bringing in a new dependency for such
+ * a small util.
+ *
+ * The check uses a [ValueSource] source to attempt to be compatible with Configuration Cache, but
+ * I'm not certain that this is necessary, or if a [ValueSource] is the best way to achieve it.
+ */
+ internal abstract class ServerActiveCheck : ValueSource<Boolean, ServerActiveCheck.Parameters> {
+
+ interface Parameters : ValueSourceParameters {
+ /** E.g. `http://localhost:63342` */
+ val uri: Property<String>
+ }
+
+ override fun obtain(): Boolean {
+ try {
+ val uri = URI.create(parameters.uri.get())
+ val client = HttpClient.newHttpClient()
+ val request = HttpRequest
+ .newBuilder()
+ .uri(uri)
+ .timeout(Duration.ofSeconds(1))
+ .GET()
+ .build()
+ val response = client.send(request, HttpResponse.BodyHandlers.ofString())
+
+ // don't care about the status - only if the server is available
+ return response.statusCode() > 0
+ } catch (ex: Exception) {
+ return false
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Control whether the [LogHtmlPublicationLinkTask] task is enabled. Useful for disabling the
+ * task locally, or in CI/CD, or for tests.
+ *
+ * ```properties
+ * #$GRADLE_USER_HOME/gradle.properties
+ * org.jetbrains.dokka.dokkatoo.tasks.logHtmlPublicationLinkEnabled=false
+ * ```
+ *
+ * or via an environment variable
+ *
+ * ```env
+ * ORG_GRADLE_PROJECT_org.jetbrains.dokka.dokkatoo.tasks.logHtmlPublicationLinkEnabled=false
+ * ```
+ */
+ const val ENABLE_TASK_PROPERTY_NAME = "org.jetbrains.dokka.dokkatoo.tasks.logHtmlPublicationLinkEnabled"
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/workers/DokkaGeneratorWorker.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/workers/DokkaGeneratorWorker.kt
new file mode 100644
index 00000000..4ac58d03
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/workers/DokkaGeneratorWorker.kt
@@ -0,0 +1,77 @@
+package org.jetbrains.dokka.dokkatoo.workers
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi
+import org.jetbrains.dokka.dokkatoo.internal.LoggerAdapter
+import java.io.File
+import java.time.Duration
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.workers.WorkAction
+import org.gradle.workers.WorkParameters
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaGenerator
+
+/**
+ * Gradle Worker Daemon for running [DokkaGenerator].
+ *
+ * The worker requires [DokkaGenerator] is present on its classpath, as well as any Dokka plugins
+ * that are used to generate the Dokka files. Transitive dependencies are also required.
+ */
+@DokkatooInternalApi
+abstract class DokkaGeneratorWorker : WorkAction<DokkaGeneratorWorker.Parameters> {
+
+ @DokkatooInternalApi
+ interface Parameters : WorkParameters {
+ val dokkaParameters: Property<DokkaConfiguration>
+ val logFile: RegularFileProperty
+ }
+
+ override fun execute() {
+ val dokkaParameters = parameters.dokkaParameters.get()
+
+ prepareOutputDir(dokkaParameters)
+
+ executeDokkaGenerator(
+ parameters.logFile.get().asFile,
+ dokkaParameters,
+ )
+ }
+
+ private fun prepareOutputDir(dokkaParameters: DokkaConfiguration) {
+ // Dokka Generator doesn't clean up old files, so we need to manually clean the output directory
+ dokkaParameters.outputDir.deleteRecursively()
+ dokkaParameters.outputDir.mkdirs()
+
+ // workaround until https://github.com/Kotlin/dokka/pull/2867 is released
+ dokkaParameters.modules.forEach { module ->
+ val moduleDir = dokkaParameters.outputDir.resolve(module.relativePathToOutputDirectory)
+ moduleDir.mkdirs()
+ }
+ }
+
+ private fun executeDokkaGenerator(
+ logFile: File,
+ dokkaParameters: DokkaConfiguration
+ ) {
+ LoggerAdapter(logFile).use { logger ->
+ logger.progress("Executing DokkaGeneratorWorker with dokkaParameters: $dokkaParameters")
+
+ val generator = DokkaGenerator(dokkaParameters, logger)
+
+ val duration = measureTime { generator.generate() }
+
+ logger.info("DokkaGeneratorWorker completed in $duration")
+ }
+ }
+
+ @DokkatooInternalApi
+ companion object {
+ // can't use kotlin.Duration or kotlin.time.measureTime {} because
+ // the implementation isn't stable across Kotlin versions
+ private fun measureTime(block: () -> Unit): Duration =
+ System.nanoTime().let { startTime ->
+ block()
+ Duration.ofNanos(System.nanoTime() - startTime)
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/DokkatooPluginTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/DokkatooPluginTest.kt
new file mode 100644
index 00000000..843708a3
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/DokkatooPluginTest.kt
@@ -0,0 +1,76 @@
+package org.jetbrains.dokka.dokkatoo
+
+import org.jetbrains.dokka.dokkatoo.utils.create_
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldEndWith
+import org.gradle.kotlin.dsl.*
+import org.gradle.testfixtures.ProjectBuilder
+
+class DokkatooPluginTest : FunSpec({
+
+ test("expect plugin id can be applied to project successfully") {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka.dokkatoo")
+ project.plugins.hasPlugin("org.jetbrains.dokka.dokkatoo") shouldBe true
+ project.plugins.hasPlugin(DokkatooPlugin::class) shouldBe true
+ }
+
+ test("expect plugin class can be applied to project successfully") {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply(type = DokkatooPlugin::class)
+ project.plugins.hasPlugin("org.jetbrains.dokka.dokkatoo") shouldBe true
+ project.plugins.hasPlugin(DokkatooPlugin::class) shouldBe true
+ }
+
+ context("Dokkatoo property conventions") {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply("org.jetbrains.dokka.dokkatoo")
+
+ val extension = project.extensions.getByType<DokkatooExtension>()
+
+ context("DokkatooSourceSets") {
+ val testSourceSet = extension.dokkatooSourceSets.create_("Test") {
+ externalDocumentationLinks.create_("gradle") {
+ url("https://docs.gradle.org/7.6.1/javadoc")
+ }
+ }
+
+ context("JDK external documentation link") {
+ val jdkLink = testSourceSet.externalDocumentationLinks.getByName("jdk")
+
+ test("when enableJdkDocumentationLink is false, expect jdk link is disabled") {
+ testSourceSet.enableJdkDocumentationLink.set(false)
+ jdkLink.enabled.get() shouldBe false
+ }
+
+ test("when enableJdkDocumentationLink is true, expect jdk link is enabled") {
+ testSourceSet.enableJdkDocumentationLink.set(true)
+ jdkLink.enabled.get() shouldBe true
+ }
+
+ (5..10).forEach { jdkVersion ->
+ test("when jdkVersion is $jdkVersion, expect packageListUrl uses package-list file") {
+ testSourceSet.jdkVersion.set(jdkVersion)
+ jdkLink.packageListUrl.get().toString() shouldEndWith "package-list"
+ }
+ }
+
+ (11..22).forEach { jdkVersion ->
+ test("when jdkVersion is $jdkVersion, expect packageListUrl uses element-list file") {
+ testSourceSet.jdkVersion.set(jdkVersion)
+ jdkLink.packageListUrl.get().toString() shouldEndWith "element-list"
+ }
+ }
+ }
+
+ context("external doc links") {
+ test("package-list url should be appended to Javadoc URL") {
+ val gradleDocLink = testSourceSet.externalDocumentationLinks.getByName("gradle")
+ gradleDocLink.packageListUrl.get()
+ .toString() shouldBe "https://docs.gradle.org/7.6.1/javadoc/package-list"
+ }
+ }
+ }
+ }
+})
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/DokkaExternalDocumentationLinkSpecTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/DokkaExternalDocumentationLinkSpecTest.kt
new file mode 100644
index 00000000..28fb2b83
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/DokkaExternalDocumentationLinkSpecTest.kt
@@ -0,0 +1,102 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.DokkatooExtension
+import org.jetbrains.dokka.dokkatoo.DokkatooPlugin
+import org.jetbrains.dokka.dokkatoo.utils.create_
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.datatest.WithDataTestName
+import io.kotest.datatest.withData
+import io.kotest.matchers.shouldBe
+import org.gradle.kotlin.dsl.*
+import org.gradle.testfixtures.ProjectBuilder
+
+
+class DokkaExternalDocumentationLinkSpecTest : FunSpec({
+
+ context("expect url can be set") {
+ test("using a string") {
+ val actual = createExternalDocLinkSpec {
+ url("https://github.com/adamko-dev/dokkatoo/")
+ }
+
+ actual.url.get().toString() shouldBe "https://github.com/adamko-dev/dokkatoo/"
+ }
+
+ test("using a string-provider") {
+ val actual = createExternalDocLinkSpec {
+ url(project.provider { "https://github.com/adamko-dev/dokkatoo/" })
+ }
+
+ actual.url.get().toString() shouldBe "https://github.com/adamko-dev/dokkatoo/"
+ }
+ }
+
+ context("expect packageListUrl can be set") {
+ test("using a string") {
+ val actual = createExternalDocLinkSpec {
+ packageListUrl("https://github.com/adamko-dev/dokkatoo/")
+ }
+
+ actual.packageListUrl.get().toString() shouldBe "https://github.com/adamko-dev/dokkatoo/"
+ }
+
+ test("using a string-provider") {
+ val actual = createExternalDocLinkSpec {
+ packageListUrl(project.provider { "https://github.com/adamko-dev/dokkatoo/" })
+ }
+
+ actual.packageListUrl.get().toString() shouldBe "https://github.com/adamko-dev/dokkatoo/"
+ }
+ }
+
+ context("expect packageList defaults to url+package-list") {
+ data class TestCase(
+ val actualUrl: String,
+ val expected: String,
+ val testName: String,
+ ) : WithDataTestName {
+ override fun dataTestName(): String = testName
+ }
+
+ withData(
+ TestCase(
+ testName = "non-empty path, with trailing slash",
+ actualUrl = "https://github.com/adamko-dev/dokkatoo/",
+ expected = "https://github.com/adamko-dev/dokkatoo/package-list",
+ ),
+ TestCase(
+ testName = "non-empty path, without trailing slash",
+ actualUrl = "https://github.com/adamko-dev/dokkatoo",
+ expected = "https://github.com/adamko-dev/dokkatoo/package-list",
+ ),
+ TestCase(
+ testName = "empty path, with trailing slash",
+ actualUrl = "https://github.com/",
+ expected = "https://github.com/package-list",
+ ),
+ TestCase(
+ testName = "empty path, without trailing slash",
+ actualUrl = "https://github.com",
+ expected = "https://github.com/package-list",
+ )
+ ) { (actualUrl, expected) ->
+ val actual = createExternalDocLinkSpec { url(actualUrl) }
+ actual.packageListUrl.get().toString() shouldBe expected
+ }
+ }
+})
+
+private val project = ProjectBuilder.builder().build().also { project ->
+ project.plugins.apply(type = DokkatooPlugin::class)
+}
+
+private fun createExternalDocLinkSpec(
+ configure: DokkaExternalDocumentationLinkSpec.() -> Unit
+): DokkaExternalDocumentationLinkSpec {
+
+ val dssContainer = project.extensions.getByType<DokkatooExtension>().dokkatooSourceSets
+
+ return dssContainer.create_("test" + dssContainer.size)
+ .externalDocumentationLinks
+ .create("testLink", configure)
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/DokkaSourceLinkSpecTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/DokkaSourceLinkSpecTest.kt
new file mode 100644
index 00000000..f3171a57
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/DokkaSourceLinkSpecTest.kt
@@ -0,0 +1,58 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.engine.spec.tempdir
+import io.kotest.matchers.shouldBe
+import org.gradle.api.Project
+import org.gradle.api.provider.Provider
+import org.gradle.kotlin.dsl.*
+import org.gradle.testfixtures.ProjectBuilder
+
+class DokkaSourceLinkSpecTest : FunSpec({
+ val project = ProjectBuilder.builder().build()
+
+ context("expect localDirectoryPath") {
+ test("is the invariantSeparatorsPath of localDirectory") {
+ val tempDir = tempdir()
+
+ val actual = project.createDokkaSourceLinkSpec {
+ localDirectory.set(tempDir)
+ }
+
+ actual.localDirectoryPath2.get() shouldBe tempDir.invariantSeparatorsPath
+ }
+ }
+
+
+ context("expect remoteUrl can be set") {
+ test("using a string") {
+ val actual = project.createDokkaSourceLinkSpec {
+ remoteUrl("https://github.com/adamko-dev/dokkatoo/")
+ }
+
+ actual.remoteUrl.get().toString() shouldBe "https://github.com/adamko-dev/dokkatoo/"
+ }
+
+ test("using a string-provider") {
+ val actual = project.createDokkaSourceLinkSpec {
+ remoteUrl(project.provider { "https://github.com/adamko-dev/dokkatoo/" })
+ }
+
+ actual.remoteUrl.get().toString() shouldBe "https://github.com/adamko-dev/dokkatoo/"
+ }
+ }
+}) {
+
+ /** Re-implement [DokkaSourceLinkSpec] to make [localDirectoryPath] accessible in tests */
+ abstract class DokkaSourceLinkSpec2 : DokkaSourceLinkSpec() {
+ val localDirectoryPath2: Provider<String>
+ get() = super.localDirectoryPath
+ }
+
+ companion object {
+ private fun Project.createDokkaSourceLinkSpec(
+ configure: DokkaSourceLinkSpec.() -> Unit
+ ): DokkaSourceLinkSpec2 =
+ objects.newInstance(DokkaSourceLinkSpec2::class).apply(configure)
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/KotlinPlatformTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/KotlinPlatformTest.kt
new file mode 100644
index 00000000..c921df9a
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/KotlinPlatformTest.kt
@@ -0,0 +1,37 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.KotlinPlatform.Companion.dokkaType
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.inspectors.shouldForAll
+import io.kotest.matchers.collections.shouldBeIn
+import io.kotest.matchers.shouldBe
+import org.jetbrains.dokka.Platform
+
+class KotlinPlatformTest : FunSpec({
+
+ test("should have same default as Dokka type") {
+ KotlinPlatform.DEFAULT.dokkaType shouldBe Platform.DEFAULT
+ }
+
+ test("Dokka platform should have equivalent KotlinPlatform") {
+
+ Platform.values().shouldForAll { dokkaPlatform ->
+ dokkaPlatform shouldBeIn KotlinPlatform.values.map { it.dokkaType }
+ }
+ }
+
+ test("platform strings should map to same KotlinPlatform and Platform") {
+ listOf(
+ "androidJvm",
+ "android",
+ "metadata",
+ "jvm",
+ "js",
+ "wasm",
+ "native",
+ "common",
+ ).shouldForAll {
+ Platform.fromString(it) shouldBe KotlinPlatform.fromString(it).dokkaType
+ }
+ }
+})
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/VisibilityModifierTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/VisibilityModifierTest.kt
new file mode 100644
index 00000000..ca5ad49a
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/VisibilityModifierTest.kt
@@ -0,0 +1,17 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters
+
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.VisibilityModifier.Companion.dokkaType
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.inspectors.shouldForAll
+import io.kotest.inspectors.shouldForOne
+import io.kotest.matchers.shouldBe
+import org.jetbrains.dokka.DokkaConfiguration
+
+class VisibilityModifierTest : FunSpec({
+
+ test("DokkaConfiguration.Visibility should have equivalent VisibilityModifier") {
+ DokkaConfiguration.Visibility.values().shouldForAll { dokkaVisibility ->
+ VisibilityModifier.entries.map { it.dokkaType }.shouldForOne { it shouldBe dokkaVisibility }
+ }
+ }
+})
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaModuleDescriptionBuilderTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaModuleDescriptionBuilderTest.kt
new file mode 100644
index 00000000..ff442663
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaModuleDescriptionBuilderTest.kt
@@ -0,0 +1,7 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters.builders
+
+import io.kotest.core.spec.style.FunSpec
+
+class DokkaModuleDescriptionBuilderTest : FunSpec({
+
+})
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaParametersBuilderTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaParametersBuilderTest.kt
new file mode 100644
index 00000000..66918194
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaParametersBuilderTest.kt
@@ -0,0 +1,7 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters.builders
+
+import io.kotest.core.spec.style.FunSpec
+
+class DokkaParametersBuilderTest : FunSpec({
+
+})
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaSourceSetBuilderTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaSourceSetBuilderTest.kt
new file mode 100644
index 00000000..bb4bf8a7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/test/kotlin/dokka/parameters/builders/DokkaSourceSetBuilderTest.kt
@@ -0,0 +1,198 @@
+package org.jetbrains.dokka.dokkatoo.dokka.parameters.builders
+
+import org.jetbrains.dokka.dokkatoo.DokkatooExtension
+import org.jetbrains.dokka.dokkatoo.DokkatooPlugin
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetSpec
+import org.jetbrains.dokka.dokkatoo.utils.all_
+import org.jetbrains.dokka.dokkatoo.utils.create_
+import org.jetbrains.dokka.dokkatoo.utils.shouldContainAll
+import org.jetbrains.dokka.dokkatoo.utils.sourceLink_
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.engine.spec.tempdir
+import io.kotest.inspectors.shouldForAll
+import io.kotest.matchers.collections.shouldBeSingleton
+import io.kotest.matchers.equals.shouldNotBeEqual
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
+import java.io.File
+import java.net.URI
+import org.gradle.api.Project
+import org.gradle.api.file.Directory
+import org.gradle.api.internal.provider.MissingValueException
+import org.gradle.kotlin.dsl.*
+import org.gradle.testfixtures.ProjectBuilder
+
+class DokkaSourceSetBuilderTest : FunSpec({
+
+ context("when building a ExternalDocumentationLinkSpec") {
+ val project = createProject()
+
+ test("expect url is required") {
+ val sourceSetSpec = project.createDokkaSourceSetSpec("test1") {
+ externalDocumentationLinks.create_("TestLink") {
+ url.set(null as URI?)
+ packageListUrl("https://github.com/adamko-dev/dokkatoo/")
+ }
+ }
+
+ val caughtException = shouldThrow<MissingValueException> {
+ DokkaSourceSetBuilder.buildAll(setOf(sourceSetSpec))
+ }
+
+ caughtException.message shouldContain "Cannot query the value of property 'url' because it has no value available"
+ }
+
+ test("expect packageListUrl is required") {
+ val sourceSetSpec = project.createDokkaSourceSetSpec("test2") {
+ externalDocumentationLinks.create_("TestLink") {
+ url("https://github.com/adamko-dev/dokkatoo/")
+ packageListUrl.convention(null as URI?)
+ packageListUrl.set(null as URI?)
+ }
+ }
+
+ val caughtException = shouldThrow<MissingValueException> {
+ DokkaSourceSetBuilder.buildAll(setOf(sourceSetSpec))
+ }
+
+ caughtException.message shouldContain "Cannot query the value of property 'packageListUrl' because it has no value available"
+ }
+
+ test("expect null when not enabled") {
+ val sourceSetSpec = project.createDokkaSourceSetSpec("test3")
+ val linkSpec = sourceSetSpec.externalDocumentationLinks.create_("TestLink") {
+ url("https://github.com/adamko-dev/dokkatoo/")
+ packageListUrl("https://github.com/adamko-dev/dokkatoo/")
+ enabled.set(false)
+ }
+
+ DokkaSourceSetBuilder.buildAll(setOf(sourceSetSpec)).shouldBeSingleton { sourceSet ->
+ sourceSet.externalDocumentationLinks.shouldForAll { link ->
+ link.url shouldNotBeEqual linkSpec.url.get().toURL()
+ link.packageListUrl shouldNotBeEqual linkSpec.packageListUrl.get().toURL()
+ }
+ }
+ }
+ }
+
+
+ context("when DokkaSourceLinkSpec is built") {
+ val project = createProject()
+
+ test("expect built object contains all properties") {
+ val tempDir = tempdir()
+
+ val sourceSetSpec = project.createDokkaSourceSetSpec("testAllProperties") {
+ sourceLink_ {
+ localDirectory.set(tempDir)
+ remoteUrl("https://github.com/adamko-dev/dokkatoo/")
+ remoteLineSuffix.set("%L")
+ }
+ }
+
+ val sourceSet = DokkaSourceSetBuilder.buildAll(setOf(sourceSetSpec)).single()
+
+ sourceSet.sourceLinks.shouldBeSingleton { sourceLink ->
+ sourceLink.remoteUrl shouldBe URI("https://github.com/adamko-dev/dokkatoo/").toURL()
+ sourceLink.localDirectory shouldBe tempDir.invariantSeparatorsPath
+ sourceLink.remoteLineSuffix shouldBe "%L"
+ }
+ }
+
+ test("expect localDirectory is required") {
+ val sourceSetSpec = project.createDokkaSourceSetSpec("testLocalDirRequired") {
+ sourceLink_ {
+ remoteUrl("https://github.com/adamko-dev/dokkatoo/")
+ remoteLineSuffix.set("%L")
+ }
+ }
+
+ sourceSetSpec.sourceLinks.all_ {
+ localDirectory.convention(null as Directory?)
+ localDirectory.set(null as File?)
+ }
+
+ val caughtException = shouldThrow<MissingValueException> {
+ DokkaSourceSetBuilder.buildAll(setOf(sourceSetSpec))
+ }
+
+ caughtException.message.shouldContainAll(
+ "Cannot query the value of this provider because it has no value available",
+ "The value of this provider is derived from",
+ "property 'localDirectory'",
+ )
+ }
+
+ test("expect localDirectory is an invariantSeparatorsPath") {
+ val tempDir = tempdir()
+
+ val sourceSetSpec = project.createDokkaSourceSetSpec("testLocalDirPath") {
+ sourceLink_ {
+ localDirectory.set(tempDir)
+ remoteUrl("https://github.com/adamko-dev/dokkatoo/")
+ remoteLineSuffix.set(null as String?)
+ }
+ }
+
+ val link = DokkaSourceSetBuilder.buildAll(setOf(sourceSetSpec))
+ .single()
+ .sourceLinks
+ .single()
+
+ link.localDirectory shouldBe tempDir.invariantSeparatorsPath
+ }
+
+ test("expect remoteUrl is required") {
+ val sourceSetSpec = project.createDokkaSourceSetSpec("testRemoteUrlRequired") {
+ sourceLink_ {
+ localDirectory.set(tempdir())
+ remoteUrl.set(project.providers.provider { null })
+ remoteLineSuffix.set("%L")
+ }
+ }
+
+ val caughtException = shouldThrow<MissingValueException> {
+ DokkaSourceSetBuilder.buildAll(setOf(sourceSetSpec))
+ }
+
+ caughtException.message shouldContain "Cannot query the value of property 'remoteUrl' because it has no value available"
+ }
+
+ test("expect remoteLineSuffix is optional") {
+ val tempDir = tempdir()
+
+ val sourceSetSpec = project.createDokkaSourceSetSpec("testRemoteLineSuffixOptional") {
+ sourceLink_ {
+ localDirectory.set(tempDir)
+ remoteUrl("https://github.com/adamko-dev/dokkatoo/")
+ remoteLineSuffix.set(project.providers.provider { null })
+ }
+ }
+
+ val sourceSet = DokkaSourceSetBuilder.buildAll(setOf(sourceSetSpec)).single()
+
+ sourceSet.sourceLinks.shouldBeSingleton { sourceLink ->
+ sourceLink.remoteUrl shouldBe URI("https://github.com/adamko-dev/dokkatoo/").toURL()
+ sourceLink.localDirectory shouldBe tempDir.invariantSeparatorsPath
+ sourceLink.remoteLineSuffix shouldBe null
+ }
+ }
+ }
+})
+
+private fun createProject(): Project {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply(type = DokkatooPlugin::class)
+ return project
+}
+
+private fun Project.createDokkaSourceSetSpec(
+ name: String,
+ configure: DokkaSourceSetSpec.() -> Unit = {}
+): DokkaSourceSetSpec {
+ return extensions
+ .getByType<DokkatooExtension>()
+ .dokkatooSourceSets
+ .create_(name, configure)
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt
new file mode 100644
index 00000000..2f9e1b41
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/GradleTestKitUtils.kt
@@ -0,0 +1,274 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+import java.io.File
+import java.nio.file.Path
+import java.nio.file.Paths
+import kotlin.properties.PropertyDelegateProvider
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+import org.gradle.testkit.runner.GradleRunner
+import org.intellij.lang.annotations.Language
+
+
+// utils for testing using Gradle TestKit
+
+
+class GradleProjectTest(
+ override val projectDir: Path,
+) : ProjectDirectoryScope {
+
+ constructor(
+ testProjectName: String,
+ baseDir: Path = funcTestTempDir,
+ ) : this(projectDir = baseDir.resolve(testProjectName))
+
+ val runner: GradleRunner
+ get() = GradleRunner.create()
+ .withProjectDir(projectDir.toFile())
+ .withJvmArguments(
+ "-XX:MaxMetaspaceSize=512m",
+ "-XX:+AlwaysPreTouch", // https://github.com/gradle/gradle/issues/3093#issuecomment-387259298
+ ).addArguments(
+ // disable the logging task so the tests work consistently on local machines and CI/CD
+ "-P" + "org.jetbrains.dokka.dokkatoo.tasks.logHtmlPublicationLinkEnabled=false"
+ )
+
+ val testMavenRepoRelativePath: String =
+ projectDir.relativize(testMavenRepoDir).toFile().invariantSeparatorsPath
+
+ companion object {
+
+ /** file-based Maven Repo that contains the Dokka dependencies */
+ val testMavenRepoDir: Path by systemProperty(Paths::get)
+
+ val projectTestTempDir: Path by systemProperty(Paths::get)
+
+ /** Temporary directory for the functional tests */
+ val funcTestTempDir: Path by lazy {
+ projectTestTempDir.resolve("functional-tests")
+ }
+
+ /** Dokka Source directory that contains Gradle projects used for integration tests */
+ val integrationTestProjectsDir: Path by systemProperty(Paths::get)
+ /** Dokka Source directory that contains example Gradle projects */
+ val exampleProjectsDir: Path by systemProperty(Paths::get)
+ }
+}
+
+
+///**
+// * 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.
+ *
+ * @param[testProjectName] the path of the project directory, relative to [baseDir
+ */
+fun gradleKtsProjectTest(
+ testProjectName: String,
+ baseDir: Path = GradleProjectTest.funcTestTempDir,
+ build: GradleProjectTest.() -> Unit,
+): GradleProjectTest {
+ return GradleProjectTest(baseDir = baseDir, testProjectName = testProjectName).apply {
+
+ settingsGradleKts = """
+ |rootProject.name = "test"
+ |
+ |@Suppress("UnstableApiUsage")
+ |dependencyResolutionManagement {
+ | repositories {
+ | mavenCentral()
+ | maven(file("$testMavenRepoRelativePath")) {
+ | mavenContent {
+ | includeGroup("org.jetbrains.dokka.dokkatoo")
+ | includeGroup("org.jetbrains.dokka.dokkatoo-html")
+ | }
+ | }
+ | }
+ |}
+ |
+ |pluginManagement {
+ | repositories {
+ | mavenCentral()
+ | gradlePluginPortal()
+ | maven(file("$testMavenRepoRelativePath")) {
+ | mavenContent {
+ | includeGroup("org.jetbrains.dokka.dokkatoo")
+ | includeGroup("org.jetbrains.dokka.dokkatoo-html")
+ | }
+ | }
+ | }
+ |}
+ |
+ """.trimMargin()
+
+ gradleProperties = """
+ |kotlin.mpp.stability.nowarn=true
+ |org.gradle.cache=true
+ """.trimMargin()
+
+ build()
+ }
+}
+
+/**
+ * Builder for testing a Gradle project that uses Groovy script and creates default,
+ * `settings.gradle`, and `gradle.properties` files.
+ *
+ * @param[testProjectName] the name of the test, which should be distinct across the project
+ */
+fun gradleGroovyProjectTest(
+ testProjectName: String,
+ baseDir: Path = GradleProjectTest.funcTestTempDir,
+ build: GradleProjectTest.() -> Unit,
+): GradleProjectTest {
+ return GradleProjectTest(baseDir = baseDir, testProjectName = testProjectName).apply {
+
+ settingsGradle = """
+ |rootProject.name = "test"
+ |
+ |dependencyResolutionManagement {
+ | repositories {
+ | mavenCentral()
+ | maven { url = file("$testMavenRepoRelativePath") }
+ | }
+ |}
+ |
+ |pluginManagement {
+ | repositories {
+ | mavenCentral()
+ | gradlePluginPortal()
+ | maven { url = file("$testMavenRepoRelativePath") }
+ | }
+ |}
+ |
+ """.trimMargin()
+
+ gradleProperties = """
+ |kotlin.mpp.stability.nowarn=true
+ |org.gradle.cache=true
+ """.trimMargin()
+
+ build()
+ }
+}
+
+
+fun GradleProjectTest.projectFile(
+ @Language("TEXT")
+ filePath: String
+): PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, String>> =
+ PropertyDelegateProvider { _, _ ->
+ TestProjectFileProvidedDelegate(this, filePath)
+ }
+
+
+/** Delegate for reading and writing a [GradleProjectTest] file. */
+private class TestProjectFileProvidedDelegate(
+ private val project: GradleProjectTest,
+ private val filePath: String,
+) : ReadWriteProperty<Any?, String> {
+ override fun getValue(thisRef: Any?, property: KProperty<*>): String =
+ project.projectDir.resolve(filePath).toFile().readText()
+
+ override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
+ project.createFile(filePath, value)
+ }
+}
+
+/** Delegate for reading and writing a [GradleProjectTest] file. */
+class TestProjectFileDelegate(
+ private val filePath: String,
+) : ReadWriteProperty<ProjectDirectoryScope, String> {
+ override fun getValue(thisRef: ProjectDirectoryScope, property: KProperty<*>): String =
+ thisRef.projectDir.resolve(filePath).toFile().readText()
+
+ override fun setValue(thisRef: ProjectDirectoryScope, property: KProperty<*>, value: String) {
+ thisRef.createFile(filePath, value)
+ }
+}
+
+
+@DslMarker
+annotation class ProjectDirectoryDsl
+
+@ProjectDirectoryDsl
+interface ProjectDirectoryScope {
+ val projectDir: Path
+}
+
+private data class ProjectDirectoryScopeImpl(
+ override val projectDir: Path
+) : ProjectDirectoryScope
+
+
+fun ProjectDirectoryScope.createFile(filePath: String, contents: String): File =
+ projectDir.resolve(filePath).toFile().apply {
+ parentFile.mkdirs()
+ createNewFile()
+ writeText(contents)
+ }
+
+
+@ProjectDirectoryDsl
+fun ProjectDirectoryScope.dir(
+ path: String,
+ block: ProjectDirectoryScope.() -> Unit = {},
+): ProjectDirectoryScope =
+ ProjectDirectoryScopeImpl(projectDir.resolve(path)).apply(block)
+
+
+@ProjectDirectoryDsl
+fun ProjectDirectoryScope.file(
+ path: String
+): Path = projectDir.resolve(path)
+
+
+fun ProjectDirectoryScope.findFiles(matcher: (File) -> Boolean): Sequence<File> =
+ projectDir.toFile().walk().filter(matcher)
+
+
+/** Set the content of `settings.gradle.kts` */
+@delegate:Language("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("build.gradle.kts")
+
+
+/** Set the content of `settings.gradle` */
+@delegate:Language("groovy")
+var ProjectDirectoryScope.settingsGradle: String by TestProjectFileDelegate("settings.gradle")
+
+
+/** Set the content of `build.gradle` */
+@delegate:Language("groovy")
+var ProjectDirectoryScope.buildGradle: String by TestProjectFileDelegate("build.gradle")
+
+
+/** Set the content of `gradle.properties` */
+@delegate:Language("properties")
+var ProjectDirectoryScope.gradleProperties: String by TestProjectFileDelegate(
+ /* language=text */ "gradle.properties"
+)
+
+
+fun ProjectDirectoryScope.createKotlinFile(filePath: String, @Language("kotlin") contents: String) =
+ createFile(filePath, contents)
+
+
+fun ProjectDirectoryScope.createKtsFile(filePath: String, @Language("kts") contents: String) =
+ createFile(filePath, contents)
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/KotestProjectConfig.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/KotestProjectConfig.kt
new file mode 100644
index 00000000..d6eadba0
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/KotestProjectConfig.kt
@@ -0,0 +1,10 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+import io.kotest.core.config.AbstractProjectConfig
+
+@Suppress("unused") // this class is automatically picked up by Kotest
+object KotestProjectConfig : AbstractProjectConfig() {
+ init {
+ displayFullTestPath = true
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/fileTree.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/fileTree.kt
new file mode 100644
index 00000000..4ba850d3
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/fileTree.kt
@@ -0,0 +1,61 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+import java.io.File
+import java.nio.file.Path
+
+// based on https://gist.github.com/mfwgenerics/d1ec89eb80c95da9d542a03b49b5e15b
+// context: https://kotlinlang.slack.com/archives/C0B8MA7FA/p1676106647658099
+
+fun Path.toTreeString(): String = toFile().toTreeString()
+
+fun File.toTreeString(): String = when {
+ isDirectory -> name + "/\n" + buildTreeString(this)
+ else -> name
+}
+
+private fun buildTreeString(
+ dir: File,
+ margin: String = "",
+): String {
+ val entries = dir.listDirectoryEntries()
+
+ return entries.joinToString("\n") { entry ->
+ val (currentPrefix, nextPrefix) = when (entry) {
+ entries.last() -> PrefixPair.LAST_ENTRY
+ else -> PrefixPair.INTERMEDIATE
+ }
+
+ buildString {
+ append("$margin${currentPrefix}${entry.name}")
+
+ if (entry.isDirectory) {
+ append("/")
+ if (entry.countDirectoryEntries() > 0) {
+ append("\n")
+ }
+ append(buildTreeString(entry, margin + nextPrefix))
+ }
+ }
+ }
+}
+
+private fun File.listDirectoryEntries(): Sequence<File> =
+ walkTopDown().maxDepth(1).filter { it != this@listDirectoryEntries }
+
+
+private fun File.countDirectoryEntries(): Int =
+ listDirectoryEntries().count()
+
+private data class PrefixPair(
+ /** The current entry should be prefixed with this */
+ val currentPrefix: String,
+ /** If the next item is a directory, it should be prefixed with this */
+ val nextPrefix: String,
+) {
+ companion object {
+ /** Prefix pair for a non-last directory entry */
+ val INTERMEDIATE = PrefixPair("├── ", "│ ")
+ /** Prefix pair for the last directory entry */
+ val LAST_ENTRY = PrefixPair("└── ", " ")
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/files.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/files.kt
new file mode 100644
index 00000000..6a423b55
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/files.kt
@@ -0,0 +1,6 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+import java.io.File
+
+fun File.copyInto(directory: File, overwrite: Boolean = false) =
+ copyTo(directory.resolve(name), overwrite = overwrite)
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/gradleRunnerUtils.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/gradleRunnerUtils.kt
new file mode 100644
index 00000000..912d1df1
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/gradleRunnerUtils.kt
@@ -0,0 +1,47 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.BuildTask
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.internal.DefaultGradleRunner
+
+
+/** Edit environment variables in the Gradle Runner */
+@Deprecated("Windows does not support withEnvironment - https://github.com/gradle/gradle/issues/23959")
+fun GradleRunner.withEnvironment(build: MutableMap<String, String?>.() -> Unit): GradleRunner {
+ val env = environment ?: mutableMapOf()
+ env.build()
+ return withEnvironment(env)
+}
+
+
+inline fun GradleRunner.build(
+ handleResult: BuildResult.() -> Unit
+): Unit = build().let(handleResult)
+
+
+inline fun GradleRunner.buildAndFail(
+ handleResult: BuildResult.() -> Unit
+): Unit = buildAndFail().let(handleResult)
+
+
+fun GradleRunner.withJvmArguments(
+ vararg jvmArguments: String
+): GradleRunner = (this as DefaultGradleRunner).withJvmArguments(*jvmArguments)
+
+
+/**
+ * Helper function to _append_ [arguments] to any existing
+ * [GradleRunner arguments][GradleRunner.getArguments].
+ */
+fun GradleRunner.addArguments(
+ vararg arguments: String
+): GradleRunner =
+ withArguments(this@addArguments.arguments + arguments)
+
+
+/**
+ * Get the name of the task, without the leading [BuildTask.getPath].
+ */
+val BuildTask.name: String
+ get() = path.substringAfterLast(':')
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestCollectionMatchers.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestCollectionMatchers.kt
new file mode 100644
index 00000000..8c33e3eb
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestCollectionMatchers.kt
@@ -0,0 +1,20 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+import io.kotest.matchers.collections.shouldBeSingleton
+import io.kotest.matchers.maps.shouldContainAll
+import io.kotest.matchers.maps.shouldContainExactly
+
+/** @see io.kotest.matchers.maps.shouldContainAll */
+fun <K, V> Map<K, V>.shouldContainAll(
+ vararg expected: Pair<K, V>
+): Unit = shouldContainAll(expected.toMap())
+
+/** @see io.kotest.matchers.maps.shouldContainExactly */
+fun <K, V> Map<K, V>.shouldContainExactly(
+ vararg expected: Pair<K, V>
+): Unit = shouldContainExactly(expected.toMap())
+
+/** Verify the sequence contains a single element, matching [match]. */
+fun <T> Sequence<T>.shouldBeSingleton(match: (T) -> Unit) {
+ toList().shouldBeSingleton(match)
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestConditions.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestConditions.kt
new file mode 100644
index 00000000..7b692afb
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestConditions.kt
@@ -0,0 +1,10 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+import io.kotest.core.annotation.EnabledCondition
+import io.kotest.core.spec.Spec
+import kotlin.reflect.KClass
+
+class NotWindowsCondition : EnabledCondition {
+ override fun enabled(kclass: KClass<out Spec>): Boolean =
+ "win" !in System.getProperty("os.name").lowercase()
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt
new file mode 100644
index 00000000..e1863c8f
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt
@@ -0,0 +1,130 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+import io.kotest.assertions.assertSoftly
+import io.kotest.matchers.*
+import org.gradle.api.NamedDomainObjectCollection
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.BuildTask
+import org.gradle.testkit.runner.TaskOutcome
+
+infix fun <T : Any> NamedDomainObjectCollection<out T>?.shouldContainDomainObject(
+ name: String
+): T {
+ this should containDomainObject(name)
+ return this?.getByName(name)!!
+}
+
+infix fun <T : Any> NamedDomainObjectCollection<out T>?.shouldNotContainDomainObject(
+ name: String
+): NamedDomainObjectCollection<out T>? {
+ this shouldNot containDomainObject(name)
+ return this
+}
+
+private fun <T> containDomainObject(name: String): Matcher<NamedDomainObjectCollection<T>?> =
+ neverNullMatcher { value ->
+ MatcherResult(
+ name in value.names,
+ { "NamedDomainObjectCollection(${value.names}) should contain DomainObject named '$name'" },
+ { "NamedDomainObjectCollection(${value.names}) should not contain DomainObject named '$name'" })
+ }
+
+/** Assert that a task ran. */
+infix fun BuildResult?.shouldHaveRunTask(taskPath: String): BuildTask {
+ this should haveTask(taskPath)
+ return this?.task(taskPath)!!
+}
+
+/** Assert that a task ran, with an [expected outcome][expectedOutcome]. */
+fun BuildResult?.shouldHaveRunTask(
+ taskPath: String,
+ expectedOutcome: TaskOutcome
+): BuildTask {
+ this should haveTask(taskPath)
+ val task = this?.task(taskPath)!!
+ task should haveOutcome(expectedOutcome)
+ return task
+}
+
+/**
+ * Assert that a task did not run.
+ *
+ * A task might not have run if one of its dependencies failed before it could be run.
+ */
+infix fun BuildResult?.shouldNotHaveRunTask(taskPath: String) {
+ this shouldNot haveTask(taskPath)
+}
+
+private fun haveTask(taskPath: String): Matcher<BuildResult?> =
+ neverNullMatcher { value ->
+ MatcherResult(
+ value.task(taskPath) != null,
+ { "BuildResult should have run task $taskPath. All tasks: ${value.tasks.joinToString { it.path }}" },
+ { "BuildResult should not have run task $taskPath. All tasks: ${value.tasks.joinToString { it.path }}" },
+ )
+ }
+
+
+infix fun BuildTask?.shouldHaveOutcome(outcome: TaskOutcome) {
+ this should haveOutcome(outcome)
+}
+
+
+infix fun BuildTask?.shouldHaveAnyOutcome(outcomes: Collection<TaskOutcome>) {
+ this should haveAnyOutcome(outcomes)
+}
+
+
+infix fun BuildTask?.shouldNotHaveOutcome(outcome: TaskOutcome) {
+ this shouldNot haveOutcome(outcome)
+}
+
+
+private fun haveOutcome(outcome: TaskOutcome): Matcher<BuildTask?> =
+ haveAnyOutcome(listOf(outcome))
+
+
+private fun haveAnyOutcome(outcomes: Collection<TaskOutcome>): Matcher<BuildTask?> {
+ val shouldHaveOutcome = when (outcomes.size) {
+ 0 -> error("Must provide 1 or more expected task outcome, but received none")
+ 1 -> "should have outcome ${outcomes.first().name}"
+ else -> "should have any outcome of ${outcomes.joinToString()}"
+ }
+
+ return neverNullMatcher { value ->
+ MatcherResult(
+ value.outcome in outcomes,
+ { "Task ${value.path} $shouldHaveOutcome, but was ${value.outcome}" },
+ { "Task ${value.path} $shouldHaveOutcome, but was ${value.outcome}" },
+ )
+ }
+}
+
+fun BuildResult.shouldHaveTaskWithOutcome(taskPath: String, outcome: TaskOutcome) {
+ this shouldHaveRunTask taskPath shouldHaveOutcome outcome
+}
+
+
+fun BuildResult.shouldHaveTaskWithAnyOutcome(taskPath: String, outcomes: Collection<TaskOutcome>) {
+ this shouldHaveRunTask taskPath shouldHaveAnyOutcome outcomes
+}
+
+fun BuildResult.shouldHaveTasksWithOutcome(
+ vararg taskPathToExpectedOutcome: Pair<String, TaskOutcome>
+) {
+ assertSoftly {
+ taskPathToExpectedOutcome.forEach { (taskPath, outcome) ->
+ shouldHaveTaskWithOutcome(taskPath, outcome)
+ }
+ }
+}
+
+fun BuildResult.shouldHaveTasksWithAnyOutcome(
+ vararg taskPathToExpectedOutcome: Pair<String, Collection<TaskOutcome>>
+) {
+ assertSoftly {
+ taskPathToExpectedOutcome.forEach { (taskPath, outcomes) ->
+ shouldHaveTaskWithAnyOutcome(taskPath, outcomes)
+ }
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestStringMatchers.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestStringMatchers.kt
new file mode 100644
index 00000000..58bbe768
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/kotestStringMatchers.kt
@@ -0,0 +1,65 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+import io.kotest.assertions.print.print
+import io.kotest.matchers.MatcherResult
+import io.kotest.matchers.neverNullMatcher
+import io.kotest.matchers.should
+import io.kotest.matchers.shouldNot
+
+
+infix fun String?.shouldContainAll(substrings: Iterable<String>): String? {
+ this should containAll(substrings)
+ return this
+}
+
+infix fun String?.shouldNotContainAll(substrings: Iterable<String>): String? {
+ this shouldNot containAll(substrings)
+ return this
+}
+
+fun String?.shouldContainAll(vararg substrings: String): String? {
+ this should containAll(substrings.asList())
+ return this
+}
+
+fun String?.shouldNotContainAll(vararg substrings: String): String? {
+ this shouldNot containAll(substrings.asList())
+ return this
+}
+
+private fun containAll(substrings: Iterable<String>) =
+ neverNullMatcher<String> { value ->
+ MatcherResult(
+ substrings.all { it in value },
+ { "${value.print().value} should include substrings ${substrings.print().value}" },
+ { "${value.print().value} should not include substrings ${substrings.print().value}" })
+ }
+
+
+infix fun String?.shouldContainAnyOf(substrings: Iterable<String>): String? {
+ this should containAnyOf(substrings)
+ return this
+}
+
+infix fun String?.shouldNotContainAnyOf(substrings: Iterable<String>): String? {
+ this shouldNot containAnyOf(substrings)
+ return this
+}
+
+fun String?.shouldContainAnyOf(vararg substrings: String): String? {
+ this should containAnyOf(substrings.asList())
+ return this
+}
+
+fun String?.shouldNotContainAnyOf(vararg substrings: String): String? {
+ this shouldNot containAnyOf(substrings.asList())
+ return this
+}
+
+private fun containAnyOf(substrings: Iterable<String>) =
+ neverNullMatcher<String> { value ->
+ MatcherResult(
+ substrings.any { it in value },
+ { "${value.print().value} should include any of these substrings ${substrings.print().value}" },
+ { "${value.print().value} should not include any of these substrings ${substrings.print().value}" })
+ }
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/samWithReceiverWorkarounds.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/samWithReceiverWorkarounds.kt
new file mode 100644
index 00000000..62cd5860
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/samWithReceiverWorkarounds.kt
@@ -0,0 +1,77 @@
+@file:Suppress("FunctionName")
+
+package org.jetbrains.dokka.dokkatoo.utils
+
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaPackageOptionsSpec
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceLinkSpec
+import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaSourceSetSpec
+import org.gradle.api.DomainObjectCollection
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.NamedDomainObjectProvider
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.DependencySet
+
+
+/**
+ * Workarounds because `SamWithReceiver` not working in test sources
+ * https://youtrack.jetbrains.com/issue/KTIJ-14684
+ *
+ * The `SamWithReceiver` plugin is automatically applied by the `kotlin-dsl` plugin.
+ * It converts all [org.gradle.api.Action] so the parameter is the receiver:
+ *
+ * ```
+ * // with SamWithReceiver ✅
+ * tasks.configureEach {
+ * val task: Task = this
+ * }
+ *
+ * // without SamWithReceiver
+ * tasks.configureEach { it ->
+ * val task: Task = it
+ * }
+ * ```
+ *
+ * This is nice because it means that the Dokka Gradle Plugin more closely matches `build.gradle.kts` files.
+ *
+ * However, [IntelliJ is bugged](https://youtrack.jetbrains.com/issue/KTIJ-14684) and doesn't
+ * acknowledge that `SamWithReceiver` has been applied in test sources. The code works and compiles,
+ * but IntelliJ shows red errors.
+ *
+ * These functions are workarounds, and should be removed ASAP.
+ */
+@Suppress("unused")
+private object Explain
+
+fun Project.subprojects_(configure: Project.() -> Unit) =
+ subprojects(configure)
+
+@Suppress("SpellCheckingInspection")
+fun Project.allprojects_(configure: Project.() -> Unit) =
+ allprojects(configure)
+
+fun <T> DomainObjectCollection<T>.configureEach_(configure: T.() -> Unit) =
+ configureEach(configure)
+
+fun <T> DomainObjectCollection<T>.all_(configure: T.() -> Unit) =
+ all(configure)
+
+fun Configuration.withDependencies_(action: DependencySet.() -> Unit): Configuration =
+ withDependencies(action)
+
+fun <T> NamedDomainObjectContainer<T>.create_(name: String, configure: T.() -> Unit = {}): T =
+ create(name, configure)
+
+fun <T> NamedDomainObjectContainer<T>.register_(
+ name: String,
+ configure: T.() -> Unit
+): NamedDomainObjectProvider<T> =
+ register(name, configure)
+
+fun DokkaSourceSetSpec.sourceLink_(
+ action: DokkaSourceLinkSpec.() -> Unit
+): Unit = sourceLink(action)
+
+fun DokkaSourceSetSpec.perPackageOption_(
+ action: DokkaPackageOptionsSpec.() -> Unit
+): Unit = perPackageOption(action)
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/stringUtils.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/stringUtils.kt
new file mode 100644
index 00000000..eb8777e7
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/stringUtils.kt
@@ -0,0 +1,21 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+
+fun String.splitToPair(delimiter: String): Pair<String, String> =
+ substringBefore(delimiter) to substringAfter(delimiter)
+
+
+/** Title case the first char of a string */
+fun String.uppercaseFirstChar(): String = mapFirstChar(Character::toTitleCase)
+
+
+private inline fun String.mapFirstChar(
+ transform: (Char) -> Char
+): String = if (isNotEmpty()) transform(this[0]) + substring(1) else this
+
+
+/** Split a string into lines, sort the lines, and re-join them (using [separator]). */
+fun String.sortLines(separator: String = "\n") =
+ lines()
+ .sorted()
+ .joinToString(separator)
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/systemVariableProviders.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/systemVariableProviders.kt
new file mode 100644
index 00000000..b15b3edb
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/systemVariableProviders.kt
@@ -0,0 +1,40 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+import kotlin.properties.ReadOnlyProperty
+
+// Utilities for fetching System Properties and Environment Variables via delegated properties
+
+
+internal fun optionalSystemProperty() = optionalSystemProperty { it }
+
+internal fun <T : Any> optionalSystemProperty(
+ convert: (String) -> T?
+): ReadOnlyProperty<Any, T?> =
+ ReadOnlyProperty { _, property ->
+ val value = System.getProperty(property.name)
+ if (value != null) convert(value) else null
+ }
+
+
+internal fun systemProperty() = systemProperty { it }
+
+internal fun <T> systemProperty(
+ convert: (String) -> T
+): ReadOnlyProperty<Any, T> =
+ ReadOnlyProperty { _, property ->
+ val value = requireNotNull(System.getProperty(property.name)) {
+ "system property ${property.name} is unavailable"
+ }
+ convert(value)
+ }
+
+
+internal fun optionalEnvironmentVariable() = optionalEnvironmentVariable { it }
+
+internal fun <T : Any> optionalEnvironmentVariable(
+ convert: (String) -> T?
+): ReadOnlyProperty<Any, T?> =
+ ReadOnlyProperty { _, property ->
+ val value = System.getenv(property.name)
+ if (value != null) convert(value) else null
+ }
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/text.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/text.kt
new file mode 100644
index 00000000..ce0ebd9d
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFixtures/kotlin/text.kt
@@ -0,0 +1,24 @@
+package org.jetbrains.dokka.dokkatoo.utils
+
+/** Replace all newlines with `\n`, so the String can be used in assertions cross-platform */
+fun String.invariantNewlines(): String =
+ lines().joinToString("\n")
+
+fun Pair<String, String>.sideBySide(
+ buffer: String = " ",
+): String {
+ val (left, right) = this
+
+ val leftLines = left.lines()
+ val rightLines = right.lines()
+
+ val maxLeftWidth = leftLines.maxOf { it.length }
+
+ return (0..maxOf(leftLines.size, rightLines.size)).joinToString("\n") { i ->
+
+ val leftLine = (leftLines.getOrNull(i) ?: "").padEnd(maxLeftWidth, ' ')
+ val rightLine = rightLines.getOrNull(i) ?: ""
+
+ leftLine + buffer + rightLine
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/DokkatooPluginFunctionalTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/DokkatooPluginFunctionalTest.kt
new file mode 100644
index 00000000..90d587ce
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/DokkatooPluginFunctionalTest.kt
@@ -0,0 +1,205 @@
+package org.jetbrains.dokka.dokkatoo
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooConstants.DOKKATOO_VERSION
+import org.jetbrains.dokka.dokkatoo.utils.*
+import io.kotest.assertions.asClue
+import io.kotest.assertions.withClue
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
+import io.kotest.matchers.string.shouldContain
+
+class DokkatooPluginFunctionalTest : FunSpec({
+ val testProject = gradleKtsProjectTest("DokkatooPluginFunctionalTest") {
+ buildGradleKts = """
+ |plugins {
+ | id("org.jetbrains.dokka.dokkatoo") version "$DOKKATOO_VERSION"
+ |}
+ |
+ """.trimMargin()
+ }
+
+ test("expect Dokka Plugin creates Dokka tasks") {
+ testProject.runner
+ .addArguments("tasks", "--group=dokkatoo", "-q")
+ .build {
+ withClue(output) {
+ val dokkatooTasks = output
+ .substringAfter("Dokkatoo tasks")
+ .lines()
+ .filter { it.contains(" - ") }
+ .associate { it.splitToPair(" - ") }
+
+ dokkatooTasks.shouldContainExactly(
+ //@formatter:off
+ "dokkatooGenerate" to "Generates Dokkatoo publications for all formats",
+ "dokkatooGenerateModuleGfm" to "Executes the Dokka Generator, generating a gfm module",
+ "dokkatooGenerateModuleHtml" to "Executes the Dokka Generator, generating a html module",
+ "dokkatooGenerateModuleJavadoc" to "Executes the Dokka Generator, generating a javadoc module",
+ "dokkatooGenerateModuleJekyll" to "Executes the Dokka Generator, generating a jekyll module",
+ "dokkatooGeneratePublicationGfm" to "Executes the Dokka Generator, generating the gfm publication",
+ "dokkatooGeneratePublicationHtml" to "Executes the Dokka Generator, generating the html publication",
+ "dokkatooGeneratePublicationJavadoc" to "Executes the Dokka Generator, generating the javadoc publication",
+ "dokkatooGeneratePublicationJekyll" to "Executes the Dokka Generator, generating the jekyll publication",
+ "prepareDokkatooModuleDescriptorGfm" to "Prepares the Dokka Module Descriptor for gfm",
+ "prepareDokkatooModuleDescriptorHtml" to "Prepares the Dokka Module Descriptor for html",
+ "prepareDokkatooModuleDescriptorJavadoc" to "Prepares the Dokka Module Descriptor for javadoc",
+ "prepareDokkatooModuleDescriptorJekyll" to "Prepares the Dokka Module Descriptor for jekyll",
+ //@formatter:on
+ )
+ }
+ }
+ }
+
+ test("expect Dokka Plugin creates Dokka outgoing variants") {
+ val build = testProject.runner
+ .addArguments("outgoingVariants", "-q")
+ .build {
+ val variants = output.invariantNewlines().replace('\\', '/')
+
+ val dokkatooVariants = variants.lines()
+ .filter { it.contains("dokka", ignoreCase = true) }
+ .mapNotNull { it.substringAfter("Variant ", "").takeIf(String::isNotBlank) }
+
+
+ dokkatooVariants.shouldContainExactlyInAnyOrder(
+ "dokkatooModuleElementsGfm",
+ "dokkatooModuleElementsHtml",
+ "dokkatooModuleElementsJavadoc",
+ "dokkatooModuleElementsJekyll",
+ )
+
+ fun checkVariant(format: String) {
+ val formatCapitalized = format.uppercaseFirstChar()
+
+ variants shouldContain /* language=text */ """
+ |--------------------------------------------------
+ |Variant dokkatooModuleElements$formatCapitalized
+ |--------------------------------------------------
+ |Provide Dokka Module files for $format to other subprojects
+ |
+ |Capabilities
+ | - :test:unspecified (default capability)
+ |Attributes
+ | - org.jetbrains.dokka.dokkatoo.base = dokkatoo
+ | - org.jetbrains.dokka.dokkatoo.category = module-files
+ | - org.jetbrains.dokka.dokkatoo.format = $format
+ |Artifacts
+ | - build/dokka-config/$format/module_descriptor.json (artifactType = json)
+ | - build/dokka-module/$format (artifactType = directory)
+ |
+ """.trimMargin()
+ }
+
+ checkVariant("gfm")
+ checkVariant("html")
+ checkVariant("javadoc")
+ checkVariant("jekyll")
+ }
+ }
+
+ test("expect Dokka Plugin creates Dokka resolvable configurations") {
+
+ val expectedFormats = listOf("Gfm", "Html", "Javadoc", "Jekyll")
+
+ testProject.runner
+ .addArguments("resolvableConfigurations", "-q")
+ .build {
+ output.invariantNewlines().asClue { allConfigurations ->
+
+ val dokkatooConfigurations = allConfigurations.lines()
+ .filter { it.contains("dokka", ignoreCase = true) }
+ .mapNotNull { it.substringAfter("Configuration ", "").takeIf(String::isNotBlank) }
+
+ dokkatooConfigurations.shouldContainExactlyInAnyOrder(
+ buildList {
+ add("dokkatoo")
+
+ addAll(expectedFormats.map { "dokkatooModule$it" })
+ addAll(expectedFormats.map { "dokkatooGeneratorClasspath$it" })
+ addAll(expectedFormats.map { "dokkatooPlugin$it" })
+ addAll(expectedFormats.map { "dokkatooPluginIntransitive$it" })
+ }
+ )
+
+ withClue("Configuration dokka") {
+ output.invariantNewlines() shouldContain /* language=text */ """
+ |--------------------------------------------------
+ |Configuration dokkatoo
+ |--------------------------------------------------
+ |Fetch all Dokkatoo files from all configurations in other subprojects
+ |
+ |Attributes
+ | - org.jetbrains.dokka.dokkatoo.base = dokkatoo
+ |
+ """.trimMargin()
+ }
+
+ fun checkConfigurations(format: String) {
+ val formatLowercase = format.lowercase()
+
+ allConfigurations shouldContain /* language=text */ """
+ |--------------------------------------------------
+ |Configuration dokkatooGeneratorClasspath$format
+ |--------------------------------------------------
+ |Dokka Generator runtime classpath for $formatLowercase - will be used in Dokka Worker. Should contain all transitive dependencies, plugins (and their transitive dependencies), so Dokka Worker can run.
+ |
+ |Attributes
+ | - org.jetbrains.dokka.dokkatoo.base = dokkatoo
+ | - org.jetbrains.dokka.dokkatoo.category = generator-classpath
+ | - org.jetbrains.dokka.dokkatoo.format = $formatLowercase
+ | - org.gradle.category = library
+ | - org.gradle.dependency.bundling = external
+ | - org.gradle.jvm.environment = standard-jvm
+ | - org.gradle.libraryelements = jar
+ | - org.gradle.usage = java-runtime
+ |Extended Configurations
+ | - dokkatooPlugin$format
+ |
+ """.trimMargin()
+
+ allConfigurations shouldContain /* language=text */ """
+ |--------------------------------------------------
+ |Configuration dokkatooPlugin$format
+ |--------------------------------------------------
+ |Dokka Plugins classpath for $formatLowercase
+ |
+ |Attributes
+ | - org.jetbrains.dokka.dokkatoo.base = dokkatoo
+ | - org.jetbrains.dokka.dokkatoo.category = plugins-classpath
+ | - org.jetbrains.dokka.dokkatoo.format = $formatLowercase
+ | - org.gradle.category = library
+ | - org.gradle.dependency.bundling = external
+ | - org.gradle.jvm.environment = standard-jvm
+ | - org.gradle.libraryelements = jar
+ | - org.gradle.usage = java-runtime
+ |
+ """.trimMargin()
+
+ allConfigurations shouldContain /* language=text */ """
+ |--------------------------------------------------
+ |Configuration dokkatooPluginIntransitive$format
+ |--------------------------------------------------
+ |Dokka Plugins classpath for $formatLowercase - for internal use. Fetch only the plugins (no transitive dependencies) for use in the Dokka JSON Configuration.
+ |
+ |Attributes
+ | - org.jetbrains.dokka.dokkatoo.base = dokkatoo
+ | - org.jetbrains.dokka.dokkatoo.category = plugins-classpath
+ | - org.jetbrains.dokka.dokkatoo.format = $formatLowercase
+ | - org.gradle.category = library
+ | - org.gradle.dependency.bundling = external
+ | - org.gradle.jvm.environment = standard-jvm
+ | - org.gradle.libraryelements = jar
+ | - org.gradle.usage = java-runtime
+ |Extended Configurations
+ | - dokkatooPlugin$format
+ |
+ """.trimMargin()
+ }
+
+ expectedFormats.forEach {
+ checkConfigurations(it)
+ }
+ }
+ }
+ }
+})
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/GradlePluginProjectIntegrationTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/GradlePluginProjectIntegrationTest.kt
new file mode 100644
index 00000000..d35150a2
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/GradlePluginProjectIntegrationTest.kt
@@ -0,0 +1,110 @@
+package org.jetbrains.dokka.dokkatoo
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooConstants
+import org.jetbrains.dokka.dokkatoo.utils.*
+import io.kotest.assertions.withClue
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.inspectors.shouldForAll
+import io.kotest.matchers.sequences.shouldNotBeEmpty
+import io.kotest.matchers.string.shouldContain
+import io.kotest.matchers.string.shouldNotContain
+
+class GradlePluginProjectIntegrationTest : FunSpec({
+
+ context("given a gradle plugin project") {
+ val project = initGradlePluginProject()
+
+ project.runner
+ .addArguments(
+ "clean",
+ "dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ )
+ .forwardOutput()
+ .build {
+
+ test("expect project builds successfully") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+
+ 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.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 initGradlePluginProject(
+ config: GradleProjectTest.() -> Unit = {},
+): GradleProjectTest {
+ return gradleKtsProjectTest("gradle-plugin-project") {
+
+ settingsGradleKts += """
+ |
+ """.trimMargin()
+
+ buildGradleKts = """
+ |plugins {
+ | `kotlin-dsl`
+ | id("org.jetbrains.dokka.dokkatoo") version "${DokkatooConstants.DOKKATOO_VERSION}"
+ |}
+ |
+ """.trimMargin()
+
+ dir("src/main/kotlin") {
+
+ createKotlinFile(
+ "MyCustomGradlePlugin.kt",
+ """
+ |package com.project.gradle.plugin
+ |
+ |import javax.inject.Inject
+ |import org.gradle.api.Plugin
+ |import org.gradle.api.Project
+ |import org.gradle.api.model.ObjectFactory
+ |import org.gradle.kotlin.dsl.*
+ |
+ |abstract class MyCustomGradlePlugin @Inject constructor(
+ | private val objects: ObjectFactory
+ |) : Plugin<Project> {
+ | override fun apply(project: Project) {
+ | println(objects.property<String>().getOrElse("empty"))
+ | }
+ |}
+
+ """.trimMargin()
+ )
+
+ createKotlinFile(
+ "MyCustomGradlePluginExtension.kt",
+ """
+ |package com.project.gradle.plugin
+ |
+ |import org.gradle.api.provider.*
+ |
+ |interface MyCustomGradlePluginExtension {
+ | val versionProperty: Property<String>
+ | val versionProvider: Provider<String>
+ |}
+ |
+ """.trimMargin()
+ )
+ }
+
+ config()
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt
new file mode 100644
index 00000000..23a6744c
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/KotlinMultiplatformFunctionalTest.kt
@@ -0,0 +1,247 @@
+package org.jetbrains.dokka.dokkatoo
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooConstants
+import org.jetbrains.dokka.dokkatoo.utils.*
+import io.kotest.assertions.withClue
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.inspectors.shouldForAll
+import io.kotest.matchers.file.shouldBeAFile
+import io.kotest.matchers.paths.shouldBeAFile
+import io.kotest.matchers.sequences.shouldNotBeEmpty
+import io.kotest.matchers.string.shouldContain
+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"
+ }
+ }
+
+ 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" }
+
+ 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") }
+ }
+ }
+ }
+ }
+ }
+ }
+})
+
+
+private fun initKotlinMultiplatformProject(
+ config: GradleProjectTest.() -> Unit = {},
+): GradleProjectTest {
+ return gradleKtsProjectTest("kotlin-multiplatform-project") {
+
+ settingsGradleKts += """
+ |
+ |dependencyResolutionManagement {
+ |
+ | repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
+ |
+ | repositories {
+ | mavenCentral()
+ |
+ | // Declare the Node.js & Yarn download repositories
+ | exclusiveContent {
+ | forRepository {
+ | ivy("https://nodejs.org/dist/") {
+ | name = "Node Distributions at ${'$'}url"
+ | patternLayout { artifact("v[revision]/[artifact](-v[revision]-[classifier]).[ext]") }
+ | metadataSources { artifact() }
+ | content { includeModule("org.nodejs", "node") }
+ | }
+ | }
+ | filter { includeGroup("org.nodejs") }
+ | }
+ |
+ | exclusiveContent {
+ | forRepository {
+ | ivy("https://github.com/yarnpkg/yarn/releases/download") {
+ | name = "Node Distributions at ${'$'}url"
+ | patternLayout { artifact("v[revision]/[artifact](-v[revision]).[ext]") }
+ | metadataSources { artifact() }
+ | content { includeModule("com.yarnpkg", "yarn") }
+ | }
+ | }
+ | filter { includeGroup("com.yarnpkg") }
+ | }
+ | }
+ |}
+ |
+ """.trimMargin()
+
+ buildGradleKts = """
+ |plugins {
+ | kotlin("multiplatform") version "1.8.22"
+ | id("org.jetbrains.dokka.dokkatoo") version "${DokkatooConstants.DOKKATOO_VERSION}"
+ |}
+ |
+ |kotlin {
+ | jvm()
+ | js(IR) {
+ | browser()
+ | }
+ |
+ | sourceSets {
+ | commonMain {
+ | dependencies {
+ | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
+ | }
+ | }
+ | commonTest {
+ | dependencies {
+ | implementation(kotlin("test"))
+ | }
+ | }
+ | }
+ |}
+ |
+ |dependencies {
+ | // must manually add this dependency for aggregation to work
+ | //dokkatooPluginHtml("org.jetbrains.dokka:all-modules-page-plugin:1.8.10")
+ |}
+ |
+ |dokkatoo {
+ | dokkatooSourceSets.configureEach {
+ | externalDocumentationLinks {
+ | create("kotlinxSerialization") {
+ | url("https://kotlinlang.org/api/kotlinx.serialization/")
+ | }
+ | }
+ | }
+ |}
+ |
+ |
+ """.trimMargin()
+
+ dir("src/commonMain/kotlin/") {
+
+ createKotlinFile(
+ "Hello.kt",
+ """
+ |package com.project
+ |
+ |import kotlinx.serialization.json.JsonObject
+ |
+ |/** The Hello class */
+ |class Hello {
+ | /** prints `Hello` and [json] to the console */
+ | fun sayHello(json: JsonObject) = println("Hello ${'$'}json")
+ |}
+ |
+ """.trimMargin()
+ )
+
+ createKotlinFile(
+ "goodbye.kt",
+ """
+ |package com.project
+ |
+ |import kotlinx.serialization.json.JsonObject
+ |
+ |/** Should print `goodbye` and [json] to the console */
+ |expect fun goodbye(json: JsonObject)
+ |
+ """.trimMargin()
+ )
+ }
+
+ 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/jsMain/kotlin/") {
+ createKotlinFile(
+ "goodbyeJs.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")
+ |
+ """.trimMargin()
+ )
+ }
+
+ config()
+ }
+}
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/MultiModuleFunctionalTest.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/MultiModuleFunctionalTest.kt
new file mode 100644
index 00000000..cac20f69
--- /dev/null
+++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/testFunctional/kotlin/MultiModuleFunctionalTest.kt
@@ -0,0 +1,468 @@
+package org.jetbrains.dokka.dokkatoo
+
+import org.jetbrains.dokka.dokkatoo.internal.DokkatooConstants.DOKKATOO_VERSION
+import org.jetbrains.dokka.dokkatoo.utils.*
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.inspectors.shouldForAll
+import io.kotest.matchers.collections.shouldBeIn
+import io.kotest.matchers.collections.shouldContainAll
+import io.kotest.matchers.file.shouldBeAFile
+import io.kotest.matchers.paths.shouldBeAFile
+import io.kotest.matchers.paths.shouldNotExist
+import io.kotest.matchers.string.shouldBeEmpty
+import io.kotest.matchers.string.shouldContain
+import org.gradle.testkit.runner.TaskOutcome.*
+
+class MultiModuleFunctionalTest : FunSpec({
+
+ context("when dokkatoo generates all formats") {
+ val project = initDokkatooProject("all-formats")
+
+ project.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGenerate",
+ "--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" }
+ .shouldForAll { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldBeAFile()
+ dokkaWorkerLog.readText().shouldNotContainAnyOf(
+ "[ERROR]",
+ "[WARN]",
+ )
+ }
+ }
+
+ context("expect HTML site is generated") {
+
+ test("with expected HTML files") {
+ project.file("subproject/build/dokka/html/index.html").shouldBeAFile()
+ project.file("subproject/build/dokka/html/com/project/hello/Hello.html")
+ .shouldBeAFile()
+ }
+
+ test("and dokka_parameters.json is generated") {
+ project.file("subproject/build/dokka/html/dokka_parameters.json")
+ .shouldBeAFile()
+ }
+
+ test("with element-list") {
+ project.file("build/dokka/html/package-list").shouldBeAFile()
+ project.file("build/dokka/html/package-list").toFile().readText()
+ .shouldContain( /* language=text */ """
+ |${'$'}dokka.format:html-v1
+ |${'$'}dokka.linkExtension:html
+ |
+ |module:subproject-hello
+ |com.project.hello
+ |module:subproject-goodbye
+ |com.project.goodbye
+ """.trimMargin()
+ )
+ }
+ }
+ }
+
+ context("Gradle caching") {
+
+ context("expect Dokkatoo is compatible with Gradle Build Cache") {
+ val project = initDokkatooProject("build-cache")
+
+ test("expect clean is successful") {
+ project.runner.addArguments("clean").build {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+ }
+
+ project.runner
+ .addArguments(
+ //"clean",
+ ":dokkatooGenerate",
+ "--stacktrace",
+ "--build-cache",
+ )
+ .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" }
+ .shouldForAll { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldBeAFile()
+ dokkaWorkerLog.readText().shouldNotContainAnyOf(
+ "[ERROR]",
+ "[WARN]",
+ )
+ }
+ }
+ }
+
+ context("when build cache is enabled") {
+ project.runner
+ .addArguments(
+ ":dokkatooGenerate",
+ "--stacktrace",
+ "--build-cache",
+ )
+ .forwardOutput()
+ .build {
+ test("expect build is successful") {
+ output shouldContainAll listOf(
+ "BUILD SUCCESSFUL",
+ "24 actionable tasks: 24 up-to-date",
+ )
+ }
+
+ test("expect all dokkatoo tasks are up-to-date") {
+ tasks
+ .filter { task ->
+ task.name.contains("dokkatoo", ignoreCase = true)
+ }
+ .shouldForAll { task ->
+ task.outcome.shouldBeIn(FROM_CACHE, UP_TO_DATE, SKIPPED)
+ }
+ }
+ }
+ }
+ }
+
+ context("Gradle Configuration Cache") {
+ val project = initDokkatooProject("config-cache")
+
+ test("expect clean is successful") {
+ project.runner.addArguments("clean").build {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+ }
+
+ project.runner
+ .addArguments(
+ //"clean",
+ ":dokkatooGenerate",
+ "--stacktrace",
+ "--no-build-cache",
+ "--configuration-cache",
+ )
+ .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" }
+ .shouldForAll { dokkaWorkerLog ->
+ dokkaWorkerLog.shouldBeAFile()
+ dokkaWorkerLog.readText().shouldNotContainAnyOf(
+ "[ERROR]",
+ "[WARN]",
+ )
+ }
+ }
+ }
+
+
+ context("expect updates in subprojects re-run tasks") {
+
+ val project = initDokkatooProject("submodule-update")
+
+ test("expect clean is successful") {
+ project.runner.addArguments("clean").build {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+ }
+
+ test("expect first build is successful") {
+ project.runner
+ .addArguments(
+ //"clean",
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--build-cache",
+ )
+ .forwardOutput()
+ .build {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+ }
+
+ context("and when a file in a subproject changes") {
+
+ val helloAgainIndexHtml =
+ @Suppress("KDocUnresolvedReference")
+ project.createKotlinFile(
+ "subproject-hello/src/main/kotlin/HelloAgain.kt",
+ """
+ |package com.project.hello
+ |
+ |/** Like [Hello], but again */
+ |class HelloAgain {
+ | /** prints `Hello Again` to the console */
+ | fun sayHelloAgain() = println("Hello Again")
+ |}
+ |
+ """.trimMargin()
+ ).toPath()
+
+ context("expect Dokka re-generates the publication") {
+ project.runner
+ .addArguments(
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--build-cache",
+ )
+ .forwardOutput()
+ .build {
+
+ test("expect HelloAgain HTML file exists") {
+ helloAgainIndexHtml.shouldBeAFile()
+ }
+
+ test("expect :subproject-goodbye tasks are up-to-date, because no files changed") {
+ shouldHaveTasksWithOutcome(
+ ":subproject-goodbye:dokkatooGenerateModuleHtml" to UP_TO_DATE,
+ ":subproject-goodbye:prepareDokkatooModuleDescriptorHtml" to UP_TO_DATE,
+ )
+ }
+
+ val successfulOutcomes = listOf(SUCCESS, FROM_CACHE)
+ test("expect :subproject-hello tasks should be re-run, since a file changed") {
+ shouldHaveTasksWithAnyOutcome(
+ ":subproject-hello:dokkatooGenerateModuleHtml" to successfulOutcomes,
+ ":subproject-hello:prepareDokkatooModuleDescriptorHtml" to successfulOutcomes,
+ )
+ }
+
+ test("expect aggregating tasks should re-run because the :subproject-hello Dokka Module changed") {
+ shouldHaveTasksWithAnyOutcome(
+ ":dokkatooGeneratePublicationHtml" to successfulOutcomes,
+ )
+ }
+
+ test("expect build is successful") {
+ output shouldContain "BUILD SUCCESSFUL"
+ }
+
+ test("expect 5 tasks are run") {
+ output shouldContain "5 actionable tasks"
+ }
+ }
+
+ context("and when the class is deleted") {
+ project.dir("subproject-hello") {
+ require(file("src/main/kotlin/HelloAgain.kt").toFile().delete()) {
+ "failed to delete HelloAgain.kt"
+ }
+ }
+
+ project.runner
+ .addArguments(
+ ":dokkatooGeneratePublicationHtml",
+ "--stacktrace",
+ "--info",
+ "--build-cache",
+ )
+ .forwardOutput()
+ .build {
+
+ test("expect HelloAgain HTML file is now deleted") {
+ helloAgainIndexHtml.shouldNotExist()
+
+ project.dir("build/dokka/html/") {
+ projectDir.toTreeString().shouldNotContainAnyOf(
+ "hello-again",
+ "-hello-again/",
+ "-hello-again.html",
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ context("logging") {
+ val project = initDokkatooProject("logging")
+
+ test("expect no logs when built using --quiet log level") {
+
+ project.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGenerate",
+ "--no-configuration-cache",
+ "--no-build-cache",
+ "--quiet",
+ )
+ .forwardOutput()
+ .build {
+ output.shouldBeEmpty()
+ }
+ }
+
+ test("expect no Dokkatoo logs when built using lifecycle log level") {
+
+ project.runner
+ .addArguments(
+ "clean",
+ ":dokkatooGenerate",
+ "--no-configuration-cache",
+ "--no-build-cache",
+ "--no-parallel",
+ // no logging option => lifecycle log level
+ )
+ .forwardOutput()
+ .build {
+
+ // projects are only configured the first time TestKit runs, and annoyingly there's no
+ // easy way to force Gradle to re-configure the projects - so only check conditionally.
+ if ("Configure project" in output) {
+ output shouldContain /*language=text*/ """
+ ¦> Configure project :
+ ¦> Configure project :subproject-goodbye
+ ¦> Configure project :subproject-hello
+ ¦> Task :clean
+ """.trimMargin("¦")
+ }
+
+ output.lines()
+ .filter { it.startsWith("> Task :") }
+ .shouldContainAll(
+ "> Task :clean",
+ "> Task :dokkatooGenerate",
+ "> Task :dokkatooGenerateModuleGfm",
+ "> Task :dokkatooGenerateModuleHtml",
+ "> Task :dokkatooGenerateModuleJavadoc",
+ "> Task :dokkatooGenerateModuleJekyll",
+ "> Task :dokkatooGeneratePublicationGfm",
+ "> Task :dokkatooGeneratePublicationHtml",
+ "> Task :dokkatooGeneratePublicationJavadoc",
+ "> Task :dokkatooGeneratePublicationJekyll",
+ "> Task :subproject-goodbye:clean",
+ "> Task :subproject-goodbye:dokkatooGenerateModuleGfm",
+ "> Task :subproject-goodbye:dokkatooGenerateModuleHtml",
+ "> Task :subproject-goodbye:dokkatooGenerateModuleJavadoc",
+ "> Task :subproject-goodbye:dokkatooGenerateModuleJekyll",
+ "> Task :subproject-goodbye:prepareDokkatooModuleDescriptorGfm",
+ "> Task :subproject-goodbye:prepareDokkatooModuleDescriptorHtml",
+ "> Task :subproject-goodbye:prepareDokkatooModuleDescriptorJavadoc",
+ "> Task :subproject-goodbye:prepareDokkatooModuleDescriptorJekyll",
+ "> Task :subproject-hello:clean",
+ "> Task :subproject-hello:dokkatooGenerateModuleGfm",
+ "> Task :subproject-hello:dokkatooGenerateModuleHtml",
+ "> Task :subproject-hello:dokkatooGenerateModuleJavadoc",
+ "> Task :subproject-hello:dokkatooGenerateModuleJekyll",
+ "> Task :subproject-hello:prepareDokkatooModuleDescriptorGfm",
+ "> Task :subproject-hello:prepareDokkatooModuleDescriptorHtml",
+ "> Task :subproject-hello:prepareDokkatooModuleDescriptorJavadoc",
+ "> Task :subproject-hello:prepareDokkatooModuleDescriptorJekyll",
+ )
+ }
+ }
+ }
+})
+
+private fun initDokkatooProject(
+ testName: String,
+ config: GradleProjectTest.() -> Unit = {},
+): GradleProjectTest {
+ return gradleKtsProjectTest("multi-module-hello-goodbye/$testName") {
+
+ settingsGradleKts += """
+ |
+ |include(":subproject-hello")
+ |include(":subproject-goodbye")
+ |
+ """.trimMargin()
+
+ buildGradleKts = """
+ |plugins {
+ | // Kotlin plugin shouldn't be necessary here, but without it Dokka errors
+ | // with ClassNotFound KotlinPluginExtension... very weird
+ | kotlin("jvm") version "1.8.22" apply false
+ | id("org.jetbrains.dokka.dokkatoo") version "$DOKKATOO_VERSION"
+ |}
+ |
+ |dependencies {
+ | dokkatoo(project(":subproject-hello"))
+ | dokkatoo(project(":subproject-goodbye"))
+ | dokkatooPluginHtml(
+ | dokkatoo.versions.jetbrainsDokka.map { dokkaVersion ->
+ | "org.jetbrains.dokka:all-modules-page-plugin:${'$'}dokkaVersion"
+ | }
+ | )
+ |}
+ |
+ """.trimMargin()
+
+ dir("subproject-hello") {
+ buildGradleKts = """
+ |plugins {
+ | kotlin("jvm") version "1.8.22"
+ | id("org.jetbrains.dokka.dokkatoo") version "$DOKKATOO_VERSION"
+ |}
+ |
+ """.trimMargin()
+
+ createKotlinFile(
+ "src/main/kotlin/Hello.kt",
+ """
+ |package com.project.hello
+ |
+ |/** The Hello class */
+ |class Hello {
+ | /** prints `Hello` to the console */
+ | fun sayHello() = println("Hello")
+ |}
+ |
+ """.trimMargin()
+ )
+
+ createKotlinFile("src/main/kotlin/HelloAgain.kt", "")
+ }
+
+ dir("subproject-goodbye") {
+
+ buildGradleKts = """
+ |plugins {
+ | kotlin("jvm") version "1.8.22"
+ | id("org.jetbrains.dokka.dokkatoo") version "$DOKKATOO_VERSION"
+ |}
+ |
+ """.trimMargin()
+
+ createKotlinFile(
+ "src/main/kotlin/Goodbye.kt",
+ """
+ |package com.project.goodbye
+ |
+ |/** The Goodbye class */
+ |class Goodbye {
+ | /** prints a goodbye message to the console */
+ | fun sayHello() = println("Goodbye!")
+ |}
+ |
+ """.trimMargin()
+ )
+ }
+
+ config()
+ }
+}
diff --git a/dokka-runners/dokkatoo/settings.gradle.kts b/dokka-runners/dokkatoo/settings.gradle.kts
new file mode 100644
index 00000000..497f3e34
--- /dev/null
+++ b/dokka-runners/dokkatoo/settings.gradle.kts
@@ -0,0 +1,95 @@
+rootProject.name = "dokkatoo"
+
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+@Suppress("UnstableApiUsage")
+dependencyResolutionManagement {
+
+ repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
+
+ repositories {
+ mavenCentral()
+ google()
+
+ maven("https://www.jetbrains.com/intellij-repository/snapshots")
+ maven("https://www.jetbrains.com/intellij-repository/releases")
+ maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-ide")
+ maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-ide-plugin-dependencies")
+ maven("https://cache-redirector.jetbrains.com/intellij-dependencies")
+ maven("https://www.myget.org/F/rd-snapshots/maven/")
+
+ ivy("https://github.com/") {
+ name = "GitHub Release"
+ patternLayout {
+ artifact("[organization]/[module]/archive/[revision].[ext]")
+ artifact("[organization]/[module]/archive/refs/tags/[revision].[ext]")
+ artifact("[organization]/[module]/archive/refs/tags/v[revision].[ext]")
+ }
+ metadataSources { artifact() }
+ }
+ }
+}
+
+include(
+ ":examples",
+
+ ":modules:docs",
+ ":modules:dokkatoo-plugin",
+ ":modules:dokkatoo-plugin-integration-tests",
+)
+
+
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
+enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
+
+
+//if (file("./examples/build/tmp/prepareDokkaSource").exists()) {
+// includeBuild("./examples/build/tmp/prepareDokkaSource")
+//}
+
+// can only include one example project at a time https://github.com/gradle/gradle/issues/23939
+//@formatter:off
+//includeBuild(file("./examples/multiplatform-example/dokkatoo" )) { name = "multiplatform-example" }
+//includeBuild(file("./examples/kotlin-as-java-example/dokkatoo" )) { name = "kotlin-as-java-example" }
+//includeBuild(file("./examples/versioning-multimodule-example/dokkatoo" )) { name = "versioning-multimodule-example" }
+//includeBuild(file("./examples/custom-format-example/dokkatoo" )) { name = "custom-format-example" }
+//includeBuild(file("./examples/gradle-example/dokkatoo" )) { name = "gradle-example" }
+//includeBuild(file("./examples/library-publishing-example/dokkatoo" )) { name = "library-publishing-example" }
+//includeBuild(file("./examples/multimodule-example/dokkatoo" )) { name = "multimodule-example" }
+//includeBuild(file("./modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-1/dokkatoo" )) { name = "it-multimodule-1" }
+//includeBuild(file("./modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-0/dokkatoo" )) { name = "it-multimodule-0" }
+//includeBuild(file("./modules/dokkatoo-plugin-integration-tests/projects/it-collector-0/dokkatoo" )) { name = "it-collector-0" }
+//includeBuild(file("./modules/dokkatoo-plugin-integration-tests/projects/it-multimodule-versioning-0/dokkatoo" )) { name = "it-multimodule-versioning-0" }
+//includeBuild(file("./modules/dokkatoo-plugin-integration-tests/projects/it-android-0/dokkatoo" )) { name = "it-android-0" }
+//includeBuild(file("./modules/dokkatoo-plugin-integration-tests/projects/it-basic/dokkatoo" )) { name = "it-basic" }
+//includeBuild(file("./modules/dokkatoo-plugin-integration-tests/projects/it-multiplatform-0/dokkatoo" )) { name = "it-multiplatform-0" }
+//includeBuild(file("./modules/dokkatoo-plugin-integration-tests/projects/it-js-ir-0/dokkatoo" )) { name = "it-js-ir-0" }
+//includeBuild(file("./modules/dokkatoo-plugin-integration-tests/projects/it-basic-groovy/dokkatoo" )) { name = "it-basic-groovy" }
+//@formatter:on
+
+//listOf(
+// "examples",
+// "modules/dokkatoo-plugin-integration-tests/projects",
+//).forEach { exampleProjectDir ->
+// file(exampleProjectDir)
+// .walk()
+// .filter {
+// it.isDirectory
+// && it.name == "dokkatoo"
+// && (
+// it.resolve("settings.gradle.kts").exists()
+// ||
+// it.resolve("settings.gradle").exists()
+// )
+// }.forEach { file ->
+// includeBuild(file) {
+// name = file.parentFile.name
+// println("$file $name")
+// }
+// }
+//}