-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add trait code generation plugin package (#2074)
Adds a new trait code generation plugin, `trait-codegen`, in a new package, `smithy-trait-codegen`. Adds a new plugin that generates Java trait classes from smithy models. The generation of java trait classes directly from smithy models removes the need to hand-write most trait implementations. Because traits definitions should be isolated in separate java packages from other smithy models and should never be projected, this plugin will only run on the `source` projection. Nested shapes within traits are also generated by this plugin. This code generation plugin defines its own codegen orchestration class `TraitCodegen` that handles the trait code generation process. The set of shapes to generate as shape classes is determined as follows: 1. Get a list of all classes with the @trait trait applied in the specified namespace 2. Filter out any traits that are not sources (see: isSourceShape) 3. Filtering out any traits with existing TraitService providers available on the classpath 4. Filtering out any traits with an excluded tag 5. Walking the closure of the applicable trait classes to pick up any nested shapes that need to be generated 6. Filtering out any member shapes from the discovered closure 7. Filtering out any prelude shapes from the discovered closure These shapes are then iterated through to generate all required trait classes and nested classes. In addition to generating trait implementation classes, this plugin also automatically adds trait Implementation classes to a generated trait service provider file (META-INF/services/software.amazon.smithy.model.traits.TraitService). Note: Blob and Union traits are not currently handled by this plugin at this time.
- Loading branch information
Showing
145 changed files
with
6,484 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
description = "Plugin for Generating Trait Code from Smithy Models" | ||
|
||
ext { | ||
displayName = "Smithy :: Trait Code Generation" | ||
moduleName = "software.amazon.smithy.traitcodegen" | ||
} | ||
|
||
dependencies { | ||
implementation project(":smithy-codegen-core") | ||
} | ||
|
||
// Set up Integration testing source sets | ||
sourceSets { | ||
create("it") { | ||
compileClasspath += sourceSets.main.output + configurations["testRuntimeClasspath"] + configurations["testCompileClasspath"] | ||
runtimeClasspath += output + compileClasspath + sourceSets.test.runtimeClasspath + sourceSets.test.output | ||
|
||
// Pull in the generated trait files | ||
java { | ||
srcDir("$buildDir/integ/") | ||
} | ||
// Add generated service provider file to resources | ||
resources { | ||
srcDirs += "$buildDir/generated-resources" | ||
} | ||
} | ||
} | ||
|
||
// Execute building of trait classes using an executable class | ||
// These traits will then be passed in to the integration test (it) | ||
// source set | ||
tasks.register("generateTraits", JavaExec) { | ||
classpath = sourceSets.test.runtimeClasspath + sourceSets.test.output | ||
mainClass = "software.amazon.smithy.traitcodegen.PluginExecutor" | ||
} | ||
|
||
// Copy generated META-INF files to a new generated-resources directory to | ||
// make it easy to include as resource srcDir | ||
def generatedMetaInf = new File("$buildDir/integ/META-INF") | ||
def destResourceDir = new File("$buildDir/generated-resources", "META-INF") | ||
tasks.register("copyGeneratedSrcs", Copy) { | ||
from generatedMetaInf | ||
into destResourceDir | ||
dependsOn("generateTraits") | ||
} | ||
|
||
|
||
// Add the integ test task | ||
tasks.register("integ", Test) { | ||
useJUnitPlatform() | ||
testClassesDirs = sourceSets.it.output.classesDirs | ||
classpath = sourceSets.it.runtimeClasspath | ||
} | ||
|
||
// Do not run checkstyle on generated trait classes | ||
tasks["checkstyleIt"].enabled = false | ||
|
||
// Force correct ordering so generated sources are available | ||
tasks["compileItJava"].dependsOn("generateTraits") | ||
tasks["compileItJava"].dependsOn("copyGeneratedSrcs") | ||
tasks["processItResources"].dependsOn("copyGeneratedSrcs") | ||
tasks["integ"].mustRunAfter("generateTraits") | ||
tasks["integ"].mustRunAfter("copyGeneratedSrcs") | ||
|
||
// Always run integ tests after base tests | ||
tasks["test"].finalizedBy("integ") | ||
|
||
// dont run spotbugs on integ tests | ||
tasks["spotbugsIt"].enabled(false) |
150 changes: 150 additions & 0 deletions
150
...-trait-codegen/src/it/java/software/amazon/smithy/traitcodegen/test/CreatesTraitTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package software.amazon.smithy.traitcodegen.test; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
import com.example.traits.StringTrait; | ||
import com.example.traits.documents.DocumentTrait; | ||
import com.example.traits.documents.StructWithNestedDocumentTrait; | ||
import com.example.traits.enums.IntEnumTrait; | ||
import com.example.traits.enums.StringEnumTrait; | ||
import com.example.traits.enums.SuitTrait; | ||
import com.example.traits.lists.ListMember; | ||
import com.example.traits.lists.NumberListTrait; | ||
import com.example.traits.lists.StringListTrait; | ||
import com.example.traits.lists.StructureListTrait; | ||
import com.example.traits.maps.MapValue; | ||
import com.example.traits.maps.StringStringMapTrait; | ||
import com.example.traits.maps.StringToStructMapTrait; | ||
import com.example.traits.mixins.StructWithMixinTrait; | ||
import com.example.traits.mixins.StructureListWithMixinMemberTrait; | ||
import com.example.traits.names.SnakeCaseStructureTrait; | ||
import com.example.traits.numbers.BigDecimalTrait; | ||
import com.example.traits.numbers.BigIntegerTrait; | ||
import com.example.traits.numbers.ByteTrait; | ||
import com.example.traits.numbers.DoubleTrait; | ||
import com.example.traits.numbers.FloatTrait; | ||
import com.example.traits.numbers.IntegerTrait; | ||
import com.example.traits.numbers.LongTrait; | ||
import com.example.traits.numbers.ShortTrait; | ||
import com.example.traits.structures.BasicAnnotationTrait; | ||
import com.example.traits.structures.NestedA; | ||
import com.example.traits.structures.NestedB; | ||
import com.example.traits.structures.StructureTrait; | ||
import com.example.traits.timestamps.DateTimeTimestampTrait; | ||
import com.example.traits.timestamps.EpochSecondsTimestampTrait; | ||
import com.example.traits.timestamps.HttpDateTimestampTrait; | ||
import com.example.traits.timestamps.TimestampTrait; | ||
import com.example.traits.uniqueitems.NumberSetTrait; | ||
import com.example.traits.uniqueitems.SetMember; | ||
import com.example.traits.uniqueitems.StringSetTrait; | ||
import com.example.traits.uniqueitems.StructureSetTrait; | ||
import java.util.stream.Stream; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
import software.amazon.smithy.model.SourceLocation; | ||
import software.amazon.smithy.model.node.ArrayNode; | ||
import software.amazon.smithy.model.node.Node; | ||
import software.amazon.smithy.model.node.ObjectNode; | ||
import software.amazon.smithy.model.shapes.ShapeId; | ||
import software.amazon.smithy.model.traits.Trait; | ||
import software.amazon.smithy.model.traits.TraitFactory; | ||
import software.amazon.smithy.utils.ListUtils; | ||
import software.amazon.smithy.utils.MapUtils; | ||
|
||
public class CreatesTraitTest { | ||
private static final ShapeId DUMMY_ID = ShapeId.from("ns.foo#foo"); | ||
private final TraitFactory provider = TraitFactory.createServiceFactory(); | ||
|
||
static Stream<Arguments> createTraitTests() { | ||
return Stream.of( | ||
// Document traits | ||
Arguments.of(DocumentTrait.ID, Node.objectNodeBuilder() | ||
.withMember("metadata", "woo") | ||
.withMember("more", "yay") | ||
.build() | ||
), | ||
Arguments.of(StructWithNestedDocumentTrait.ID, | ||
ObjectNode.objectNodeBuilder().withMember("doc", ObjectNode.builder() | ||
.withMember("foo", "bar").withMember("fizz", "buzz").build()).build()), | ||
// Enums | ||
Arguments.of(StringEnumTrait.ID, Node.from("no")), | ||
Arguments.of(IntEnumTrait.ID, Node.from(2)), | ||
Arguments.of(SuitTrait.ID, Node.from("clubs")), | ||
// Lists | ||
Arguments.of(NumberListTrait.ID, ArrayNode.fromNodes( | ||
Node.from(1), Node.from(2), Node.from(3)) | ||
), | ||
Arguments.of(StringListTrait.ID, ArrayNode.fromStrings("a", "b", "c")), | ||
Arguments.of(StructureListTrait.ID, ArrayNode.fromNodes( | ||
ListMember.builder().a("first").b(1).c("other").build().toNode(), | ||
ListMember.builder().a("second").b(2).c("more").build().toNode() | ||
)), | ||
// Maps | ||
Arguments.of(StringStringMapTrait.ID, StringStringMapTrait.builder() | ||
.putValues("a", "first").putValues("b", "other").build().toNode() | ||
), | ||
Arguments.of(StringToStructMapTrait.ID, StringToStructMapTrait.builder() | ||
.putValues("one", MapValue.builder().a("foo").b(2).build()) | ||
.putValues("two", MapValue.builder().a("bar").b(4).build()) | ||
.build().toNode() | ||
), | ||
// Mixins | ||
Arguments.of(StructureListWithMixinMemberTrait.ID, | ||
ArrayNode.fromNodes(ObjectNode.builder().withMember("a", "a").withMember("d", "d").build())), | ||
Arguments.of(StructWithMixinTrait.ID, StructWithMixinTrait.builder() | ||
.d("d").build().toNode()), | ||
// Naming Conflicts | ||
Arguments.of(SnakeCaseStructureTrait.ID, ObjectNode.builder() | ||
.withMember("snake_case_member", "stuff").build()), | ||
// Numbers | ||
Arguments.of(BigDecimalTrait.ID, Node.from(1)), | ||
Arguments.of(BigIntegerTrait.ID, Node.from(1)), | ||
Arguments.of(ByteTrait.ID, Node.from(1)), | ||
Arguments.of(DoubleTrait.ID, Node.from(1.2)), | ||
Arguments.of(FloatTrait.ID, Node.from(1.2)), | ||
Arguments.of(IntegerTrait.ID, Node.from(1)), | ||
Arguments.of(LongTrait.ID, Node.from(1L)), | ||
Arguments.of(ShortTrait.ID, Node.from(1)), | ||
// Structures | ||
Arguments.of(BasicAnnotationTrait.ID, Node.objectNode()), | ||
Arguments.of(StructureTrait.ID, StructureTrait.builder() | ||
.fieldA("a") | ||
.fieldB(true) | ||
.fieldC(NestedA.builder() | ||
.fieldN("nested") | ||
.fieldQ(false) | ||
.fieldZ(NestedB.B) | ||
.build() | ||
) | ||
.fieldD(ListUtils.of("a", "b", "c")) | ||
.fieldE(MapUtils.of("a", "one", "b", "two")) | ||
.build().toNode() | ||
), | ||
// Timestamps | ||
Arguments.of(TimestampTrait.ID, Node.from("1985-04-12T23:20:50.52Z")), | ||
Arguments.of(DateTimeTimestampTrait.ID, Node.from("1985-04-12T23:20:50.52Z")), | ||
Arguments.of(HttpDateTimestampTrait.ID, Node.from("Tue, 29 Apr 2014 18:30:38 GMT")), | ||
Arguments.of(EpochSecondsTimestampTrait.ID, Node.from(1515531081.123)), | ||
// Unique Items (sets) | ||
Arguments.of(NumberSetTrait.ID, ArrayNode.fromNodes( | ||
Node.from(1), Node.from(2), Node.from(3)) | ||
), | ||
Arguments.of(StringSetTrait.ID, ArrayNode.fromStrings("a", "b", "c")), | ||
Arguments.of(StructureSetTrait.ID, ArrayNode.fromNodes( | ||
SetMember.builder().a("first").b(1).c("other").build().toNode(), | ||
SetMember.builder().a("second").b(2).c("more").build().toNode() | ||
)), | ||
// Strings | ||
Arguments.of(StringTrait.ID, Node.from("SPORKZ SPOONS YAY! Utensils.")) | ||
); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("createTraitTests") | ||
void createsTraitFromNode(ShapeId traitId, Node fromNode) { | ||
Trait trait = provider.createTrait(traitId, DUMMY_ID, fromNode).orElseThrow(RuntimeException::new); | ||
assertEquals(SourceLocation.NONE, trait.getSourceLocation()); | ||
assertEquals(trait, provider.createTrait(traitId, DUMMY_ID, trait.toNode()).orElseThrow(RuntimeException::new)); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
...it-codegen/src/it/java/software/amazon/smithy/traitcodegen/test/DeprecatedStringTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package software.amazon.smithy.traitcodegen.test; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
|
||
import com.example.traits.DeprecatedStringTrait; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class DeprecatedStringTest { | ||
@Test | ||
void checkForDeprecatedAnnotation() { | ||
Deprecated deprecated = DeprecatedStringTrait.class.getAnnotation(Deprecated.class); | ||
assertNotNull(deprecated); | ||
} | ||
} |
Oops, something went wrong.