From 389eb3714d093bc9766637f2ba32b763e6f13e77 Mon Sep 17 00:00:00 2001 From: Jordon Phillips Date: Fri, 20 Oct 2023 19:03:49 +0200 Subject: [PATCH] Add automatic integration configuration This adds a configuration step for integrations, allowing them to use `smithy-build.json` settings that aren't used by the generator that they target. Generators using the node mapper helper for their settings will have this set for free. Others will need to grab it themselves. --- .../codegen/core/SmithyIntegration.java | 49 ++++++++++++++++++- .../core/directed/CodegenDirector.java | 48 +++++++++++++++++- .../core/directed/CapturingIntegration.java | 18 +++++++ .../core/directed/CodegenDirectorTest.java | 44 +++++++++++++++++ ...ithy.codegen.core.directed.TestIntegration | 1 + 5 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CapturingIntegration.java create mode 100644 smithy-codegen-core/src/test/resources/META-INF/services/software.amazon.smithy.codegen.core.directed.TestIntegration diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/SmithyIntegration.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/SmithyIntegration.java index 5ab5ddad2b5..1e60ff918fa 100644 --- a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/SmithyIntegration.java +++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/SmithyIntegration.java @@ -19,6 +19,7 @@ import java.util.List; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.utils.AbstractCodeWriter; import software.amazon.smithy.utils.CodeInterceptor; import software.amazon.smithy.utils.CodeSection; @@ -70,6 +71,50 @@ default byte priority() { return 0; } + /** + * Configures the integration. + * + *

This provides access to both the parsed settings for the generator and + * an unparsed {@link ObjectNode} containing settings for all integrations. + * Integrations SHOULD put all of their settings inside a nested object so + * that they don't experience conflicts with other integrations. + * + *

The following {@code smithy-build.json} file contains an example of how + * this configuration will be set. + * + *

