|
| 1 | +/** |
| 2 | + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 3 | + * SPDX-License-Identifier: Apache-2.0. |
| 4 | + */ |
| 5 | +package com.amazonaws.util.awsclientsmithygenerator.generators.endpointrules; |
| 6 | + |
| 7 | +import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriterDelegator; |
| 8 | +import com.amazonaws.util.awsclientsmithygenerator.generators.ServiceNameUtil; |
| 9 | +import software.amazon.smithy.build.PluginContext; |
| 10 | +import software.amazon.smithy.build.SmithyBuildPlugin; |
| 11 | +import software.amazon.smithy.model.Model; |
| 12 | +import software.amazon.smithy.model.node.Node; |
| 13 | +import software.amazon.smithy.model.node.ObjectNode; |
| 14 | +import software.amazon.smithy.model.node.StringNode; |
| 15 | +import software.amazon.smithy.model.shapes.ServiceShape; |
| 16 | +import software.amazon.smithy.model.shapes.ShapeId; |
| 17 | + |
| 18 | +import java.util.Map; |
| 19 | +import java.util.Optional; |
| 20 | +import java.util.stream.Collectors; |
| 21 | + |
| 22 | +/** |
| 23 | + * Generates the {@code <Prefix>EndpointRules.{h,cpp}} pair carrying the compiled BDD bytecode blob. |
| 24 | + * |
| 25 | + * <p>This is the smithy-side counterpart to the C2J {@code --skip-endpoint-rules-blob} flag: when that |
| 26 | + * flag is set the legacy generator deliberately emits nothing for these two files, leaving this plugin |
| 27 | + * to write them whole. The emitted ABI is identical to the JSON path (see {@link EndpointRulesRenderer}); |
| 28 | + * only the blob contents change from ruleset JSON to binary BDD bytecode. |
| 29 | + * |
| 30 | + * <p>The BDD is read from the {@code smithy.rules#endpointBdd} trait on the service shape and compiled by |
| 31 | + * shelling out to the in-tree {@code bdd-bytecoder.py}. The interpreter and compiler paths are supplied via |
| 32 | + * the {@code pythonExecutable} and {@code bddBytecoderPath} plugin settings (threaded in from |
| 33 | + * {@code smithy_cpp_gen.py}) because gradle's working directory cannot reach the compiler under {@code crt/}. |
| 34 | + */ |
| 35 | +public class EndpointRulesCodegenPlugin implements SmithyBuildPlugin { |
| 36 | + |
| 37 | + private static final ShapeId ENDPOINT_BDD_TRAIT = ShapeId.from("smithy.rules#endpointBdd"); |
| 38 | + private static final String DEFAULT_PYTHON = "python3"; |
| 39 | + |
| 40 | + @Override |
| 41 | + public String getName() { |
| 42 | + return "smithy-cpp-codegen-endpoint-rules"; |
| 43 | + } |
| 44 | + |
| 45 | + @Override |
| 46 | + public void execute(PluginContext context) { |
| 47 | + Model model = context.getModel(); |
| 48 | + |
| 49 | + // Mock projections stand in for legacy services with no Smithy model, hence no BDD to compile. |
| 50 | + if (context.getProjectionName().endsWith(".mock")) { |
| 51 | + return; |
| 52 | + } |
| 53 | + |
| 54 | + ObjectNode settings = context.getSettings(); |
| 55 | + Map<String, String> serviceMap = parseMapSetting(settings, "c2jMap"); |
| 56 | + Map<String, String> namespaceMap = parseNamespaceMap(settings); |
| 57 | + String pythonExecutable = settings.getStringMemberOrDefault("pythonExecutable", DEFAULT_PYTHON); |
| 58 | + String bddBytecoderPath = settings.getStringMember("bddBytecoderPath") |
| 59 | + .map(n -> n.getValue()) |
| 60 | + .orElseThrow(() -> new IllegalStateException( |
| 61 | + "endpoint-rules plugin requires the 'bddBytecoderPath' setting (path to bdd-bytecoder.py)")); |
| 62 | + |
| 63 | + CppWriterDelegator writerDelegator = new CppWriterDelegator(context.getFileManifest()); |
| 64 | + |
| 65 | + // Services without an endpointBdd trait keep resolving via the JSON path, so skip them here. |
| 66 | + model.getServiceShapes().stream() |
| 67 | + .map(service -> ServiceNameUtil.processS3CrtProjection(service, context.getProjectionName())) |
| 68 | + .filter(service -> service.hasTrait(ENDPOINT_BDD_TRAIT)) |
| 69 | + .forEach(service -> generateEndpointRules( |
| 70 | + service, serviceMap, namespaceMap, pythonExecutable, bddBytecoderPath, writerDelegator)); |
| 71 | + |
| 72 | + writerDelegator.flushWriters(); |
| 73 | + } |
| 74 | + |
| 75 | + private void generateEndpointRules(ServiceShape service, Map<String, String> serviceMap, |
| 76 | + Map<String, String> namespaceMap, String pythonExecutable, |
| 77 | + String bddBytecoderPath, CppWriterDelegator writerDelegator) { |
| 78 | + String smithyServiceName = ServiceNameUtil.getSmithyServiceName(service, serviceMap); |
| 79 | + String exportMacro = ServiceNameUtil.getExportMacro(service, serviceMap); |
| 80 | + String namespace = namespaceMap.getOrDefault(smithyServiceName, ServiceNameUtil.getServiceName(service)); |
| 81 | + String classPrefix = Optional.ofNullable(namespaceMap.get(smithyServiceName)) |
| 82 | + .map(ServiceNameUtil::capitalize) |
| 83 | + .orElse(ServiceNameUtil.getServiceNameUpperCamel(service)); |
| 84 | + |
| 85 | + String traitJson = Node.printJson(service.findTrait(ENDPOINT_BDD_TRAIT).orElseThrow().toNode()); |
| 86 | + byte[] bytecode = BddBytecoder.compile(pythonExecutable, bddBytecoderPath, traitJson, smithyServiceName); |
| 87 | + |
| 88 | + writerDelegator.useFileWriter( |
| 89 | + "include/aws/" + smithyServiceName + "/internal/" + classPrefix + "EndpointRules.h", |
| 90 | + writer -> EndpointRulesRenderer.renderHeader(writer, namespace, classPrefix, smithyServiceName, exportMacro)); |
| 91 | + writerDelegator.useFileWriter( |
| 92 | + "source/" + classPrefix + "EndpointRules.cpp", |
| 93 | + writer -> EndpointRulesRenderer.renderSource(writer, namespace, classPrefix, smithyServiceName, bytecode)); |
| 94 | + } |
| 95 | + |
| 96 | + private Map<String, String> parseMapSetting(ObjectNode settings, String key) { |
| 97 | + return settings.getMember(key) |
| 98 | + .filter(Node::isStringNode) |
| 99 | + .map(Node::expectStringNode) |
| 100 | + .map(StringNode::getValue) |
| 101 | + .map(Node::parseJsonWithComments) |
| 102 | + .map(Node::expectObjectNode) |
| 103 | + .map(mapNode -> mapNode.getMembers().entrySet().stream() |
| 104 | + .collect(Collectors.toMap( |
| 105 | + entry -> entry.getKey().getValue(), |
| 106 | + entry -> entry.getValue().expectStringNode().getValue()))) |
| 107 | + .orElse(Map.of()); |
| 108 | + } |
| 109 | + |
| 110 | + private Map<String, String> parseNamespaceMap(ObjectNode settings) { |
| 111 | + return settings.getMember("namespaceMappings") |
| 112 | + .map(node -> node.expectStringNode().getValue()) |
| 113 | + .map(jsonStr -> Node.parseJsonWithComments(jsonStr).expectObjectNode()) |
| 114 | + .map(node -> node.getMembers().entrySet().stream() |
| 115 | + .collect(Collectors.toMap( |
| 116 | + entry -> entry.getKey().getValue(), |
| 117 | + entry -> sanitize(entry.getValue().expectStringNode().getValue())))) |
| 118 | + .orElse(Map.of()); |
| 119 | + } |
| 120 | + |
| 121 | + private static String sanitize(String s) { |
| 122 | + return s.replace(" ", "").replace("-", "").replace("_", "") |
| 123 | + .replace("Amazon", "").replace("AWS", "").replace("/", ""); |
| 124 | + } |
| 125 | +} |
0 commit comments