From 2f12dbd887143a0a856faa536cccd22936dd21bd Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Sun, 18 Feb 2024 12:54:44 -0800 Subject: [PATCH 001/164] Fix dropped `implementSerializable` flag Fixes and closes apple/pkl#191 Signed-off-by: Sam Gammon --- .../org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt index 72e65eaab..2800fdea0 100644 --- a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt +++ b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt @@ -39,5 +39,5 @@ data class CliKotlinCodeGeneratorOptions( val implementSerializable: Boolean = false ) { fun toKotlinCodegenOptions(): KotlinCodegenOptions = - KotlinCodegenOptions(indent, generateKdoc, generateSpringBootConfig) + KotlinCodegenOptions(indent, generateKdoc, generateSpringBootConfig, implementSerializable) } From f6c5bf3e47f2bc1b152b03fb7c029cdd373be3c6 Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Tue, 20 Feb 2024 11:39:37 -0800 Subject: [PATCH 002/164] Add setting for Kotlin package to codegen Adds a setting to the Kotlin code generator which controls the target Kotlin package for codegen. Applied as a prefix to the normal generated package name. The current versions of the `kotlinx.serialization` and `kotlinx.html` libraries are declared dynamically, which causes a failure when refreshing dependencies. To avoid Kotlin metadata build issues, these have been pinned. - feat(codegen): use `kotlinPackage` as prefix for kotlin codegen - feat(gradle): `kotlinPackage` property in gradle plugin - test(codegen): add tests for custom kotlin package - test(gradle): add tests for generating with custom kotlin package Signed-off-by: Sam Gammon --- .../kotlin/CliKotlinCodeGeneratorOptions.kt | 11 ++- .../pkl/codegen/kotlin/KotlinCodeGenerator.kt | 19 +++- .../codegen/kotlin/KotlinCodeGeneratorTest.kt | 92 ++++++++++++++++++- .../main/java/org/pkl/gradle/PklPlugin.java | 3 + .../pkl/gradle/spec/KotlinCodeGenSpec.java | 4 + .../pkl/gradle/task/KotlinCodeGenTask.java | 4 + .../pkl/gradle/KotlinCodeGeneratorsTest.kt | 20 +++- 7 files changed, 145 insertions(+), 8 deletions(-) diff --git a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt index 2800fdea0..bf56f90d0 100644 --- a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt +++ b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt @@ -29,6 +29,9 @@ data class CliKotlinCodeGeneratorOptions( /** The characters to use for indenting generated source code. */ val indent: String = " ", + /** Kotlin package to use for generated code; if none is provided, the root package is used. */ + val kotlinPackage: String = "", + /** Whether to generate Kdoc based on doc comments for Pkl modules, classes, and properties. */ val generateKdoc: Boolean = false, @@ -39,5 +42,11 @@ data class CliKotlinCodeGeneratorOptions( val implementSerializable: Boolean = false ) { fun toKotlinCodegenOptions(): KotlinCodegenOptions = - KotlinCodegenOptions(indent, generateKdoc, generateSpringBootConfig, implementSerializable) + KotlinCodegenOptions( + indent = indent, + generateKdoc = generateKdoc, + generateSpringBootConfig = generateSpringBootConfig, + implementSerializable = implementSerializable, + kotlinPackage = kotlinPackage, + ) } diff --git a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt index f2699e7e2..b67c291d8 100644 --- a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt +++ b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt @@ -27,6 +27,9 @@ data class KotlinCodegenOptions( /** The characters to use for indenting generated Kotlin code. */ val indent: String = " ", + /** Kotlin package to use for generated code; if none is provided, the root package is used. */ + val kotlinPackage: String = "", + /** Whether to generate KDoc based on doc comments for Pkl modules, classes, and properties. */ val generateKdoc: Boolean = false, @@ -111,6 +114,9 @@ class KotlinCodeGenerator( append("lin/${relativeOutputPathFor(moduleSchema.moduleName)}") } + val kotlinPackage: String? + get() = options.kotlinPackage.ifEmpty { null } + val kotlinFile: String get() { if (moduleSchema.moduleUri.scheme == "pkl") { @@ -123,6 +129,7 @@ class KotlinCodeGenerator( val hasProperties = pModuleClass.properties.any { !it.value.isHidden } val isGenerateClass = hasProperties || pModuleClass.isOpen || pModuleClass.isAbstract + val packagePrefix = kotlinPackage?.let { "$it." } ?: "" val moduleType = if (isGenerateClass) { generateTypeSpec(pModuleClass, moduleSchema) @@ -172,7 +179,9 @@ class KotlinCodeGenerator( val packageName = if (index == -1) "" else moduleName.substring(0, index) val moduleTypeName = moduleName.substring(index + 1).replaceFirstChar { it.titlecaseChar() } - val fileSpec = FileSpec.builder(packageName, moduleTypeName).indent(options.indent) + val packagePath = + if (packagePrefix.isNotBlank()) "$packagePrefix.$packageName" else packageName + val fileSpec = FileSpec.builder(packagePath, moduleTypeName).indent(options.indent) for (typeAlias in moduleSchema.typeAliases.values) { if (typeAlias.aliasedType is PType.Alias) { @@ -638,10 +647,14 @@ class KotlinCodeGenerator( val index = moduleName.lastIndexOf(".") val packageName = if (index == -1) "" else moduleName.substring(0, index) val moduleTypeName = moduleName.substring(index + 1).replaceFirstChar { it.titlecaseChar() } + val packagePrefix = kotlinPackage?.let { "$it." } ?: "" + val renderedPackage = + if (packagePrefix.isNotBlank()) "$packagePrefix.$packageName" else packageName + return if (isModuleClass) { - ClassName(packageName, moduleTypeName) + ClassName(renderedPackage, moduleTypeName) } else { - ClassName(packageName, moduleTypeName, simpleName) + ClassName(renderedPackage, moduleTypeName, simpleName) } } diff --git a/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt b/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt index e8fd03198..5aa0d6aaa 100644 --- a/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt +++ b/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt @@ -133,7 +133,8 @@ class KotlinCodeGeneratorTest { pklCode: String, generateKdoc: Boolean = false, generateSpringBootConfig: Boolean = false, - implementSerializable: Boolean = false + implementSerializable: Boolean = false, + kotlinPackage: String? = null, ): String { val module = Evaluator.preconfigured().evaluateSchema(ModuleSource.text(pklCode)) @@ -144,7 +145,8 @@ class KotlinCodeGeneratorTest { KotlinCodegenOptions( generateKdoc = generateKdoc, generateSpringBootConfig = generateSpringBootConfig, - implementSerializable = implementSerializable + implementSerializable = implementSerializable, + kotlinPackage = kotlinPackage ?: "", ) ) return generator.kotlinFile @@ -553,6 +555,92 @@ class KotlinCodeGeneratorTest { assertCompilesSuccessfully(kotlinCode) } + @Test + fun `custom kotlin package prefix`() { + val kotlinCode = + generateKotlinCode( + """ + module my.mod + + class Person { + name: String + age: Int + hobbies: List + friends: Map + sibling: Person? + } + """, + kotlinPackage = "cool.pkg.path", + ) + + assertEqualTo( + """ + package cool.pkg.path.my + + import kotlin.Long + import kotlin.String + import kotlin.collections.List + import kotlin.collections.Map + + object Mod { + data class Person( + val name: String, + val age: Long, + val hobbies: List, + val friends: Map, + val sibling: Person? + ) + } + """, + kotlinCode + ) + + assertCompilesSuccessfully(kotlinCode) + } + + @Test + fun `empty kotlin package prefix`() { + val kotlinCode = + generateKotlinCode( + """ + module my.mod + + class Person { + name: String + age: Int + hobbies: List + friends: Map + sibling: Person? + } + """, + kotlinPackage = "", + ) + + assertEqualTo( + """ + package my + + import kotlin.Long + import kotlin.String + import kotlin.collections.List + import kotlin.collections.Map + + object Mod { + data class Person( + val name: String, + val age: Long, + val hobbies: List, + val friends: Map, + val sibling: Person? + ) + } + """, + kotlinCode + ) + + assertCompilesSuccessfully(kotlinCode) + } + @Test fun `recursive types`() { val kotlinCode = diff --git a/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java b/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java index 23741970b..10ffdf669 100644 --- a/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java +++ b/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java @@ -196,12 +196,15 @@ private void configureKotlinCodeGenTasks(NamedDomainObjectContainer { configureCodeGenTask(task, spec); task.getGenerateKdoc().set(spec.getGenerateKdoc()); + task.getIndent().set(spec.getIndent()); + task.getKotlinPackage().set(spec.getKotlinPackage()); }); }); diff --git a/pkl-gradle/src/main/java/org/pkl/gradle/spec/KotlinCodeGenSpec.java b/pkl-gradle/src/main/java/org/pkl/gradle/spec/KotlinCodeGenSpec.java index 62b8faa9d..50e7d56fc 100644 --- a/pkl-gradle/src/main/java/org/pkl/gradle/spec/KotlinCodeGenSpec.java +++ b/pkl-gradle/src/main/java/org/pkl/gradle/spec/KotlinCodeGenSpec.java @@ -19,5 +19,9 @@ /** Configuration options for Kotlin code generators. Documented in user manual. */ public interface KotlinCodeGenSpec extends CodeGenSpec { + Property getIndent(); + + Property getKotlinPackage(); + Property getGenerateKdoc(); } diff --git a/pkl-gradle/src/main/java/org/pkl/gradle/task/KotlinCodeGenTask.java b/pkl-gradle/src/main/java/org/pkl/gradle/task/KotlinCodeGenTask.java index 240cbe6ec..7ed4175e8 100644 --- a/pkl-gradle/src/main/java/org/pkl/gradle/task/KotlinCodeGenTask.java +++ b/pkl-gradle/src/main/java/org/pkl/gradle/task/KotlinCodeGenTask.java @@ -25,6 +25,9 @@ public abstract class KotlinCodeGenTask extends CodeGenTask { @Input public abstract Property getGenerateKdoc(); + @Input + public abstract Property getKotlinPackage(); + @Override protected void doRunTask() { //noinspection ResultOfMethodCallIgnored @@ -35,6 +38,7 @@ protected void doRunTask() { getCliBaseOptions(), getProject().file(getOutputDir()).toPath(), getIndent().get(), + getKotlinPackage().get(), getGenerateKdoc().get(), getGenerateSpringBootConfig().get(), getImplementSerializable().get())) diff --git a/pkl-gradle/src/test/kotlin/org/pkl/gradle/KotlinCodeGeneratorsTest.kt b/pkl-gradle/src/test/kotlin/org/pkl/gradle/KotlinCodeGeneratorsTest.kt index 3e563ff66..ac72482b6 100644 --- a/pkl-gradle/src/test/kotlin/org/pkl/gradle/KotlinCodeGeneratorsTest.kt +++ b/pkl-gradle/src/test/kotlin/org/pkl/gradle/KotlinCodeGeneratorsTest.kt @@ -65,7 +65,22 @@ class KotlinCodeGeneratorsTest : AbstractTest() { assertThat(personClassFile).exists() assertThat(addressClassFile).exists() } - + + @Test + fun `compile generated code with custom kotlin package`() { + writeBuildFile(kotlinPackage = "my.cool.pkl.pkg") + writePklFile() + runTask("compileKotlin") + + val classesDir = testProjectDir.resolve("build/classes/kotlin/main") + val moduleClassFile = classesDir.resolve("my/cool/pkl/pkg/org/Mod.class") + val personClassFile = classesDir.resolve("my/cool/pkl/pkg/org/Mod\$Person.class") + val addressClassFile = classesDir.resolve("my/cool/pkl/pkg/org/Mod\$Address.class") + assertThat(moduleClassFile).exists() + assertThat(personClassFile).exists() + assertThat(addressClassFile).exists() + } + @Test fun `no source modules`() { writeFile( @@ -88,7 +103,7 @@ class KotlinCodeGeneratorsTest : AbstractTest() { assertThat(result.output).contains("No source modules specified.") } - private fun writeBuildFile() { + private fun writeBuildFile(kotlinPackage: String? = null) { val kotlinVersion = "1.6.0" writeFile( @@ -125,6 +140,7 @@ class KotlinCodeGeneratorsTest : AbstractTest() { sourceModules = ["mod.pkl"] outputDir = file("build/generated") settingsModule = "pkl:settings" + ${if (kotlinPackage != null) "kotlinPackage = \"$kotlinPackage\"" else ""} } } } From 2e45a9ffd751139352043779743dd72aff581990 Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Sun, 18 Feb 2024 15:12:35 -0800 Subject: [PATCH 003/164] Implement support for KotlinX Serialization This change adds support for a new code-gen argument, `implementKSerializable`, which results in the annotation `kotlinx.serialization.Serializable` being added to `data` classes during codegen. Relates to discussion apple/pkl#185 - feat(codegen): add support for kotlin `Serializable` annotation - feat(gradle): add `implementKSerializable` argument - test(codegen): add test for KotlinX serialization support - test(codegen): add test for both Java and KotlinX serialization - test(gradle): add test for compiling with KotlinX serialization Signed-off-by: Sam Gammon --- buildSrc/build.gradle.kts | 1 + .../main/kotlin/pklKotlinLibrary.gradle.kts | 1 + gradle/libs.versions.toml | 2 + .../pkl-codegen-kotlin.gradle.kts | 4 + .../kotlin/CliKotlinCodeGeneratorOptions.kt | 6 +- .../pkl/codegen/kotlin/KotlinCodeGenerator.kt | 27 +++- .../codegen/kotlin/KotlinCodeGeneratorTest.kt | 151 +++++++++++++++++- pkl-doc/pkl-doc.gradle.kts | 2 +- .../main/java/org/pkl/gradle/PklPlugin.java | 4 + .../pkl/gradle/spec/KotlinCodeGenSpec.java | 4 + .../pkl/gradle/task/KotlinCodeGenTask.java | 6 +- .../pkl/gradle/KotlinCodeGeneratorsTest.kt | 28 +++- 12 files changed, 227 insertions(+), 9 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 6c61d5079..015a06561 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,6 +8,7 @@ dependencies { implementation(libs.kotlinPlugin) { exclude(module = "kotlin-android-extensions") } + implementation(libs.kotlinPluginSerialization) implementation(libs.shadowPlugin) // fix from the Gradle team: makes version catalog symbols available in build scripts diff --git a/buildSrc/src/main/kotlin/pklKotlinLibrary.gradle.kts b/buildSrc/src/main/kotlin/pklKotlinLibrary.gradle.kts index cbc728365..49d603082 100644 --- a/buildSrc/src/main/kotlin/pklKotlinLibrary.gradle.kts +++ b/buildSrc/src/main/kotlin/pklKotlinLibrary.gradle.kts @@ -4,6 +4,7 @@ plugins { id("pklJavaLibrary") kotlin("jvm") + kotlin("plugin.serialization") } val buildInfo = project.extensions.getByType() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec99df8e0..494195c26 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,10 +72,12 @@ kotlinCompilerEmbeddable = { group = "org.jetbrains.kotlin", name = "kotlin-comp kotlinScriptingCompilerEmbeddable = { group = "org.jetbrains.kotlin", name = "kotlin-scripting-compiler-embeddable", version.ref = "kotlin" } kotlinScriptUtil = { group = "org.jetbrains.kotlin", name = "kotlin-script-util", version.ref = "kotlin" } kotlinPlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } +kotlinPluginSerialization = { group = "org.jetbrains.kotlin", name = "kotlin-serialization", version.ref = "kotlin" } kotlinPoet = { group = "com.squareup", name = "kotlinpoet", version.ref = "kotlinPoet" } kotlinReflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } kotlinStdLib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlinxHtml = { group = "org.jetbrains.kotlinx", name = "kotlinx-html-jvm", version.ref = "kotlinxHtml" } +kotlinxSerializationCore = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "kotlinxSerialization" } kotlinxSerializationJson = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" } log4j12Api = { group = "org.apache.logging.log4j", name = "log4j-1.2-api", version.ref = "log4j" } nuValidator = { group = "nu.validator", name = "validator", version.ref = "nuValidator" } diff --git a/pkl-codegen-kotlin/pkl-codegen-kotlin.gradle.kts b/pkl-codegen-kotlin/pkl-codegen-kotlin.gradle.kts index fb1d09dc6..f91ef5da9 100644 --- a/pkl-codegen-kotlin/pkl-codegen-kotlin.gradle.kts +++ b/pkl-codegen-kotlin/pkl-codegen-kotlin.gradle.kts @@ -31,6 +31,10 @@ dependencies { implementation(libs.kotlinPoet) implementation(libs.kotlinReflect) + implementation(libs.kotlinxSerializationCore) { + exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib") + exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-common") + } testImplementation(projects.pklConfigKotlin) testImplementation(projects.pklCommonsTest) diff --git a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt index bf56f90d0..6c05774df 100644 --- a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt +++ b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt @@ -39,7 +39,10 @@ data class CliKotlinCodeGeneratorOptions( val generateSpringBootConfig: Boolean = false, /** Whether to make generated classes implement [java.io.Serializable] */ - val implementSerializable: Boolean = false + val implementSerializable: Boolean = false, + + /** Whether to annotate generated data classes with [kotlinx.serialization.Serializable] */ + val implementKSerializable: Boolean = false ) { fun toKotlinCodegenOptions(): KotlinCodegenOptions = KotlinCodegenOptions( @@ -48,5 +51,6 @@ data class CliKotlinCodeGeneratorOptions( generateSpringBootConfig = generateSpringBootConfig, implementSerializable = implementSerializable, kotlinPackage = kotlinPackage, + implementKSerializable = implementKSerializable, ) } diff --git a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt index b67c291d8..1071fbf2a 100644 --- a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt +++ b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt @@ -20,6 +20,7 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import java.io.StringWriter import java.net.URI import java.util.* +import kotlinx.serialization.Serializable import org.pkl.core.* import org.pkl.core.util.CodeGeneratorUtils @@ -37,7 +38,10 @@ data class KotlinCodegenOptions( val generateSpringBootConfig: Boolean = false, /** Whether to make generated classes implement [java.io.Serializable] */ - val implementSerializable: Boolean = false + val implementSerializable: Boolean = false, + + /** Whether to annotate data classes with [kotlinx.serialization.Serializable] */ + val implementKSerializable: Boolean = false, ) class KotlinCodeGeneratorException(message: String) : RuntimeException(message) @@ -138,7 +142,14 @@ class KotlinCodeGenerator( } for (pClass in moduleSchema.classes.values) { - moduleType.addType(generateTypeSpec(pClass, moduleSchema).ensureSerializable().build()) + moduleType.addType( + generateTypeSpec(pClass, moduleSchema) + .apply { + ensureSerializable() + ensureKSerializable() + } + .build() + ) } // generate append method for module classes w/o parent class; reuse in subclasses and nested @@ -522,6 +533,18 @@ class KotlinCodeGenerator( else generateRegularClass() } + private fun TypeSpec.Builder.ensureKSerializable(): TypeSpec.Builder { + if (!options.implementKSerializable) { + return this + } + + val spec = AnnotationSpec.builder(Serializable::class).build() + if (!this.annotationSpecs.contains(spec)) { + addAnnotation(spec) + } + return this + } + private fun TypeSpec.Builder.ensureSerializable(): TypeSpec.Builder { if (!options.implementSerializable) { return this diff --git a/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt b/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt index 5aa0d6aaa..8b6ed40d8 100644 --- a/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt +++ b/pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt @@ -134,9 +134,9 @@ class KotlinCodeGeneratorTest { generateKdoc: Boolean = false, generateSpringBootConfig: Boolean = false, implementSerializable: Boolean = false, + implementKSerializable: Boolean = false, kotlinPackage: String? = null, ): String { - val module = Evaluator.preconfigured().evaluateSchema(ModuleSource.text(pklCode)) val generator = @@ -145,8 +145,9 @@ class KotlinCodeGeneratorTest { KotlinCodegenOptions( generateKdoc = generateKdoc, generateSpringBootConfig = generateSpringBootConfig, - implementSerializable = implementSerializable, kotlinPackage = kotlinPackage ?: "", + implementSerializable = implementSerializable, + implementKSerializable = implementKSerializable, ) ) return generator.kotlinFile @@ -641,6 +642,152 @@ class KotlinCodeGeneratorTest { assertCompilesSuccessfully(kotlinCode) } + @Test + fun `data class implementing serializable`() { + val kotlinCode = + generateKotlinCode( + /* language=pkl */ + """ + module my.mod + + class Person { + name: String + age: Int + hobbies: List + friends: Map + sibling: Person? + } + """, + implementSerializable = true, + ) + + assertEqualTo( + """ + package my + + import java.io.Serializable + import kotlin.Long + import kotlin.String + import kotlin.collections.List + import kotlin.collections.Map + + object Mod { + data class Person( + val name: String, + val age: Long, + val hobbies: List, + val friends: Map, + val sibling: Person? + ) : Serializable { + companion object { + private const val serialVersionUID: Long = 0L + } + } + } + """, + kotlinCode + ) + + assertCompilesSuccessfully(kotlinCode) + } + + @Test + fun `data class implementing kserializable`() { + val kotlinCode = + generateKotlinCode( + /* language=pkl */ + """ + module my.mod + + class Person { + name: String + age: Int + hobbies: List + friends: Map + sibling: Person? + } + """, + implementKSerializable = true, + ) + + assertEqualTo( + """ + package my + + import kotlin.Long + import kotlin.String + import kotlin.collections.List + import kotlin.collections.Map + import kotlinx.serialization.Serializable + + object Mod { + @Serializable + data class Person( + val name: String, + val age: Long, + val hobbies: List, + val friends: Map, + val sibling: Person? + ) + } + """, + kotlinCode + ) + + assertCompilesSuccessfully(kotlinCode) + } + + @Test + fun `data class implementing all serialization`() { + val kotlinCode = + generateKotlinCode( + /* language=pkl */ + """ + module my.mod + + class Person { + name: String + age: Int + hobbies: List + friends: Map + sibling: Person? + } + """, + implementSerializable = true, + implementKSerializable = true, + ) + + assertEqualTo( + """ + package my + + import kotlin.Long + import kotlin.String + import kotlin.collections.List + import kotlin.collections.Map + import kotlinx.serialization.Serializable + + object Mod { + @Serializable + data class Person( + val name: String, + val age: Long, + val hobbies: List, + val friends: Map, + val sibling: Person? + ) : java.io.Serializable { + companion object { + private const val serialVersionUID: Long = 0L + } + } + } + """, + kotlinCode + ) + + assertCompilesSuccessfully(kotlinCode) + } + @Test fun `recursive types`() { val kotlinCode = diff --git a/pkl-doc/pkl-doc.gradle.kts b/pkl-doc/pkl-doc.gradle.kts index c880aea10..952c0bc1d 100644 --- a/pkl-doc/pkl-doc.gradle.kts +++ b/pkl-doc/pkl-doc.gradle.kts @@ -6,7 +6,7 @@ plugins { pklPublishLibrary pklHtmlValidator @Suppress("DSL_SCOPE_VIOLATION") // https://youtrack.jetbrains.com/issue/KTIJ-19369 - alias(libs.plugins.kotlinxSerialization) + id(libs.plugins.kotlinxSerialization.get().pluginId) } val graalVmBaseDir = buildInfo.graalVm.baseDir diff --git a/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java b/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java index 10ffdf669..1d32d0dc3 100644 --- a/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java +++ b/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java @@ -197,6 +197,8 @@ private void configureKotlinCodeGenTasks(NamedDomainObjectContainer getKotlinPackage(); Property getGenerateKdoc(); + + Property getImplementSerializable(); + + Property getImplementKSerializable(); } diff --git a/pkl-gradle/src/main/java/org/pkl/gradle/task/KotlinCodeGenTask.java b/pkl-gradle/src/main/java/org/pkl/gradle/task/KotlinCodeGenTask.java index 7ed4175e8..a4cc1a95f 100644 --- a/pkl-gradle/src/main/java/org/pkl/gradle/task/KotlinCodeGenTask.java +++ b/pkl-gradle/src/main/java/org/pkl/gradle/task/KotlinCodeGenTask.java @@ -28,6 +28,9 @@ public abstract class KotlinCodeGenTask extends CodeGenTask { @Input public abstract Property getKotlinPackage(); + @Input + public abstract Property getImplementKSerializable(); + @Override protected void doRunTask() { //noinspection ResultOfMethodCallIgnored @@ -41,7 +44,8 @@ protected void doRunTask() { getKotlinPackage().get(), getGenerateKdoc().get(), getGenerateSpringBootConfig().get(), - getImplementSerializable().get())) + getImplementSerializable().get(), + getImplementKSerializable().get())) .run(); } } diff --git a/pkl-gradle/src/test/kotlin/org/pkl/gradle/KotlinCodeGeneratorsTest.kt b/pkl-gradle/src/test/kotlin/org/pkl/gradle/KotlinCodeGeneratorsTest.kt index ac72482b6..e68c6498a 100644 --- a/pkl-gradle/src/test/kotlin/org/pkl/gradle/KotlinCodeGeneratorsTest.kt +++ b/pkl-gradle/src/test/kotlin/org/pkl/gradle/KotlinCodeGeneratorsTest.kt @@ -81,6 +81,21 @@ class KotlinCodeGeneratorsTest : AbstractTest() { assertThat(addressClassFile).exists() } + @Test + fun `compile generated code with kserialization support`() { + writeBuildFile(kotlinxSerde = true) + writePklFile() + runTask("compileKotlin") + + val classesDir = testProjectDir.resolve("build/classes/kotlin/main") + val moduleClassFile = classesDir.resolve("org/Mod.class") + val personClassFile = classesDir.resolve("org/Mod\$Person.class") + val addressClassFile = classesDir.resolve("org/Mod\$Address.class") + assertThat(moduleClassFile).exists() + assertThat(personClassFile).exists() + assertThat(addressClassFile).exists() + } + @Test fun `no source modules`() { writeFile( @@ -103,8 +118,14 @@ class KotlinCodeGeneratorsTest : AbstractTest() { assertThat(result.output).contains("No source modules specified.") } - private fun writeBuildFile(kotlinPackage: String? = null) { - val kotlinVersion = "1.6.0" + private fun writeBuildFile(kotlinPackage: String? = null, kotlinxSerde: Boolean = false) { + val kotlinVersion = "1.7.10" + val kotlinXSerdeVersion = "1.5.0" + val kotlinXRuntimeDependency = StringBuilder().apply { + append("org.jetbrains.kotlinx:kotlinx-serialization-core") + append(":") + append(kotlinXSerdeVersion) + }.toString() writeFile( "build.gradle", """ @@ -117,6 +138,7 @@ class KotlinCodeGeneratorsTest : AbstractTest() { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") { exclude module: "kotlin-android-extensions" } + classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion") } } @@ -125,6 +147,7 @@ class KotlinCodeGeneratorsTest : AbstractTest() { } apply plugin: "kotlin" + ${if (kotlinxSerde) "apply plugin: 'kotlinx-serialization'" else ""} repositories { mavenCentral() @@ -132,6 +155,7 @@ class KotlinCodeGeneratorsTest : AbstractTest() { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + ${if (kotlinxSerde) "implementation '$kotlinXRuntimeDependency'" else ""} } pkl { From 4a9a5eebdfc12bf605ac155daeaaef0afc36062f Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Sun, 18 Feb 2024 15:57:21 -0800 Subject: [PATCH 004/164] General build upgrades and improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - feat: support for build scans with gradle enterprise - feat: support for caching with buildless (inert without key) - feat: support for gradle java toolchains - feat: support for static analysis with detekt - feat: support for gradle toolchains - feat: support for toolchain vs. runtime target - feat: support for dynamic provisioning of toolchains - feat: support for static java checking with pmd - feat: enable typed project accessors, use them project-wide - feat: stricter repositories, locking for build classpath - feat: property to retarget java or kotlin bytecode versions - feat: parameter name integration between javac and kotlinc - feat: kotlin coverage support via `kover` plugin - feat: dependency verification for gradle build - feat: support for new gradle `jvm-test-suite` plugin - feat: reasonable local and remote build caching support - feat: project icon in intellij new ui - feat: aggregate reporting for tests, coverage, detekt - fix: repeatable/consistent archives from gradle - fix: don't list ephemeral spotless configurations in lockfiles - fix: make version catalog symbols available in `buildSrc` - fix: specify `rootProject.name` for `buildSrc` - fix: error when running `gradlew tasks` - fix: various java or gradle deprecations - fix: move all tool (linter, etc) versions into version catalog - chore: add testlogger for clearer test outcomes - chore: check build configuration with gradle doctor plugin - chore: generate initial suite of dependency verification material - chore: transition to property set syntax (`property = xyz`) - chore: cleanup uses of `buildDir` (becomes `layout.buildDirectory`) - chore: add Gradle Versions plugin for update checks - chore: upgrade Gradle → `8.6` (supports Java 21) - chore: upgrade Kotlin → `1.9.22` (build-time) - chore: upgrade KotlinX Serialization → `1.6.3` - chore: upgrade KotlinX HTML → `0.11.0` - chore: general dependency upgrades, where safe Not yet completed: - feat: signing of artifacts with sigstore - feat: embedding of SPDX SBOM in artifacts - feat: dependency vulnerability checks with owasp - test: checksum failures Signed-off-by: Sam Gammon --- .gitignore | 1 + .idea/codeStyles/Project.xml | 2 - bench/bench.gradle.kts | 14 +- bench/gradle.lockfile | 41 +- build.gradle.kts | 64 +- buildSrc/build.gradle.kts | 45 +- buildSrc/gradle.lockfile | 83 + buildSrc/settings.gradle.kts | 4 +- buildSrc/src/main/kotlin/BuildInfo.kt | 5 +- buildSrc/src/main/kotlin/GradleVersionInfo.kt | 7 +- .../src/main/kotlin/pklAllProjects.gradle.kts | 114 +- buildSrc/src/main/kotlin/pklFatJar.gradle.kts | 8 +- .../src/main/kotlin/pklGraalVm.gradle.kts | 2 +- .../main/kotlin/pklHtmlValidator.gradle.kts | 4 +- .../src/main/kotlin/pklJavaLibrary.gradle.kts | 29 +- .../main/kotlin/pklJvmEntrypoint.gradle.kts | 19 + .../main/kotlin/pklKotlinLibrary.gradle.kts | 63 +- .../src/main/kotlin/pklKotlinTest.gradle.kts | 33 + .../main/kotlin/pklPublishLibrary.gradle.kts | 6 +- config/detekt/detekt.yml | 16 + docs/docs.gradle.kts | 6 +- docs/gradle.lockfile | 34 +- gradle.properties | 34 +- gradle/libs.versions.toml | 51 +- gradle/verification-keyring.gpg | Bin 0 -> 207433 bytes gradle/verification-keyring.keys | 5651 +++++++++++++++++ gradle/verification-metadata.xml | 4528 +++++++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 41 +- gradlew.bat | 35 +- pkl-cli/detekt-baseline.xml | 44 + pkl-cli/gradle.lockfile | 46 +- pkl-cli/pkl-cli.gradle.kts | 82 +- .../pkl/cli/CliDownloadPackageCommandTest.kt | 4 + .../org/pkl/cli/CliProjectPackagerTest.kt | 2 + .../org/pkl/cli/CliProjectResolverTest.kt | 3 + pkl-codegen-java/detekt-baseline.xml | 24 + pkl-codegen-java/gradle.lockfile | 37 +- pkl-codegen-java/pkl-codegen-java.gradle.kts | 6 +- pkl-codegen-kotlin/detekt-baseline.xml | 22 + pkl-codegen-kotlin/gradle.lockfile | 40 +- .../pkl-codegen-kotlin.gradle.kts | 8 +- .../codegen/kotlin/KotlinCodeGeneratorTest.kt | 2 + pkl-commons-cli/detekt-baseline.xml | 12 + pkl-commons-cli/gradle.lockfile | 35 +- pkl-commons-cli/pkl-commons-cli.gradle.kts | 4 +- pkl-commons-test/detekt-baseline.xml | 11 + pkl-commons-test/gradle.lockfile | 28 +- pkl-commons-test/pkl-commons-test.gradle.kts | 26 +- .../org/pkl/commons/test/FileTestUtils.kt | 1 + pkl-commons/gradle.lockfile | 28 +- pkl-commons/pkl-commons.gradle.kts | 4 +- pkl-config-java/gradle.lockfile | 38 +- pkl-config-java/pkl-config-java.gradle.kts | 12 +- pkl-config-kotlin/detekt-baseline.xml | 14 + pkl-config-kotlin/gradle.lockfile | 37 +- .../pkl-config-kotlin.gradle.kts | 6 +- .../org/pkl/config/kotlin/ConfigExtensions.kt | 2 + .../pkl/config/kotlin/ConfigExtensionsTest.kt | 2 + pkl-core/gradle.lockfile | 44 - pkl-core/pkl-core.gradle.kts | 12 +- .../pkl/core/LanguageSnippetTestsEngine.kt | 2 +- .../ProjectDependenciesResolverTest.kt | 5 +- .../org/pkl/core/project/ProjectTest.kt | 4 +- pkl-doc/detekt-baseline.xml | 45 + pkl-doc/gradle.lockfile | 79 +- pkl-doc/pkl-doc.gradle.kts | 4 +- .../kotlin/org/pkl/doc/CliDocGeneratorTest.kt | 2 + pkl-executor/gradle.lockfile | 35 +- pkl-executor/pkl-executor.gradle.kts | 6 +- pkl-gradle/gradle.lockfile | 30 +- pkl-gradle/pkl-gradle.gradle.kts | 19 +- .../pkl/gradle/KotlinCodeGeneratorsTest.kt | 1 + pkl-server/detekt-baseline.xml | 34 + pkl-server/gradle.lockfile | 34 +- pkl-server/pkl-server.gradle.kts | 19 +- pkl-tools/gradle.lockfile | 32 +- pkl-tools/pkl-tools.gradle.kts | 37 +- settings.gradle.kts | 40 +- stdlib/gradle.lockfile | 24 - stdlib/stdlib.gradle.kts | 10 +- 82 files changed, 11199 insertions(+), 843 deletions(-) create mode 100644 buildSrc/gradle.lockfile create mode 100644 buildSrc/src/main/kotlin/pklJvmEntrypoint.gradle.kts create mode 100644 config/detekt/detekt.yml create mode 100644 gradle/verification-keyring.gpg create mode 100644 gradle/verification-keyring.keys create mode 100644 gradle/verification-metadata.xml create mode 100644 pkl-cli/detekt-baseline.xml create mode 100644 pkl-codegen-java/detekt-baseline.xml create mode 100644 pkl-codegen-kotlin/detekt-baseline.xml create mode 100644 pkl-commons-cli/detekt-baseline.xml create mode 100644 pkl-commons-test/detekt-baseline.xml create mode 100644 pkl-config-kotlin/detekt-baseline.xml delete mode 100644 pkl-core/gradle.lockfile create mode 100644 pkl-doc/detekt-baseline.xml create mode 100644 pkl-server/detekt-baseline.xml delete mode 100644 stdlib/gradle.lockfile diff --git a/.gitignore b/.gitignore index 4c1ed3a12..ee64558c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # macOS .DS_STORE +.codebase/ # Gradle .gradle/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index afb7ce46c..1fa98d1f5 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -239,10 +239,8 @@ -