diff options
14 files changed, 263 insertions, 72 deletions
diff --git a/build.gradle.kts b/build.gradle.kts index cae9ecca..f1f66a9c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,11 @@ allprojects { val language_version: String by project tasks.withType(KotlinCompile::class).all { kotlinOptions { - freeCompilerArgs += "-Xjsr305=strict -Xskip-metadata-version-check -Xopt-in=kotlin.RequiresOptIn." + freeCompilerArgs = freeCompilerArgs + listOf( + "-Xopt-in=kotlin.RequiresOptIn", + "-Xskip-metadata-version-check", + "-Xjsr305=strict" + ) languageVersion = language_version apiVersion = language_version jvmTarget = "1.8" diff --git a/core/build.gradle.kts b/core/build.gradle.kts index fcb616e1..5051707b 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -3,15 +3,13 @@ import org.jetbrains.registerDokkaArtifactPublication plugins { `maven-publish` id("com.jfrog.bintray") - kotlin("plugin.serialization") } dependencies { api("org.jetbrains:markdown:0.1.45") implementation(kotlin("reflect")) - implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0-1.4-M3") - implementation("com.google.code.gson:gson:2.8.5") implementation("org.jsoup:jsoup:1.12.1") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.1") testImplementation(project(":testApi")) testImplementation(kotlin("test-junit")) diff --git a/core/src/main/kotlin/configuration.kt b/core/src/main/kotlin/configuration.kt index bdb493b8..08a3f07a 100644 --- a/core/src/main/kotlin/configuration.kt +++ b/core/src/main/kotlin/configuration.kt @@ -2,10 +2,8 @@ package org.jetbrains.dokka -import kotlinx.serialization.Serializable -import kotlinx.serialization.UnstableDefault -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonConfiguration +import org.jetbrains.dokka.utilities.toJsonString +import org.jetbrains.dokka.utilities.parseJson import java.io.File import java.net.URL @@ -52,7 +50,6 @@ enum class Platform(val key: String) { } } -@Serializable data class DokkaSourceSetID( val moduleName: String, val sourceSetName: String @@ -62,10 +59,12 @@ data class DokkaSourceSetID( } } -@OptIn(UnstableDefault::class) -fun DokkaConfigurationImpl(input: String): DokkaConfigurationImpl { - val json = Json(JsonConfiguration.Default.copy(ignoreUnknownKeys = true)) - return json.parse(DokkaConfigurationImpl.serializer(), input) +fun DokkaConfigurationImpl(json: String): DokkaConfigurationImpl { + return parseJson(json) +} + +fun DokkaConfiguration.toJsonString(): String { + return toJsonString(this) } interface DokkaConfiguration { diff --git a/core/src/main/kotlin/defaultConfiguration.kt b/core/src/main/kotlin/defaultConfiguration.kt index 4730bab4..8e38e8d5 100644 --- a/core/src/main/kotlin/defaultConfiguration.kt +++ b/core/src/main/kotlin/defaultConfiguration.kt @@ -1,23 +1,20 @@ package org.jetbrains.dokka -import kotlinx.serialization.* import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet import java.io.File import java.net.URL -@Serializable data class DokkaConfigurationImpl( override val outputDir: String = DokkaDefaults.outputDir, override val cacheRoot: String? = DokkaDefaults.cacheRoot, override val offlineMode: Boolean = DokkaDefaults.offlineMode, override val sourceSets: List<DokkaSourceSetImpl> = emptyList(), - override val pluginsClasspath: List<@Serializable(with = FileSerializer::class) File> = emptyList(), + override val pluginsClasspath: List<File> = emptyList(), override val pluginsConfiguration: Map<String, String> = emptyMap(), override val modules: List<DokkaModuleDescriptionImpl> = emptyList(), override val failOnWarning: Boolean = DokkaDefaults.failOnWarning ) : DokkaConfiguration -@Serializable data class DokkaSourceSetImpl( override val moduleDisplayName: String, override val displayName: String = DokkaDefaults.sourceSetDisplayName, @@ -44,19 +41,16 @@ data class DokkaSourceSetImpl( override val analysisPlatform: Platform = DokkaDefaults.analysisPlatform ) : DokkaSourceSet -@Serializable data class DokkaModuleDescriptionImpl( override val name: String, override val path: String, override val docFile: String ) : DokkaConfiguration.DokkaModuleDescription -@Serializable data class SourceRootImpl( override val path: String ) : DokkaConfiguration.SourceRoot -@Serializable data class SourceLinkDefinitionImpl( override val path: String, override val url: String, @@ -73,7 +67,6 @@ data class SourceLinkDefinitionImpl( } } -@Serializable data class PackageOptionsImpl( override val prefix: String, override val includeNonPublic: Boolean, @@ -82,28 +75,8 @@ data class PackageOptionsImpl( override val suppress: Boolean ) : DokkaConfiguration.PackageOptions -@Serializable + data class ExternalDocumentationLinkImpl( - @Serializable(with = URLSerializer::class) override val url: URL, - @Serializable(with = URLSerializer::class) override val packageListUrl: URL + override val url: URL, + override val packageListUrl: URL ) : DokkaConfiguration.ExternalDocumentationLink - -private object FileSerializer : KSerializer<File> { - override val descriptor: SerialDescriptor = PrimitiveDescriptor("File", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: File) { - encoder.encodeString(value.path) - } - - override fun deserialize(decoder: Decoder): File = File(decoder.decodeString()) -} - -private object URLSerializer : KSerializer<URL> { - override val descriptor: SerialDescriptor = PrimitiveDescriptor("URL", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: URL) { - encoder.encodeString(value.toString()) - } - - override fun deserialize(decoder: Decoder): URL = URL(decoder.decodeString()) -} diff --git a/core/src/main/kotlin/plugability/DokkaPlugin.kt b/core/src/main/kotlin/plugability/DokkaPlugin.kt index 2c755a49..a62327d2 100644 --- a/core/src/main/kotlin/plugability/DokkaPlugin.kt +++ b/core/src/main/kotlin/plugability/DokkaPlugin.kt @@ -1,7 +1,8 @@ package org.jetbrains.dokka.plugability -import com.google.gson.Gson import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.utilities.parseJson +import org.jetbrains.dokka.utilities.toJsonString import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 @@ -15,13 +16,12 @@ abstract class DokkaPlugin { protected inline fun <reified T : DokkaPlugin> plugin(): T = context?.plugin(T::class) ?: throwIllegalQuery() - protected fun <T : Any> extensionPoint() = - object : ReadOnlyProperty<DokkaPlugin, ExtensionPoint<T>> { - override fun getValue(thisRef: DokkaPlugin, property: KProperty<*>) = ExtensionPoint<T>( - thisRef::class.qualifiedName ?: throw AssertionError("Plugin must be named class"), - property.name - ) - } + protected fun <T : Any> extensionPoint() = ReadOnlyProperty<DokkaPlugin, ExtensionPoint<T>> { thisRef, property -> + ExtensionPoint( + thisRef::class.qualifiedName ?: throw AssertionError("Plugin must be named class"), + property.name + ) + } protected fun <T : Any> extending(definition: ExtendingDSL.() -> Extension<T, *, *>) = ExtensionProvider(definition) @@ -58,7 +58,7 @@ inline fun <reified P : DokkaPlugin, reified T : ConfigurableBlock> Configurable val instance = T::class.createInstance().apply(block) val mutablePluginsConfiguration = pluginsConfiguration as MutableMap<String, String> - mutablePluginsConfiguration[P::class.qualifiedName!!] = Gson().toJson(instance, T::class.java) + mutablePluginsConfiguration[P::class.qualifiedName!!] = toJsonString(instance) } inline fun <reified P : DokkaPlugin, reified E : Any> P.query(extension: P.() -> ExtensionPoint<E>): List<E> = @@ -71,12 +71,10 @@ fun throwIllegalQuery(): Nothing = throw IllegalStateException("Querying about plugins is only possible with dokka context initialised") inline fun <reified T : DokkaPlugin, reified R : ConfigurableBlock> configuration(context: DokkaContext): ReadOnlyProperty<Any?, R> { - return object : ReadOnlyProperty<Any?, R> { - override fun getValue(thisRef: Any?, property: KProperty<*>): R { - return context.configuration.pluginsConfiguration[T::class.qualifiedName - ?: throw AssertionError("Plugin must be named class")].let { - Gson().fromJson(it, R::class.java) - } - } + return ReadOnlyProperty { _, _ -> + val configuration = context.configuration.pluginsConfiguration[ + T::class.qualifiedName ?: throw AssertionError("Plugin must be named class") + ] + parseJson(checkNotNull(configuration)) } } diff --git a/core/src/main/kotlin/utilities/json.kt b/core/src/main/kotlin/utilities/json.kt new file mode 100644 index 00000000..d2e70d75 --- /dev/null +++ b/core/src/main/kotlin/utilities/json.kt @@ -0,0 +1,59 @@ +package org.jetbrains.dokka.utilities + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.* +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import java.io.File +import java.lang.reflect.Type +import com.fasterxml.jackson.core.type.TypeReference as JacksonTypeReference + +private val objectMapper = run { + val module = SimpleModule().apply { + addSerializer(FileSerializer) + } + jacksonObjectMapper() + .registerModule(module) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) +} + +@PublishedApi +internal class TypeReference<T> private constructor( + internal val jackson: JacksonTypeReference<T> +) { + companion object { + @OptIn(ExperimentalStdlibApi::class) + internal inline operator fun <reified T> invoke(): TypeReference<T> = TypeReference(jacksonTypeRef()) + } +} + +@PublishedApi +internal fun toJsonString(value: Any): String = objectMapper.writeValueAsString(value) + +@PublishedApi +internal inline fun <reified T : Any> parseJson(json: String): T { + return parseJson(json, TypeReference()) +} + +@PublishedApi +internal fun <T : Any> parseJson(json: String, typeReference: TypeReference<T>): T { + return objectMapper.readValue(json, typeReference.jackson) +} + +private object FileSerializer : StdScalarSerializer<File>(File::class.java) { + override fun serialize(value: File, g: JsonGenerator, provider: SerializerProvider) { + g.writeString(value.path) + } + + override fun getSchema(provider: SerializerProvider, typeHint: Type): JsonNode { + return createSchemaNode("string", true) + } + + @Throws(JsonMappingException::class) + override fun acceptJsonFormatVisitor(visitor: JsonFormatVisitorWrapper, typeHint: JavaType) { + visitStringFormat(visitor, typeHint) + } +} diff --git a/core/src/test/kotlin/utilities/DokkaConfigurationJsonTest.kt b/core/src/test/kotlin/utilities/DokkaConfigurationJsonTest.kt new file mode 100644 index 00000000..d0cb55a1 --- /dev/null +++ b/core/src/test/kotlin/utilities/DokkaConfigurationJsonTest.kt @@ -0,0 +1,68 @@ +package utilities + +import org.jetbrains.dokka.* +import java.io.File +import kotlin.test.Test +import kotlin.test.assertEquals + +class DokkaConfigurationJsonTest { + @Test + fun `simple configuration toJsonString then parseJson`() { + val configuration = DokkaConfigurationImpl( + outputDir = "customOutputDir", + pluginsClasspath = listOf(File("plugins/customPlugin.jar")), + sourceSets = listOf( + DokkaSourceSetImpl( + moduleDisplayName = "customModuleDisplayName", + sourceRoots = listOf(SourceRootImpl("customSourceRoot")), + sourceSetID = DokkaSourceSetID("customModuleName", "customSourceSetName") + ) + ) + ) + + val jsonString = configuration.toJsonString() + val parsedConfiguration = DokkaConfigurationImpl(jsonString) + assertEquals(configuration, parsedConfiguration) + } + + @Test + fun `parse simple configuration json`() { + val json = """ + { + "outputDir": "customOutputDir", + "pluginsClasspath": [ "plugins/customPlugin.jar" ], + "sourceSets": [ + { + "moduleDisplayName": "customModuleDisplayName", + "sourceSetID": { + "moduleName": "customModuleName", + "sourceSetName": "customSourceSetName" + }, + "classpath": [], + "sourceRoots": [ + { + "path": "customSourceRoot" + } + ] + } + ] + } + """.trimIndent() + + val parsedConfiguration = DokkaConfigurationImpl(json) + assertEquals( + DokkaConfigurationImpl( + outputDir = "customOutputDir", + pluginsClasspath = listOf(File("plugins/customPlugin.jar")), + sourceSets = listOf( + DokkaSourceSetImpl( + moduleDisplayName = "customModuleDisplayName", + sourceRoots = listOf(SourceRootImpl("customSourceRoot")), + sourceSetID = DokkaSourceSetID("customModuleName", "customSourceSetName") + ) + ) + ), + parsedConfiguration + ) + } +} diff --git a/runners/gradle-plugin/build.gradle.kts b/runners/gradle-plugin/build.gradle.kts index 0222f5e0..4ad1d144 100644 --- a/runners/gradle-plugin/build.gradle.kts +++ b/runners/gradle-plugin/build.gradle.kts @@ -11,6 +11,7 @@ repositories { dependencies { implementation(project(":core")) + compileOnly("com.fasterxml.jackson.core:jackson-annotations:2.11.1") compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin") compileOnly("com.android.tools.build:gradle:3.0.0") compileOnly("com.android.tools.build:gradle-core:3.0.0") diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt index 846f021c..1269b305 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaTask.kt @@ -18,7 +18,7 @@ abstract class AbstractDokkaTask : DefaultTask(), Configurable { var outputDirectory: String = defaultDokkaOutputDirectory().absolutePath @Input - override val pluginsConfiguration: Map<String, String> = mutableMapOf() + override val pluginsConfiguration: MutableMap<String, String> = mutableMapOf() @Classpath val plugins: Configuration = project.maybeCreateDokkaPluginConfiguration(name) diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt index 6fd58afe..986b883a 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaMultimoduleTask.kt @@ -1,11 +1,10 @@ package org.jetbrains.dokka.gradle -import com.google.gson.GsonBuilder -import org.gradle.api.plugins.JavaBasePlugin import org.gradle.api.plugins.JavaBasePlugin.DOCUMENTATION_GROUP import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.jetbrains.dokka.plugability.Configurable +import org.jetbrains.dokka.toJsonString open class DokkaMultimoduleTask : AbstractDokkaTask(), Configurable { @@ -23,9 +22,8 @@ open class DokkaMultimoduleTask : AbstractDokkaTask(), Configurable { override fun generate() { val bootstrap = DokkaBootstrap("org.jetbrains.dokka.DokkaMultimoduleBootstrapImpl") - val gson = GsonBuilder().setPrettyPrinting().create() val configuration = getConfiguration() - bootstrap.configure(gson.toJson(configuration)) { level, message -> + bootstrap.configure(configuration.toJsonString()) { level, message -> when (level) { "debug" -> logger.debug(message) "info" -> logger.info(message) diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt index 0d7e74a3..2253a975 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt @@ -1,6 +1,5 @@ package org.jetbrains.dokka.gradle -import com.google.gson.GsonBuilder import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.Task @@ -15,6 +14,7 @@ import org.jetbrains.dokka.Platform import org.jetbrains.dokka.ReflectDsl import org.jetbrains.dokka.ReflectDsl.isNotInstance import org.jetbrains.dokka.gradle.ConfigurationExtractor.PlatformData +import org.jetbrains.dokka.toJsonString import java.io.File import java.util.concurrent.Callable @@ -123,9 +123,7 @@ open class DokkaTask : AbstractDokkaTask() { outputDiagnosticInfo = true val bootstrap = DokkaBootstrap("org.jetbrains.dokka.DokkaBootstrapImpl") - bootstrap.configure( - GsonBuilder().setPrettyPrinting().create().toJson(configuration) - ) { level, message -> + bootstrap.configure(configuration.toJsonString()) { level, message -> when (level) { "debug" -> logger.debug(message) "info" -> logger.info(message) diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt index b6b8399c..84f35cb4 100644 --- a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt @@ -3,6 +3,7 @@ package org.jetbrains.dokka.gradle import com.android.build.gradle.api.AndroidSourceSet +import com.fasterxml.jackson.annotation.JsonIgnore import groovy.lang.Closure import org.gradle.api.Action import org.gradle.api.Project @@ -34,8 +35,8 @@ class GradleSourceRootImpl : SourceRoot, Serializable { } open class GradleDokkaSourceSet constructor( - @Transient @get:Input val name: String, - @Transient @get:Internal internal val project: Project + @get:JsonIgnore @Transient @get:Input val name: String, + @get:JsonIgnore @Transient @get:Internal internal val project: Project ) : DokkaSourceSet { @Input @@ -117,6 +118,7 @@ open class GradleDokkaSourceSet constructor( @Optional var platform: String? = null + @JsonIgnore @Internal @Transient var collectKotlinTasks: (() -> List<Any?>?)? = null diff --git a/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaConfigurationJsonTest.kt b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaConfigurationJsonTest.kt new file mode 100644 index 00000000..b6cf2119 --- /dev/null +++ b/runners/gradle-plugin/src/test/kotlin/org/jetbrains/dokka/gradle/GradleDokkaConfigurationJsonTest.kt @@ -0,0 +1,94 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.artifacts.FileCollectionDependency +import org.gradle.kotlin.dsl.withType +import org.gradle.testfixtures.ProjectBuilder +import org.jetbrains.dokka.* +import java.net.URL +import kotlin.test.Test +import kotlin.test.assertEquals + +class GradleDokkaConfigurationJsonTest { + + @Test + fun `DokkaTask configuration toJsonString then parseJson`() { + val project = ProjectBuilder.builder().build() + project.plugins.apply("org.jetbrains.dokka") + val dokkaTask = project.tasks.withType<DokkaTask>().first() + dokkaTask.plugins.withDependencies { dependencies -> + dependencies.clear() + } + dokkaTask.apply { + this.failOnWarning = true + this.offlineMode = true + this.outputDirectory = "customOutputDir" + this.cacheRoot = "customCacheRoot" + this.pluginsConfiguration["0"] = "a" + this.pluginsConfiguration["1"] = "b" + this.dokkaSourceSets.create("main") { sourceSet -> + sourceSet.moduleDisplayName = "moduleDisplayName" + sourceSet.displayName = "customSourceSetDisplayName" + sourceSet.reportUndocumented = true + + sourceSet.externalDocumentationLink { link -> + link.packageListUrl = URL("http://some.url") + link.url = URL("http://some.other.url") + } + sourceSet.collectKotlinTasks = { + println(this@GradleDokkaConfigurationJsonTest) + println("This lambda is capturing the entire test") + emptyList() + } + + sourceSet.perPackageOption { packageOption -> + packageOption.includeNonPublic = true + packageOption.reportUndocumented = true + packageOption.skipDeprecated = true + } + } + } + + val sourceConfiguration = dokkaTask.getConfigurationOrThrow() + val configurationJson = sourceConfiguration.toJsonString() + val parsedConfiguration = DokkaConfigurationImpl(configurationJson) + + assertEquals( + DokkaConfigurationImpl( + failOnWarning = sourceConfiguration.failOnWarning, + offlineMode = sourceConfiguration.offlineMode, + outputDir = sourceConfiguration.outputDir, + cacheRoot = sourceConfiguration.cacheRoot, + pluginsClasspath = emptyList(), + pluginsConfiguration = sourceConfiguration.pluginsConfiguration.toMap(), + sourceSets = listOf( + DokkaSourceSetImpl( + moduleDisplayName = sourceConfiguration.sourceSets.single().moduleDisplayName, + displayName = sourceConfiguration.sourceSets.single().displayName, + reportUndocumented = sourceConfiguration.sourceSets.single().reportUndocumented, + externalDocumentationLinks = sourceConfiguration.sourceSets.single().externalDocumentationLinks + .map { link -> + ExternalDocumentationLinkImpl( + url = link.url, + packageListUrl = link.packageListUrl + ) + }, + perPackageOptions = sourceConfiguration.sourceSets.single().perPackageOptions.map { option -> + PackageOptionsImpl( + prefix = option.prefix, + includeNonPublic = option.includeNonPublic, + reportUndocumented = option.reportUndocumented, + skipDeprecated = option.skipDeprecated, + suppress = option.suppress + ) + }, + sourceSetID = sourceConfiguration.sourceSets.single().sourceSetID, + sourceRoots = sourceConfiguration.sourceSets.single().sourceRoots.map { sourceRoot -> + SourceRootImpl(sourceRoot.path) + } + ) + ) + ), parsedConfiguration + ) + println(parsedConfiguration) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e5bad3a3..a9c50387 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,7 +26,6 @@ pluginManagement { val kotlin_version: String by settings plugins { id("org.jetbrains.kotlin.jvm") version kotlin_version - id("org.jetbrains.kotlin.plugin.serialization") version kotlin_version id("com.github.johnrengelman.shadow") version "5.2.0" id("com.jfrog.bintray") version "1.8.5" id("com.gradle.plugin-publish") version "0.12.0" |