-
Notifications
You must be signed in to change notification settings - Fork 55
feat: add Schema generator Gradle plugin #1385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a51de4b
0a779a1
011f367
75e208a
d8c65c9
5edef54
df80d66
3d5818e
73514a5
ba04d3e
dbd737e
1da4704
48455a9
77f0792
725a391
77c3264
a0134e0
165e59b
6120b6b
abae3b0
924a04e
d864987
3513113
e995954
a1cfc8e
f5cdd9a
54ec0b3
c8148fc
1176517
aac0fda
3f566a6
a0f0bfc
8327568
48c4206
7ea934e
171e47a
47499cb
3ea7b6f
599c9b1
addb7c7
69b7c5f
f010bb5
42aebb2
1788f2a
5a39f82
e63b9e1
b68fa5e
c676c56
c283149
1306e75
406070a
5d698ca
5c2915e
98b59fb
c971520
881a7a8
f4f399a
9c63c47
d3e3aeb
1da7f9a
345fd8e
fbd7ba1
930fb53
3513aa6
1ca559c
a1b54b6
a5d6c9d
2f26925
7e3c972
4873078
2ae79f9
4e34cc1
07916ad
5deaf7f
94d269a
cdafd33
b28f927
7f2cdf0
9038324
1da0e4a
32ce6c4
52641eb
2e9397a
2f606d0
7c9272f
5e03d53
21dc9e8
ca3ec01
99a0465
72bb40f
90c4fd8
17a5c5b
6343977
a0c4d59
c5b863b
8bfcf13
4106bed
48968c9
9642be3
09f2f9c
2e29bf0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| public final class aws/sdk/kotlin/hll/dynamodbmapper/plugins/SchemaGeneratorPlugin : org/gradle/api/Plugin { | ||
| public fun <init> ()V | ||
| public synthetic fun apply (Ljava/lang/Object;)V | ||
| public fun apply (Lorg/gradle/api/Project;)V | ||
| } | ||
|
|
||
| public final class aws/sdk/kotlin/hll/dynamodbmapper/plugins/SchemaGeneratorPlugin$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 class aws/sdk/kotlin/hll/dynamodbmapper/plugins/SchemaGeneratorPluginExtension { | ||
| public fun <init> ()V | ||
| } | ||
|
|
||
| public final class aws/sdk/kotlin/hll/dynamodbmapper/plugins/SchemaGeneratorPluginKt { | ||
| public static final field SCHEMA_GENERATOR_PLUGIN_EXTENSION Ljava/lang/String; | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | ||
|
|
||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| description = "Plugin used to generate DynamoDbMapper schemas from user classes" | ||
| extra["displayName"] = "AWS :: SDK :: Kotlin :: HLL :: DynamoDbMapper :: Schema Generator Plugin" | ||
| extra["moduleName"] = "aws.sdk.kotlin.hll.dynamodbmapper.plugins" | ||
|
|
||
| plugins { | ||
| `kotlin-dsl` | ||
| `java-gradle-plugin` | ||
| alias(libs.plugins.gradle.plugin.publish) | ||
| `maven-publish` | ||
| } | ||
|
|
||
| dependencies { | ||
| implementation(kotlin("gradle-plugin")) | ||
| implementation(libs.ksp.gradle.plugin) | ||
|
|
||
| testImplementation(libs.junit.jupiter) | ||
| testImplementation(libs.junit.jupiter.params) | ||
| testImplementation(libs.kotlin.test) | ||
| } | ||
|
|
||
| gradlePlugin { | ||
| website = "https://github.com/awslabs/aws-sdk-kotlin" | ||
| vcsUrl = "https://github.com/awslabs/aws-sdk-kotlin.git" | ||
| plugins { | ||
| create("dynamodb-mapper-schema-generator") { | ||
| id = "aws.sdk.kotlin.hll.dynamodbmapper.schema.generator" | ||
| displayName = "DynamoDbMapper Schema Generator" | ||
| description = "Plugin used to generate DynamoDbMapper schemas from user classes" | ||
| tags = setOf("kotlin", "dynamodb", "aws") | ||
| implementationClass = "aws.sdk.kotlin.hll.dynamodbmapper.plugins.SchemaGeneratorPlugin" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| val sdkVersion: String by project | ||
| group = "aws.sdk.kotlin" | ||
| version = sdkVersion | ||
|
|
||
| publishing { | ||
| publications { | ||
| create<MavenPublication>("dynamodb-mapper-schema-generator-plugin") { | ||
| from(components["java"]) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| tasks.test { | ||
| useJUnitPlatform() | ||
| testLogging { | ||
| events("passed", "skipped", "failed") | ||
| showStandardStreams = true | ||
| showStackTraces = true | ||
| showExceptions = true | ||
| exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Create a file containing the sdkVersion to use as a resource | ||
| * This saves us from having to manually change version numbers in multiple places | ||
| */ | ||
| val generateSdkRuntimeVersion by tasks.registering { | ||
| val resourcesDir = layout.buildDirectory.dir("resources/main/aws/sdk/kotlin/hll/dynamodbmapper/plugins").get() | ||
| val versionFile = file("$resourcesDir/sdk-version.txt") | ||
| val gradlePropertiesFile = rootProject.file("gradle.properties") | ||
| inputs.file(gradlePropertiesFile) | ||
| outputs.file(versionFile) | ||
| sourceSets.main.get().output.dir(resourcesDir) | ||
| doLast { | ||
| versionFile.writeText(sdkVersion) | ||
| } | ||
| } | ||
|
|
||
| tasks.withType<KotlinCompile> { | ||
| dependsOn(generateSdkRuntimeVersion) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package aws.sdk.kotlin.hll.dynamodbmapper.plugins | ||
|
|
||
| import com.google.devtools.ksp.gradle.KspExtension | ||
| import org.gradle.api.Plugin | ||
| import org.gradle.api.Project | ||
| import org.gradle.kotlin.dsl.configure | ||
| import org.gradle.kotlin.dsl.create | ||
|
|
||
| open class SchemaGeneratorPluginExtension { | ||
| // TODO Add configuration here (such as codegen configuration) | ||
| } | ||
|
|
||
| const val SCHEMA_GENERATOR_PLUGIN_EXTENSION = "schemaGeneratorPluginExtension" | ||
|
|
||
| public class SchemaGeneratorPlugin : Plugin<Project> { | ||
| override fun apply(project: Project): Unit = project.run { | ||
| createExtension() | ||
| configureDependencies() | ||
| } | ||
|
|
||
| private fun Project.createExtension(): SchemaGeneratorPluginExtension = extensions.create<SchemaGeneratorPluginExtension>(SCHEMA_GENERATOR_PLUGIN_EXTENSION) | ||
|
|
||
| private fun Project.configureDependencies() { | ||
| logger.info("Configuring dependencies for schema generation...") | ||
| pluginManager.apply("com.google.devtools.ksp") | ||
|
|
||
| extensions.configure<KspExtension> { | ||
| excludeProcessor("aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.HighLevelOpsProcessorProvider") | ||
| // TODO pass plugin configuration to KSP as args... | ||
| } | ||
|
|
||
| val sdkVersion = getSdkVersion() | ||
| dependencies.add("ksp", "aws.sdk.kotlin:dynamodb-mapper-codegen:$sdkVersion") | ||
| } | ||
|
|
||
| // Reads sdk-version.txt for the SDK version to add dependencies on. The file is created in this module's build.gradle.kts | ||
| private fun getSdkVersion(): String = try { | ||
| this.javaClass.getResource("sdk-version.txt")?.readText() ?: throw IllegalStateException("sdk-version.txt does not exist") | ||
| } catch (ex: Exception) { | ||
| throw IllegalStateException("Failed to load sdk-version.txt which sets the SDK version", ex) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package aws.sdk.kotlin.hll.dynamodbmapper.plugins | ||
|
|
||
| import org.gradle.testkit.runner.GradleRunner | ||
| import org.gradle.testkit.runner.TaskOutcome | ||
| import org.junit.jupiter.api.AfterEach | ||
| import org.junit.jupiter.api.BeforeEach | ||
| import org.junit.jupiter.api.Test | ||
| import org.junit.jupiter.api.io.TempDir | ||
| import java.io.File | ||
| import kotlin.test.assertContains | ||
|
|
||
| class SchemaGeneratorPluginTest { | ||
| @TempDir | ||
| lateinit var testProjectDir: File | ||
|
|
||
| private lateinit var settingsFile: File | ||
| private lateinit var buildFile: File | ||
|
|
||
| @BeforeEach | ||
| fun setup() { | ||
| settingsFile = File(testProjectDir, "settings.gradle.kts") | ||
| buildFile = File(testProjectDir, "build.gradle.kts") | ||
| } | ||
|
|
||
| @AfterEach | ||
| fun cleanup() { | ||
| if (settingsFile.exists()) { | ||
| settingsFile.delete() | ||
| } | ||
| if (buildFile.exists()) { | ||
| buildFile.delete() | ||
| } | ||
| } | ||
|
|
||
| // TODO Parameterize the test across multiple versions of Kotlin and Gradle | ||
| @Test | ||
| fun `applies the plugin`() { | ||
| val buildFileContent = """ | ||
| plugins { | ||
| id("org.jetbrains.kotlin.jvm") version "2.0.0" | ||
| id("aws.sdk.kotlin.hll.dynamodbmapper.schema.generator") | ||
| } | ||
| configure<aws.sdk.kotlin.hll.dynamodbmapper.plugins.SchemaGeneratorPluginExtension>{ } | ||
| """.trimIndent() | ||
|
|
||
| buildFile.writeText(buildFileContent) | ||
|
|
||
| val result = GradleRunner.create() | ||
| .withProjectDir(testProjectDir) | ||
| .withArguments("--info", "build") | ||
| .withPluginClasspath() | ||
| .withGradleVersion("8.5") | ||
| .forwardOutput() | ||
| .build() | ||
|
|
||
| assertContains(setOf(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE), result.task(":build")?.outcome) | ||
| } | ||
|
Comment on lines
+36
to
+57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Opinion: This test/module may benefit from more dynamic versioning retrieved at runtime to avoid hardcoding things like the Gradle runner version, Kotlin version, etc.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally we would have a parameterized test with multiple versions of Kotlin and Gradle. I think it should be added in follow-on PRs, I will add a TODO here. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,7 +80,7 @@ if (project.NATIVE_ENABLED) { | |
| // Start by invoking the JVM-only KSP configuration | ||
| dependencies.kspJvm(project(":hll:dynamodb-mapper:dynamodb-mapper-codegen")) | ||
|
|
||
| // Then we need to move the generated source from jvm to common. Gradle lacks a move task so we roll our own! | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: I see we have a solution already but would this be useful here?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good find, that's a copy but we want to move the files in this case |
||
| // Then we need to move the generated source from jvm to common | ||
| val moveGenSrc by tasks.registering { | ||
| // Can't move src until the src is generated | ||
| dependsOn(tasks.named("kspKotlinJvm")) | ||
|
|
@@ -105,6 +105,10 @@ if (project.NATIVE_ENABLED) { | |
| } | ||
| } | ||
|
|
||
| tasks.named("jvmSourcesJar") { | ||
| dependsOn(moveGenSrc) | ||
| } | ||
|
|
||
| tasks.withType<KotlinCompilationTask<*>> { | ||
| if (this !is KspTaskJvm) { | ||
| // Ensure that any **non-KSP** compile tasks depend on the generated src move | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| val sdkVersion: String by project | ||
|
|
||
| plugins { | ||
| id("aws.sdk.kotlin.hll.dynamodbmapper.schema.generator") version "1.3.17-SNAPSHOT" | ||
| } | ||
|
|
||
| kotlin { | ||
| sourceSets { | ||
| commonMain { | ||
| dependencies { | ||
| implementation(project(":hll:dynamodb-mapper:dynamodb-mapper")) | ||
| implementation(project(":hll:dynamodb-mapper:dynamodb-mapper-annotations")) | ||
| implementation(project(":hll:dynamodb-mapper:dynamodb-mapper-schema-generator-plugin")) | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package aws.sdk.kotlin.hll.dynamodbmapper.tests.plugins | ||
|
|
||
| import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbItem | ||
| import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbPartitionKey | ||
|
|
||
| @DynamoDbItem | ||
| public data class Group( | ||
| @DynamoDbPartitionKey val name: String, | ||
| val userIds: String, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package aws.sdk.kotlin.hll.dynamodbmapper.tests.plugins | ||
|
|
||
| import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbAttribute | ||
| import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbItem | ||
| import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbPartitionKey | ||
|
|
||
| @DynamoDbItem | ||
| public data class User( | ||
| @DynamoDbPartitionKey val id: Int, | ||
| @DynamoDbAttribute("fName") val givenName: String, | ||
| @DynamoDbAttribute("lName") val surname: String, | ||
| val age: Int, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package aws.sdk.kotlin.hll.dynamodbmapper.tests.plugins | ||
|
|
||
| import aws.sdk.kotlin.hll.dynamodbmapper.model.itemOf | ||
| import aws.sdk.kotlin.hll.dynamodbmapper.tests.plugins.mapper.schemas.UserConverter | ||
| import aws.sdk.kotlin.services.dynamodb.model.AttributeValue | ||
| import kotlin.test.Test | ||
| import kotlin.test.assertEquals | ||
|
|
||
| class UserTest { | ||
| @Test | ||
| fun testConversion() { | ||
| val user = User(123, "Steve", "Rogers", 84) | ||
| val converted = UserConverter.toItem(user) | ||
|
|
||
| assertEquals( | ||
| itemOf( | ||
| "id" to AttributeValue.N("123"), | ||
| "fName" to AttributeValue.S("Steve"), | ||
| "lName" to AttributeValue.S("Rogers"), | ||
| "age" to AttributeValue.N("84"), | ||
| ), | ||
| converted, | ||
| ) | ||
|
|
||
| val unconverted = UserConverter.fromItem(converted) | ||
|
|
||
| assertEquals(user, unconverted) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Oh interesting—I was expecting your plugin tests to be another module sort of like dynamodb-mapper-annotation-processor-test. This looks like it works but I wonder if we miss anything by not having actual test modules as part of the overall Gradle project. Do you know if this is the recommended style of testing Gradle plugins and, if so, what tradeoffs it might have?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is using the Gradle TestKit which seemed recommended for testing plugins. I think having another test module would be another good test to have, I'll look at adding it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added another test package which consumes the plugin. It's basically a copy of dynamodb-mapper-annotation-processor-test without the manual KSP configuration. We can expand this as more features are added like codegen configuration.