diff --git a/.changes/next-release/feature-CodeGeneratorMavenPlugin-64c91e8.json b/.changes/next-release/feature-CodeGeneratorMavenPlugin-64c91e8.json new file mode 100644 index 000000000000..91090c74555f --- /dev/null +++ b/.changes/next-release/feature-CodeGeneratorMavenPlugin-64c91e8.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "Code Generator Maven Plugin", + "contributor": "", + "description": "Update the generator plugin to support model validation during code generation. In addition, this adds the `writeValidationReport` flag to support writing the validation report to disk." +} diff --git a/codegen-maven-plugin/pom.xml b/codegen-maven-plugin/pom.xml index af92ffb4edb8..0533043d5e7f 100644 --- a/codegen-maven-plugin/pom.xml +++ b/codegen-maven-plugin/pom.xml @@ -57,6 +57,11 @@ software.amazon.awssdk ${awsjavasdk.version} + + utils + software.amazon.awssdk + ${awsjavasdk.version} + org.junit.jupiter junit-jupiter diff --git a/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java b/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java index 4ce4e7be116b..8b088846b055 100644 --- a/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java +++ b/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java @@ -21,7 +21,11 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -30,21 +34,23 @@ import org.apache.maven.project.MavenProject; import software.amazon.awssdk.codegen.C2jModels; import software.amazon.awssdk.codegen.CodeGenerator; +import software.amazon.awssdk.codegen.IntermediateModelBuilder; import software.amazon.awssdk.codegen.internal.Utils; import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; +import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; import software.amazon.awssdk.codegen.model.rules.endpoints.EndpointTestSuiteModel; import software.amazon.awssdk.codegen.model.service.EndpointRuleSetModel; import software.amazon.awssdk.codegen.model.service.Paginators; import software.amazon.awssdk.codegen.model.service.ServiceModel; import software.amazon.awssdk.codegen.model.service.Waiters; import software.amazon.awssdk.codegen.utils.ModelLoaderUtils; +import software.amazon.awssdk.utils.StringUtils; /** * The Maven mojo to generate Java client code using software.amazon.awssdk:codegen module. */ @Mojo(name = "generate") public class GenerationMojo extends AbstractMojo { - private static final String MODEL_FILE = "service-2.json"; private static final String CUSTOMIZATION_CONFIG_FILE = "customization.config"; private static final String WAITERS_FILE = "waiters-2.json"; @@ -62,6 +68,8 @@ public class GenerationMojo extends AbstractMojo { @Parameter(property = "writeIntermediateModel", defaultValue = "false") private boolean writeIntermediateModel; + @Parameter(property = "writeValidationReport", defaultValue = "false") + private boolean writeValidationReport; @Parameter(defaultValue = "${project}", readonly = true) private MavenProject project; @@ -76,22 +84,59 @@ public void execute() throws MojoExecutionException { this.resourcesDirectory = Paths.get(outputDirectory).resolve("generated-resources").resolve("sdk-resources"); this.testsDirectory = Paths.get(outputDirectory).resolve("generated-test-sources").resolve("sdk-tests"); - findModelRoots().forEach(p -> { - Path modelRootPath = p.modelRoot; - getLog().info("Loading from: " + modelRootPath.toString()); - generateCode(C2jModels.builder() - .customizationConfig(p.customizationConfig) - .serviceModel(loadServiceModel(modelRootPath)) - .waitersModel(loadWaiterModel(modelRootPath)) - .paginatorsModel(loadPaginatorModel(modelRootPath)) - .endpointRuleSetModel(loadEndpointRuleSetModel(modelRootPath)) - .endpointTestSuiteModel(loadEndpointTestSuiteModel(modelRootPath)) - .build()); + List generationParams = initGenerationParams(); + + Map serviceNameToModelMap = new HashMap<>(); + + generationParams.forEach( + params -> { + IntermediateModel model = params.intermediateModel; + String lowercaseServiceName = StringUtils.lowerCase(model.getMetadata().getServiceName()); + IntermediateModel previous = serviceNameToModelMap.put(lowercaseServiceName, model); + if (previous != null) { + String warning = String.format("Multiple service models found with service name %s. Model validation " + + "will likely be incorrect", lowercaseServiceName); + getLog().warn(warning); + } + }); + + // Update each param with the intermediate model it shares models with, if any + generationParams.forEach(params -> { + CustomizationConfig customizationConfig = params.intermediateModel.getCustomizationConfig(); + + if (customizationConfig.getShareModelConfig() != null) { + String shareModelWithName = customizationConfig.getShareModelConfig().getShareModelWith(); + params.withShareModelsTarget(serviceNameToModelMap.get(shareModelWithName)); + } }); + + generationParams.forEach(this::generateCode); + project.addCompileSourceRoot(sourcesDirectory.toFile().getAbsolutePath()); project.addTestCompileSourceRoot(testsDirectory.toFile().getAbsolutePath()); } + private List initGenerationParams() throws MojoExecutionException { + List modelRoots = findModelRoots().collect(Collectors.toList()); + + return modelRoots.stream().map(r -> { + Path modelRootPath = r.modelRoot; + getLog().info("Loading from: " + modelRootPath.toString()); + C2jModels c2jModels = C2jModels.builder() + .customizationConfig(r.customizationConfig) + .serviceModel(loadServiceModel(modelRootPath)) + .waitersModel(loadWaiterModel(modelRootPath)) + .paginatorsModel(loadPaginatorModel(modelRootPath)) + .endpointRuleSetModel(loadEndpointRuleSetModel(modelRootPath)) + .endpointTestSuiteModel(loadEndpointTestSuiteModel(modelRootPath)) + .build(); + String intermediateModelFileNamePrefix = intermediateModelFileNamePrefix(c2jModels); + IntermediateModel intermediateModel = new IntermediateModelBuilder(c2jModels).build(); + return new GenerationParams().withIntermediateModel(intermediateModel) + .withIntermediateModelFileNamePrefix(intermediateModelFileNamePrefix); + }).collect(Collectors.toList()); + } + private Stream findModelRoots() throws MojoExecutionException { try { return Files.find(codeGenResources.toPath(), 10, this::isModelFile) @@ -111,13 +156,15 @@ private boolean isModelFile(Path p, BasicFileAttributes a) { return p.toString().endsWith(MODEL_FILE); } - private void generateCode(C2jModels models) { + private void generateCode(GenerationParams params) { CodeGenerator.builder() - .models(models) + .intermediateModel(params.intermediateModel) + .shareModelsTarget(params.shareModelsTarget) .sourcesDirectory(sourcesDirectory.toFile().getAbsolutePath()) .resourcesDirectory(resourcesDirectory.toFile().getAbsolutePath()) .testsDirectory(testsDirectory.toFile().getAbsolutePath()) - .intermediateModelFileNamePrefix(intermediateModelFileNamePrefix(models)) + .intermediateModelFileNamePrefix(params.intermediateModelFileNamePrefix) + .emitValidationReport(writeValidationReport) .build() .execute(); } @@ -178,4 +225,25 @@ private ModelRoot(Path modelRoot, CustomizationConfig customizationConfig) { this.customizationConfig = customizationConfig; } } + + private static class GenerationParams { + private IntermediateModel intermediateModel; + private IntermediateModel shareModelsTarget; + private String intermediateModelFileNamePrefix; + + public GenerationParams withIntermediateModel(IntermediateModel intermediateModel) { + this.intermediateModel = intermediateModel; + return this; + } + + public GenerationParams withShareModelsTarget(IntermediateModel shareModelsTarget) { + this.shareModelsTarget = shareModelsTarget; + return this; + } + + public GenerationParams withIntermediateModelFileNamePrefix(String intermediateModelFileNamePrefix) { + this.intermediateModelFileNamePrefix = intermediateModelFileNamePrefix; + return this; + } + } }