aboutsummaryrefslogtreecommitdiff
path: root/dokka-runners/dokkatoo/modules/dokkatoo-plugin
diff options
context:
space:
mode:
Diffstat (limited to 'dokka-runners/dokkatoo/modules/dokkatoo-plugin')
-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
74 files changed, 7592 insertions, 0 deletions
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()
+ }
+}