Skip to content

Commit

Permalink
Merge pull request #2 from JordonPhillips/shape-generation
Browse files Browse the repository at this point in the history
Generate basic Go module with structures
  • Loading branch information
JordonPhillips authored Mar 18, 2020
2 parents daca317 + 5c8d7bb commit 55b5ac8
Show file tree
Hide file tree
Showing 11 changed files with 893 additions and 6 deletions.
8 changes: 2 additions & 6 deletions codegen/smithy-go-codegen-test/smithy-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@
"plugins": {
"go-codegen": {
"service": "example.weather#Weather",
"targetNamespace": "Weather",
"package": "weather",
"packageVersion": "0.0.1",
"packageJson": {
"license": "Apache-2.0"
}
"module": "weather",
"moduleVersion": "0.0.1"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.smithy.go.codegen;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.utils.StringUtils;

/**
* Utility methods likely to be needed across packages.
*/
public final class CodegenUtils {

private static final Logger LOGGER = Logger.getLogger(CodegenUtils.class.getName());

private CodegenUtils() {}

/**
* Executes a given shell command in a given directory.
*
* @param command The string command to execute, e.g. "go fmt".
* @param directory The directory to run the command in.
* @return Returns the console output of the command.
*/
public static String runCommand(String command, Path directory) {
String[] finalizedCommand;
if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
finalizedCommand = new String[]{"cmd.exe", "/c", command};
} else {
finalizedCommand = new String[]{"sh", "-c", command};
}

ProcessBuilder processBuilder = new ProcessBuilder(finalizedCommand)
.redirectErrorStream(true)
.directory(directory.toFile());

try {
Process process = processBuilder.start();
List<String> output = new ArrayList<>();

// Capture output for reporting.
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
process.getInputStream(), Charset.defaultCharset()))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
LOGGER.finest(line);
output.add(line);
}
}

process.waitFor();
process.destroy();

String joinedOutput = String.join(System.lineSeparator(), output);
if (process.exitValue() != 0) {
throw new CodegenException(String.format(
"Command `%s` failed with output:%n%n%s", command, joinedOutput));
}
return joinedOutput;
} catch (InterruptedException | IOException e) {
throw new CodegenException(e);
}
}

