Skip to content

Commit 57346af

Browse files
authored
feat: enhance generic codegen to be more KMP-friendly (#610)
1 parent 8dd6bd0 commit 57346af

File tree

7 files changed

+124
-130
lines changed

7 files changed

+124
-130
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "0011f293-6d95-46ab-ac8c-59d11ef54559",
3+
"type": "feature",
4+
"description": "Enhance generic codegen to be more KMP-friendly. This is a **breaking change** which means service client artifacts will now include their platform name (e.g., `s3-jvm-<version>.jar` vs `s3-<version>.jar`). Users consuming dependencies through the Gradle Kotlin plugin will have this handled automatically for them.",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#460"
7+
]
8+
}

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsKotlinDependency.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ internal fun KotlinDependency.dependencyNotation(allowProjectNotation: Boolean =
6262
val dep = this
6363
return if (allowProjectNotation && sameProjectDeps.contains(dep)) {
6464
val projectNotation = sameProjectDeps[dep]
65-
"${dep.config}($projectNotation)"
65+
"${dep.config.kmpName}($projectNotation)"
6666
} else {
67-
"${dep.config}(\"${dep.group}:${dep.artifact}:${dep.version}\")"
67+
"${dep.config.kmpName}(\"${dep.group}:${dep.artifact}:${dep.version}\")"
6868
}
6969
}

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/GradleGenerator.kt

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,26 @@ class GradleGenerator : KotlinIntegration {
4949

5050
writer.write("val kotlinVersion: String by project")
5151

52-
val dependencies = delegator.dependencies.mapNotNull { it.properties["dependency"] as? KotlinDependency }.distinct()
52+
val allDependencies = delegator.dependencies.mapNotNull { it.properties["dependency"] as? KotlinDependency }.distinct()
5353

54-
writer.write("")
55-
.withBlock("dependencies {", "}") {
56-
val orderedDependencies = dependencies.sortedWith(compareBy({ it.config }, { it.artifact }))
57-
for (dependency in orderedDependencies) {
58-
write(dependency.dependencyNotation())
54+
writer
55+
.write("")
56+
.withBlock("kotlin {", "}") {
57+
withBlock("sourceSets {", "}") {
58+
allDependencies
59+
.sortedWith(compareBy({ it.config }, { it.artifact }))
60+
.groupBy { it.config.sourceSet }
61+
.forEach { (sourceSet, dependencies) ->
62+
withBlock("$sourceSet {", "}") {
63+
withBlock("dependencies {", "}") {
64+
dependencies
65+
.map { it.dependencyNotation() }
66+
.forEach(::write)
67+
}
68+
}
69+
}
5970
}
6071
}
61-
.write("")
6272

6373
val contents = writer.toString()
6474
delegator.fileManifest.writeFile("build.gradle.kts", contents)

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/core/QueryHttpBindingProtocolGenerator.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,9 @@ abstract class AbstractQueryFormUrlSerializerGenerator(
155155
) {
156156
// render the serde descriptors
157157
descriptorGenerator(ctx, shape, members, writer).render()
158-
if (shape.isUnionShape) {
159-
SerializeUnionGenerator(ctx, members, writer, defaultTimestampFormat).render()
160-
} else {
161-
SerializeStructGenerator(ctx, members, writer, defaultTimestampFormat).render()
158+
when (shape) {
159+
is UnionShape -> SerializeUnionGenerator(ctx, shape, members, writer, defaultTimestampFormat).render()
160+
else -> SerializeStructGenerator(ctx, members, writer, defaultTimestampFormat).render()
162161
}
163162
}
164163

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ kotlin.native.ignoreDisabledTargets=true
66
org.gradle.jvmargs=-Xmx6g -XX:MaxPermSize=6g -XX:MaxMetaspaceSize=1G
77

88
# sdk
9-
sdkVersion=0.15.3-SNAPSHOT
9+
sdkVersion=0.16.0-SNAPSHOT
1010

1111
# codegen
1212
smithyVersion=1.17.0

services/build.gradle.kts

Lines changed: 90 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
/*
22
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
* SPDX-License-Identifier: Apache-2.0.
4-
*
54
*/
5+
6+
import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest
7+
import org.jetbrains.kotlin.gradle.tasks.KotlinTest
8+
69
plugins {
7-
kotlin("jvm")
10+
kotlin("multiplatform")
811
`maven-publish`
912
id("org.jetbrains.dokka")
1013
}
1114

15+
val platforms = listOf("common", "jvm")
16+
1217
val sdkVersion: String by project
1318
val kotlinVersion: String by project
1419
val coroutinesVersion: String by project
@@ -17,149 +22,119 @@ val kotestVersion: String by project
1722
val optinAnnotations = listOf(
1823
"aws.smithy.kotlin.runtime.util.InternalApi",
1924
"aws.sdk.kotlin.runtime.InternalSdkApi",
25+
"kotlin.RequiresOptIn",
2026
)
2127

28+
kotlin {
29+
jvm() // Create a JVM target with the default name 'jvm'
30+
}
31+
2232
subprojects {
2333
group = "aws.sdk.kotlin"
2434
version = sdkVersion
2535

2636
apply {
27-
plugin("org.jetbrains.kotlin.jvm")
37+
plugin("org.jetbrains.kotlin.multiplatform")
2838
plugin("org.jetbrains.dokka")
2939
}
3040

31-
// have generated sdk's opt-in to internal runtime features
32-
kotlin.sourceSets.all {
33-
optinAnnotations.forEach { languageSettings.optIn(it) }
34-
}
41+
logger.info("configuring: $project")
3542

36-
kotlin {
37-
sourceSets.getByName("main") {
38-
kotlin.srcDir("common/src")
39-
kotlin.srcDir("generated-src/main/kotlin")
40-
}
41-
sourceSets.getByName("test") {
42-
kotlin.srcDir("common/test")
43-
kotlin.srcDir("generated-src/test")
44-
45-
dependencies {
46-
implementation(kotlin("test-junit5"))
47-
implementation("org.jetbrains.kotlin:kotlin-test-common:$kotlinVersion")
48-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
49-
implementation(project(":aws-runtime:testing"))
50-
implementation("io.kotest:kotest-assertions-core:$kotestVersion")
51-
}
43+
platforms.forEach { platform ->
44+
configure(listOf(project)) {
45+
apply(from = rootProject.file("gradle/$platform.gradle"))
5246
}
5347
}
5448

55-
tasks.withType<org.jetbrains.dokka.gradle.DokkaTaskPartial>().configureEach {
56-
dokkaSourceSets {
57-
named("main") {
58-
platform.set(org.jetbrains.dokka.Platform.jvm)
59-
sourceRoots.from(kotlin.sourceSets.getByName("main").kotlin.srcDirs)
49+
kotlin {
50+
sourceSets {
51+
all {
52+
val srcDir = if (name.endsWith("Main")) "src" else "test"
53+
val resourcesPrefix = if (name.endsWith("Test")) "test-" else ""
54+
// the name is always the platform followed by a suffix of either "Main" or "Test" (e.g. jvmMain, commonTest, etc)
55+
val platform = name.substring(0, name.length - 4)
56+
kotlin.srcDir("$platform/$srcDir")
57+
resources.srcDir("$platform/${resourcesPrefix}resources")
58+
59+
languageSettings.progressiveMode = true
60+
61+
// have generated sdk's opt-in to internal runtime features
62+
optinAnnotations.forEach { languageSettings.optIn(it) }
6063
}
61-
}
62-
}
63-
64-
tasks.test {
65-
useJUnitPlatform()
66-
testLogging {
67-
events("passed", "skipped", "failed")
68-
showStandardStreams = true
69-
showStackTraces = true
70-
showExceptions = true
71-
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
72-
}
73-
}
74-
75-
76-
tasks.compileKotlin {
77-
kotlinOptions {
78-
jvmTarget = "1.8" // this is the default but it's better to be explicit (e.g. it may change in Kotlin 1.5)
79-
allWarningsAsErrors = false // FIXME Tons of errors occur in generated code
80-
}
81-
}
82-
tasks.compileTestKotlin {
83-
kotlinOptions {
84-
jvmTarget = "1.8" // this is the default but it's better to be explicit (e.g. it may change in Kotlin 1.5)
85-
allWarningsAsErrors = false // FIXME Tons of errors occur in generated code
86-
// Enable coroutine runTests in 1.6.10
87-
// NOTE: may be removed after coroutines-test runTests becomes stable
88-
freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn"
89-
}
90-
}
9164

92-
// FIXME - we can remove this when we implement generated services as multiplatform.
93-
setOutgoingVariantMetadata()
65+
getByName("commonMain") {
66+
kotlin.srcDir("generated-src/main/kotlin")
67+
}
9468

95-
val sourcesJar by tasks.creating(Jar::class) {
96-
group = "publishing"
97-
description = "Assembles Kotlin sources jar"
98-
classifier = "sources"
99-
from(sourceSets.getByName("main").allSource)
100-
}
69+
getByName("commonTest") {
70+
kotlin.srcDir("generated-src/test")
10171

102-
// FIXME - kotlin multiplatform configures publications for you so when we switch we can remove this
103-
// and just apply "publish.gradle" from the set of root gradle scripts (just like we do for the runtime)
104-
plugins.apply("maven-publish")
105-
publishing {
106-
publications {
107-
create<MavenPublication>("sdk"){
108-
from(components["java"])
109-
artifact(sourcesJar)
72+
dependencies {
73+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
74+
implementation(project(":aws-runtime:testing"))
75+
}
11076
}
11177
}
112-
}
113-
114-
apply(from = rootProject.file("gradle/publish.gradle"))
115-
116-
if (project.file("e2eTest").exists()) {
11778

118-
kotlin.target.compilations {
119-
val main by getting
120-
val e2eTest by creating {
121-
defaultSourceSet {
122-
kotlin.srcDir("e2eTest")
123-
dependencies {
124-
implementation(main.compileDependencyFiles + main.runtimeDependencyFiles + main.output.classesDirs)
79+
if (project.file("e2eTest").exists()) {
80+
jvm().compilations {
81+
val main by getting
82+
val e2eTest by creating {
83+
defaultSourceSet {
84+
kotlin.srcDir("e2eTest")
85+
86+
dependencies {
87+
// Compile against the main compilation's compile classpath and outputs:
88+
implementation(main.compileDependencyFiles + main.output.classesDirs)
89+
90+
implementation(kotlin("test"))
91+
implementation(kotlin("test-junit5"))
92+
implementation(project(":aws-runtime:testing"))
93+
implementation(project(":tests:e2e-test-util"))
94+
}
95+
}
12596

126-
implementation(kotlin("test"))
127-
implementation(kotlin("test-junit5"))
128-
implementation(project(":aws-runtime:testing"))
129-
implementation(project(":tests:e2e-test-util"))
97+
kotlinOptions {
98+
// Enable coroutine runTests in 1.6.10
99+
// NOTE: may be removed after coroutines-test runTests becomes stable
100+
freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn"
130101
}
131-
}
132-
kotlinOptions {
133-
// Enable coroutine runTests in 1.6.10
134-
// NOTE: may be removed after coroutines-test runTests becomes stable
135-
freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn"
136-
}
137102

138-
tasks.register<Test>("e2eTest") {
139-
description = "Run e2e service tests"
140-
group = "verification"
141-
classpath = compileDependencyFiles + runtimeDependencyFiles
142-
testClassesDirs = output.classesDirs
143-
useJUnitPlatform()
144-
testLogging {
145-
events("passed", "skipped", "failed")
146-
showStandardStreams = true
147-
showStackTraces = true
148-
showExceptions = true
149-
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
103+
tasks.register<Test>("e2eTest") {
104+
description = "Run e2e service tests"
105+
group = "verification"
106+
107+
// Run the tests with the classpath containing the compile dependencies (including 'main'),
108+
// runtime dependencies, and the outputs of this compilation:
109+
classpath = compileDependencyFiles + runtimeDependencyFiles + output.allOutputs
110+
111+
// Run only the tests from this compilation's outputs:
112+
testClassesDirs = output.classesDirs
113+
114+
useJUnitPlatform()
115+
testLogging {
116+
events("passed", "skipped", "failed")
117+
showStandardStreams = true
118+
showStackTraces = true
119+
showExceptions = true
120+
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
121+
}
150122
}
151123
}
152124
}
153125
}
154126
}
155-
}
156127

128+
dependencies {
129+
dokkaPlugin(project(":dokka-aws"))
130+
}
157131

158-
// fixes outgoing variant metadata: https://github.com/awslabs/smithy-kotlin/issues/258
159-
fun Project.setOutgoingVariantMetadata() {
160-
tasks.withType<JavaCompile>() {
161-
val javaVersion = JavaVersion.VERSION_1_8.toString()
162-
sourceCompatibility = javaVersion
163-
targetCompatibility = javaVersion
132+
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
133+
kotlinOptions {
134+
allWarningsAsErrors = false // FIXME Tons of errors occur in generated code
135+
jvmTarget = "1.8" // fixes outgoing variant metadata: https://github.com/awslabs/smithy-kotlin/issues/258
136+
}
164137
}
138+
139+
apply(from = rootProject.file("gradle/publish.gradle"))
165140
}

services/s3/e2eTest/S3TestUtils.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ object S3TestUtils {
3434

3535
if (testBucket == null) {
3636
testBucket = prefix + UUID.randomUUID()
37+
println("Creating S3 bucket: $testBucket")
38+
3739
client.createBucket {
3840
bucket = testBucket
3941
createBucketConfiguration {
@@ -42,7 +44,7 @@ object S3TestUtils {
4244
}
4345

4446
client.waitUntilBucketExists { bucket = testBucket }
45-
}
47+
} else println("Using existing S3 bucket: $testBucket")
4648

4749
client.putBucketLifecycleConfiguration {
4850
bucket = testBucket

0 commit comments

Comments
 (0)