Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 42 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,49 @@ and building services. These examples can be used as a template for a new projec

### Codegen (Gradle)

To use the Smithy Java code generators with Gradle, first create a Smithy Gradle project.
The recommended way to use Smithy Java code generation with Gradle is the
[**Smithy Java Plugin**](./gradle-plugin/README.md), which handles dependency management,
source set wiring, and task ordering automatically:

```kotlin
// build.gradle.kts
plugins {
`java-library` // or `java` / `application` for leaf projects
id("software.amazon.smithy.java.gradle.smithy-java") version "<smithy-java-version>"
}
```

Configure your [`smithy-build.json`](https://smithy.io/2.0/guides/smithy-build-json.html) with the
`java-codegen` plugin:

```json
{
"version": "1.0",
"plugins": {
"java-codegen": {
"service": "com.example#CoffeeShop",
"namespace": "software.amazon.smithy.java.examples",
"headerFile": "license.txt",
"modes": ["client"]
}
}
}
```

That's it, run `./gradlew build` and the plugin takes care of the rest. See the
[gradle-plugin README](./gradle-plugin/README.md) for full configuration options,
examples (types-only, client, server), and escape hatches.

<details>
<summary>Manual setup (without the Gradle plugin)</summary>

If you prefer manual control, apply [`smithy-base`](https://smithy.io/2.0/guides/gradle-plugin/index.html#smithy-gradle-plugins)
directly:

> [!NOTE]
> You can use the `smithy init` [CLI](https://smithy.io/2.0/guides/smithy-cli/index.html) command to create a new
> Smithy Gradle project. The command `smithy init --template quickstart-gradle` will create a new basic Smithy Gradle project.

Then apply the [`smithy-base`](https://smithy.io/2.0/guides/gradle-plugin/index.html#smithy-gradle-plugins) gradle plugin to
your project.

```diff
// build.gradle.kts
plugins {
Expand All @@ -67,28 +101,8 @@ dependencies {
}
```

Now, configure your [`smithy-build`](https://smithy.io/2.0/guides/smithy-build-json.html) to use one of the
Smithy Java codegen plugins. For example, to generate a client for a `CoffeeShop` service we would
add the following to our `smithy-build.json`:

```diff
// smithy-build.json
{
...
"plugins": {
+ "java-codegen": {
+ "service": "com.example#CoffeeShop",
+ "namespace": "software.amazon.smithy.java.examples",
+ "headerFile": "license.txt",
+ "modes": ["client"]
+ }
}
}
```

Your project is now configured to generate Java code from our model. To execute a build run the
gradle `build` task for your project. To compile the generated code as part of your project,
add the generated package to your `main` sourceSet. For example:
To compile the generated code as part of your project,
add the generated package to your `main` sourceSet:

```kotlin
// build.gradle.kts
Expand All @@ -114,6 +128,8 @@ tasks.named("compileJava") {
}
```

</details>

### Stability

Classes and API's annotated with `@SmithyInternal` are for internal use by Smithy-Java libraries and should **not** be
Expand Down
93 changes: 14 additions & 79 deletions benchmarks/serde-benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ plugins {
id("smithy-java.java-conventions")
id("com.gradleup.shadow")
id("smithy-java.jmh-conventions")
id("software.amazon.smithy.gradle.smithy-base")
id("software.amazon.smithy.java.gradle.smithy-java")
}

description = "Serde (serialization/deserialization) microbenchmarks for smithy-java codecs."

// Not published. No `smithy-java.module-conventions`, no publishing, no BOM entry.
smithyJava {
projections.addAll(
"aws-json-rpc-1-0-client",
"aws-query-client",
"rest-json-client",
"rest-xml-client",
"rpc-v2-cbor-client",
)
}

// Benchmarks intentionally target JDK 25 (the rest of smithy-java targets 21).
// Performance measurements should reflect the latest JIT/runtime improvements
// available to consumers; the runtime deps were compiled for 21 but run fine
// on a newer JVM.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
Expand All @@ -24,33 +29,18 @@ tasks.withType<JavaCompile>().configureEach {
}

dependencies {
// Smithy traits needed at build time (smithy-build) and at runtime (when
// BenchmarkTestCases loads the model). The jmh configuration extends
// implementation, so these are available to both.
implementation(libs.smithy.model)
implementation(libs.smithy.aws.traits)
implementation(libs.smithy.protocol.traits)
implementation(libs.smithy.protocol.test.traits)
implementation(libs.smithy.utils)

// The Smithy Java codegen plugin produces typed shape classes plus
// ApiOperation classes per service (see `smithy-build.json`). The
// client-core dep is required because the generated client classes
// reference it.
smithyBuild(project(":codegen:codegen-plugin"))
smithyBuild(project(":client:client-core"))

// smithy-java runtime stack — what we are benchmarking.
jmh(project(":core"))
jmh(project(":io"))
jmh(project(":logging"))
jmh(project(":codecs:json-codec", configuration = "shadow"))
jmh(project(":codecs:cbor-codec"))
jmh(project(":codecs:xml-codec"))

// Client protocols — every benchmark drives the corresponding
// ClientProtocol#createRequest / #deserializeResponse, mirroring the
// reference implementation's protocol-level entry points.
jmh(project(":client:client-core"))
jmh(project(":client:client-http"))
jmh(project(":client:client-http-binding"))
Expand All @@ -60,24 +50,17 @@ dependencies {
jmh(project(":aws:client:aws-client-restjson"))
jmh(project(":aws:client:aws-client-restxml"))

// Protocol test document for converting test case params into typed shapes.
jmh(project(":protocol-test-harness"))
}

// Smithy benchmark model files (tagged @httpRequestTests / @httpResponseTests
// with the `serde-benchmark` tag).
//
// At runtime BenchmarkContext loads the model via
// `Model.assembler().discoverModels()`, which walks `META-INF/smithy/manifest`
// resources on the classpath.
abstract class GenerateSmithyManifest : DefaultTask() {
@get:InputDirectory
abstract val sourceDir: DirectoryProperty

@get:OutputDirectory
abstract val outputDir: DirectoryProperty

@org.gradle.api.tasks.TaskAction
@TaskAction
fun run() {
val outRoot = outputDir.get().asFile
val smithyDir = outRoot.resolve("META-INF/smithy")
Expand All @@ -104,50 +87,14 @@ val generateSmithyManifest by tasks.registering(GenerateSmithyManifest::class) {
outputDir.set(layout.buildDirectory.dir("generated-resources/smithy-manifest"))
}

// Wire each codegen projection's output into the jmh source set. There are
// five projections (one per service); each emits Java source under
// `build/smithyprojections/<project>/<projection>/java-codegen/java` into a
// distinct package so same-named typed shapes from different protocols don't
// collide.
val codegenProjections =
listOf(
"aws-json-rpc-1-0-client",
"aws-query-client",
"rest-json-client",
"rest-xml-client",
"rpc-v2-cbor-client",
)

afterEvaluate {
val projectionPaths =
codegenProjections.map { name ->
smithy.getPluginProjectionPath(name, "java-codegen").get()
}
sourceSets.named("jmh") {
java {
projectionPaths.forEach { srcDir("$it/java") }
}
resources {
projectionPaths.forEach { srcDir("$it/resources") }
srcDir(generateSmithyManifest)
}
sourceSets.named("jmh") {
resources {
srcDir(generateSmithyManifest)
}
}

tasks.named("processJmhResources") {
dependsOn(generateSmithyManifest)
dependsOn("smithyBuild")
}

// Multiple codegen projections each register META-INF/services/...SchemaIndex
// (and other SPI files). Both should be on the runtime classpath; tell
// processJmhResources to keep both entries by concatenating.
tasks.named<Copy>("processJmhResources") {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

tasks.named("compileJmhJava") {
dependsOn("smithyBuild")
}

// Test case: -Pjmh.testCaseId=rpcv2Cbor_PutItemRequest_BinaryData_S
Expand Down Expand Up @@ -176,23 +123,11 @@ jmh {
resultsFile = layout.buildDirectory.file("results/jmh/results.json")
}

// With shadow applied before jmh, jmhJar is a ShadowJar. Configure
// mergeServiceFiles() so duplicate META-INF/services/ entries from
// multiple codegen projections are concatenated rather than overwritten.
tasks.jmhJar {
mergeServiceFiles()
append("META-INF/smithy/manifest")
}

// Run the cross-language result converter. Reads the JMH JSON written by the
// `jmh` task and writes a JSON + Markdown pair conforming to the shared
// benchmark output schema. OS, instance type (via EC2 IMDS), and smithy-java
// version are detected at runtime.
//
// ./gradlew :benchmarks:serde-benchmarks:convertJmhResults
//
// Optional properties:
// -PoutputPrefix=<path> prefix for the output files (default: build/results/jmh/output)
tasks.register<JavaExec>("convertJmhResults") {
group = "benchmarks"
description = "Convert JMH JSON output to the cross-language serde benchmark schema."
Expand Down
8 changes: 8 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ val smithyJavaVersion = project.file("VERSION").readText().replace(System.lineSe
allprojects {
group = "software.amazon.smithy.java"
version = smithyJavaVersion

configurations.all {
resolutionStrategy.dependencySubstitution {
rootProject.allprojects.forEach {
substitute(module("${it.group}:${it.name}")).using(project(it.path))
}
}
}
}
println("Smithy-Java version: '${smithyJavaVersion}'")

Expand Down
24 changes: 1 addition & 23 deletions examples/basic-server/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
plugins {
`java-library`
id("software.amazon.smithy.gradle.smithy-base")
id("software.amazon.smithy.java.gradle.smithy-java")
application
}

dependencies {
val smithyJavaVersion: String by project

smithyBuild("software.amazon.smithy.java:codegen-plugin:$smithyJavaVersion")
smithyBuild("software.amazon.smithy.java:server-api:$smithyJavaVersion")

implementation("software.amazon.smithy.java:server-netty:$smithyJavaVersion")
implementation("software.amazon.smithy.java:aws-server-restjson:$smithyJavaVersion")
}
Expand All @@ -19,24 +15,6 @@ application {
mainClass = "software.amazon.smithy.java.server.example.BasicServerExample"
}

// Add generated Java files to the main sourceSet
afterEvaluate {
val serverPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-codegen").get()
sourceSets {
main {
java {
srcDir("$serverPath/java")
}
}
}
}

tasks {
compileJava {
dependsOn(smithyBuild)
}
}

// Helps Intellij IDE's discover smithy models
sourceSets {
main {
Expand Down
2 changes: 2 additions & 0 deletions examples/basic-server/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
smithyJavaVersion=+
smithyGradleVersion=1.4.0
2 changes: 2 additions & 0 deletions examples/basic-server/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

pluginManagement {
val smithyGradleVersion: String by settings
val smithyJavaVersion: String by settings

plugins {
id("software.amazon.smithy.gradle.smithy-base").version(smithyGradleVersion)
id("software.amazon.smithy.java.gradle.smithy-java").version(smithyJavaVersion)
}

repositories {
Expand Down
9 changes: 0 additions & 9 deletions examples/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@

// Substitute any maven module dependencies with and project dependencies
subprojects {
group = "software.amazon.smithy.java.example"

configurations.all {
resolutionStrategy.dependencySubstitution {
rootProject.allprojects.forEach {
substitute(module("${it.group}:${it.name}")).using(project(it.path))
}
}
}

plugins.withId("java") {
the<JavaPluginExtension>().toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
Expand Down
Loading