/**
* Gets the name under which the given package will be exported by default.
*
* @param packageName The full package name of the exported package.
* @return The name a the package will be imported under by default.
*/
public static String getDefaultPackageImportName(String packageName) {
if (StringUtils.isBlank(packageName) || !packageName.contains("/")) {
return packageName;
}
return packageName.substring(packageName.lastIndexOf('/') + 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.smithy.go.codegen;

import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import software.amazon.smithy.build.FileManifest;
import software.amazon.smithy.build.PluginContext;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.neighbor.Walker;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeVisitor;
import software.amazon.smithy.model.shapes.StructureShape;

/**
* Orchestrates Go client generation.
*/
final class CodegenVisitor extends ShapeVisitor.Default<Void> {

private static final Logger LOGGER = Logger.getLogger(CodegenVisitor.class.getName());

private final GoSettings settings;
private final Model model;
private final Model modelWithoutTraitShapes;
private final ServiceShape service;
private final FileManifest fileManifest;
private final SymbolProvider symbolProvider;
private final GoDelegator writers;

CodegenVisitor(PluginContext context) {
settings = GoSettings.from(context.getSettings());
model = context.getModel();
modelWithoutTraitShapes = context.getModelWithoutTraitShapes();
service = settings.getService(model);
fileManifest = context.getFileManifest();
LOGGER.info(() -> "Generating Go client for service " + service.getId());

symbolProvider = GoCodegenPlugin.createSymbolProvider(model);
writers = new GoDelegator(settings, model, fileManifest, symbolProvider);
}

void execute() {
// Generate models that are connected to the service being generated.
LOGGER.fine("Walking shapes from " + service.getId() + " to find shapes to generate");
Set<Shape> serviceShapes = new TreeSet<>(new Walker(modelWithoutTraitShapes).walkShapes(service));

for (Shape shape : serviceShapes) {
shape.accept(this);
}

LOGGER.fine("Flushing go writers");
writers.flushWriters();

LOGGER.fine("Generating go.mod file");
GoModGenerator.writeGoMod(settings, fileManifest);

LOGGER.fine("Running go fmt");
CodegenUtils.runCommand("go fmt", fileManifest.getBaseDir());
}

@Override
protected Void getDefault(Shape shape) {
return null;
}

@Override
public Void structureShape(StructureShape shape) {
writers.useShapeWriter(shape, writer -> new StructureGenerator(model, symbolProvider, writer, shape).run());
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.smithy.go.codegen;

import software.amazon.smithy.build.PluginContext;
import software.amazon.smithy.build.SmithyBuildPlugin;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.model.Model;

/**
* Plugin to trigger Go code generation.
*/
public final class GoCodegenPlugin implements SmithyBuildPlugin {
@Override
public String getName() {
return "go-codegen";
}

@Override
public void execute(PluginContext context) {
new CodegenVisitor(context).execute();
}

/**
* Creates a Go symbol provider.
* @param model The model to generate symbols for.
* @return Returns the created provider.
*/
public static SymbolProvider createSymbolProvider(Model model) {
return new SymbolVisitor(model);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.smithy.go.codegen;

import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import software.amazon.smithy.build.FileManifest;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.Shape;

/**
* Manages writers for Go files.
*/
final class GoDelegator {

private final GoSettings settings;
private final Model model;
private final FileManifest fileManifest;
private final SymbolProvider symbolProvider;
private final Map<String, GoWriter> writers = new HashMap<>();

GoDelegator(GoSettings settings, Model model, FileManifest fileManifest, SymbolProvider symbolProvider) {
this.settings = settings;
this.model = model;
this.fileManifest = fileManifest;
this.symbolProvider = symbolProvider;
}

/**
* Writes all pending writers to disk and then clears them out.
*/
void flushWriters() {
writers.forEach((filename, writer) -> fileManifest.writeFile(filename, writer.toString()));
writers.clear();
}

/**
* Gets a previously created writer or creates a new one if needed.
*
* @param shape Shape to create the writer for.
* @param writerConsumer Consumer that accepts and works with the file.
*/
void useShapeWriter(Shape shape, Consumer<GoWriter> writerConsumer) {
Symbol symbol = symbolProvider.toSymbol(shape);
String namespace = symbol.getNamespace();
if (namespace.equals(".")) {
namespace = CodegenUtils.getDefaultPackageImportName(settings.getModuleName());
}
GoWriter writer = checkoutWriter(symbol.getDefinitionFile(), namespace);

writer.pushState();
writerConsumer.accept(writer);
writer.popState();
}

private GoWriter checkoutWriter(String filename, String namespace) {
String formattedFilename = Paths.get(filename).normalize().toString();
boolean needsNewline = writers.containsKey(formattedFilename);

GoWriter writer = writers.computeIfAbsent(formattedFilename, f -> new GoWriter(namespace));

if (needsNewline) {
writer.write("\n");
}

return writer;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.smithy.go.codegen;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import software.amazon.smithy.build.FileManifest;
import software.amazon.smithy.codegen.core.CodegenException;

/**
* Generates a go.mod file for the project.
*
* <p>See here for more information on the format: https://github.com/golang/go/wiki/Modules#gomod
*
* TODO: pull in dependencies
*/
final class GoModGenerator {

private GoModGenerator() {}

static void writeGoMod(GoSettings settings, FileManifest manifest) {
Path goModFile = manifest.getBaseDir().resolve("go.mod");

// `go mod init` will fail if the `go.mod` already exists, so this deletes
// it if it's present in the output. While it's technically possible
// to simply edit the file, it's easier to just start fresh.
if (Files.exists(goModFile)) {
try {
Files.delete(goModFile);
} catch (IOException e) {
throw new CodegenException("Failed to delete existing go.mod file", e);
}
}
CodegenUtils.runCommand("go mod init " + settings.getModuleName(), manifest.getBaseDir());
}
}
Loading

0 comments on commit 55b5ac8

Please sign in to comment.