{@code
+     * {
+     *     "version": "1.0",
+     *     "projections": {
+     *         "codegen-projection": {
+     *             "plugins": {
+     *                 "code-generator": {
+     *                     "service": "com.example#DocumentedService",
+     *                     "integrations": {
+     *                         "my-integration": {
+     *                             "example-setting": "foo"
+     *                         }
+     *                     }
+     *                 }
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+ * + *

In this example, everything under the key {@code integrations} will be + * provided as the {@code rawSettings} value and the {@code my-integration} key + * represents the settings for a particular integration. + * + *

Integrations SHOULD use modeled traits as much as possible to drive + * configuration. This is intended for configuration that doesn't make sense + * as a trait, such as configuring a documentation theme. + * + * @param settings Settings used to generate code. + * @param integrationSettings Settings used to configure integrations. + */ + default void configure(S settings, ObjectNode integrationSettings) {} + /** * Gets the names of integrations that this integration must come before. * @@ -103,7 +148,7 @@ default List runAfter() { *

By default, this method will return the given {@code model} as-is. * * @param model Model being generated. - * @param settings Setting used to generate code. + * @param settings Settings used to generate code. * @return Returns the updated model. */ default Model preprocessModel(Model model, S settings) { @@ -122,7 +167,7 @@ default Model preprocessModel(Model model, S settings) { *

This integration method should be called only after {@link #preprocessModel}. * * @param model Model being generated. - * @param settings Setting used to generate. + * @param settings Settings used to generate. * @param symbolProvider The original {@code SymbolProvider}. * @return The decorated {@code SymbolProvider}. */ diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java index 127f746f942..474a04fa003 100644 --- a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java +++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java @@ -36,6 +36,7 @@ import software.amazon.smithy.model.neighbor.Walker; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.NodeMapper; +import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.EnumShape; import software.amazon.smithy.model.shapes.IntEnumShape; import software.amazon.smithy.model.shapes.OperationShape; @@ -74,6 +75,7 @@ public final class CodegenDirector< private ShapeId service; private Model model; private S settings; + private ObjectNode integrationSettings = Node.objectNode(); private FileManifest fileManifest; private Supplier> integrationFinder; private DirectedCodegen directedCodegen; @@ -159,6 +161,9 @@ public void settings(S settings) { * You will need to manually deserialize your settings if using types that * are not supported by Smithy's {@link NodeMapper}. * + *

This will also set {@link #integrationSettings} if the {@code integrations} + * key is present. + * * @param settingsType Settings type to deserialize into. * @param settingsNode Settings node value to deserialize. * @return Returns the deserialized settings as this is needed to provide a service shape ID. @@ -167,9 +172,47 @@ public S settings(Class settingsType, Node settingsNode) { LOGGER.fine(() -> "Loading codegen settings from node value: " + settingsNode.getSourceLocation()); S deserialized = new NodeMapper().deserialize(settingsNode, settingsType); settings(deserialized); + settingsNode.asObjectNode() + .flatMap(node -> node.getObjectMember("integrations")) + .ifPresent(this::integrationSettings); return deserialized; } + /** + * Sets the settings node to be passed to integrations. + * + *

Generators MUST set this with the {@code integrations} key from their + * plugin settings. + * + *

{@code
+     * {
+     *     "version": "1.0",
+     *     "projections": {
+     *         "codegen-projection": {
+     *             "plugins": {
+     *                 "code-generator": {
+     *                     "service": "com.example#DocumentedService",
+     *                     "integrations": {
+     *                         "my-integration": {
+     *                             "example-setting": "foo"
+     *                         }
+     *                     }
+     *                 }
+     *             }
+     *         }
+     *     }
+     * }
+     * }
+ * + *

In this example, the value of the {@code integrations} key is what must + * be passed to this method. + * + * @param integrationSettings Settings used to configure integrations. + */ + public void integrationSettings(ObjectNode integrationSettings) { + this.integrationSettings = Objects.requireNonNull(integrationSettings); + } + /** * Sets the required file manifest used to write files to disk. * @@ -366,7 +409,10 @@ private void performModelTransforms() { private List findIntegrations() { LOGGER.fine(() -> "Finding integration implementations of " + integrationClass.getName()); List integrations = SmithyIntegration.sort(integrationFinder.get()); - integrations.forEach(i -> LOGGER.finest(() -> "Found integration " + i.getClass().getCanonicalName())); + integrations.forEach(i -> { + LOGGER.finest(() -> "Found integration " + i.getClass().getCanonicalName()); + i.configure(settings, integrationSettings); + }); return integrations; } diff --git a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CapturingIntegration.java b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CapturingIntegration.java new file mode 100644 index 00000000000..6a185a941ac --- /dev/null +++ b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CapturingIntegration.java @@ -0,0 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.codegen.core.directed; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; + +public class CapturingIntegration implements TestIntegration { + public ObjectNode integrationSettings = Node.objectNode(); + + @Override + public void configure(TestSettings settings, ObjectNode integrationSettings) { + this.integrationSettings = integrationSettings; + } +} diff --git a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java index 295633630c9..9d6c61f6e5c 100644 --- a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java +++ b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java @@ -19,6 +19,9 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; import java.util.ArrayList; import java.util.List; @@ -33,6 +36,7 @@ import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.ExpectationNotMetException; import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.ShapeId; public class CodegenDirectorTest { @@ -42,6 +46,8 @@ private static final class TestDirected implements DirectedCodegen generatedEnumTypeEnums = new ArrayList<>(); public final List generatedStringTypeEnums = new ArrayList<>(); + public final List integrations = new ArrayList<>(); + @Override public SymbolProvider createSymbolProvider(CreateSymbolProviderDirective directive) { return shape -> Symbol.builder() @@ -52,6 +58,8 @@ public SymbolProvider createSymbolProvider(CreateSymbolProviderDirective directive) { + integrations.clear(); + integrations.addAll(directive.integrations()); WriterDelegator delegator = new WriterDelegator<>( directive.fileManifest(), directive.symbolProvider(), @@ -339,4 +347,40 @@ public void testShapesGenerationWithoutOrder() { ShapeId.from("smithy.example#Foo") )); } + + @Test + public void testConfiguresIntegrations() { + TestDirected testDirected = new TestDirected(); + CodegenDirector runner + = new CodegenDirector<>(); + FileManifest manifest = new MockManifest(); + Model model = Model.assembler() + .addImport(getClass().getResource("needs-sorting.smithy")) + .assemble() + .unwrap(); + + ObjectNode integrationSettings = Node.objectNode().withMember("spam", "eggs"); + ObjectNode settings = Node.objectNode() + .withMember("foo", "hi") + .withMember("integrations", integrationSettings); + runner.settings(TestSettings.class, settings); + runner.directedCodegen(testDirected); + runner.fileManifest(manifest); + runner.service(ShapeId.from("smithy.example#Foo")); + runner.model(model); + runner.integrationClass(TestIntegration.class); + runner.performDefaultCodegenTransforms(); + runner.shapeGenerationOrder(ShapeGenerationOrder.NONE); + runner.run(); + + assertThat(testDirected.integrations, not(empty())); + CapturingIntegration capturingIntegration = null; + for (TestIntegration integration : testDirected.integrations) { + if (integration instanceof CapturingIntegration) { + capturingIntegration = (CapturingIntegration) integration; + } + } + assertThat(capturingIntegration, notNullValue()); + assertThat(capturingIntegration.integrationSettings, equalTo(integrationSettings)); + } } diff --git a/smithy-codegen-core/src/test/resources/META-INF/services/software.amazon.smithy.codegen.core.directed.TestIntegration b/smithy-codegen-core/src/test/resources/META-INF/services/software.amazon.smithy.codegen.core.directed.TestIntegration new file mode 100644 index 00000000000..7b3cf821d31 --- /dev/null +++ b/smithy-codegen-core/src/test/resources/META-INF/services/software.amazon.smithy.codegen.core.directed.TestIntegration @@ -0,0 +1 @@ +software.amazon.smithy.codegen.core.directed.CapturingIntegration