Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(codegen): create go.mod file automatically #405

Merged
merged 17 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion codegen/smithy-go-codegen-test/smithy-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"go-codegen": {
"service": "example.weather#Weather",
"module": "github.com/aws/smithy-go/internal/tests/service/weather",
"moduleVersion": "0.0.1"
"moduleVersion": "0.0.1",
"generateGoMod": true,
"goDirective": "1.18"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ void execute() {
List<SymbolDependency> dependencies = writers.getDependencies();
writers.flushWriters();

GoModGenerator.writeGoMod(settings, fileManifest, SymbolDependency.gatherDependencies(dependencies.stream()));

LOGGER.fine("Generating build manifest file");
ManifestWriter.writeManifest(settings, model, fileManifest, dependencies);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2023 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 java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import software.amazon.smithy.build.FileManifest;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.codegen.core.SymbolDependency;

/**
* 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
*/
final class GoModGenerator {

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

private GoModGenerator() {}

static void writeGoMod(
GoSettings settings,
FileManifest manifest,
Map<String, Map<String, SymbolDependency>> dependencies
) {
Boolean generateGoMod = settings.getGenerateGoMod();
if (!generateGoMod) {
return;
}

Path goModFile = manifest.getBaseDir().resolve("go.mod");
LOGGER.fine("Generating go.mod file at path " + goModFile.toString());

// `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);
}
}
manifest.addFile(goModFile);
CodegenUtils.runCommand("go mod init " + settings.getModuleName(), manifest.getBaseDir());

Map<String, String> externalDependencies = getExternalDependencies(dependencies);
for (Map.Entry<String, String> dependency : externalDependencies.entrySet()) {
CodegenUtils.runCommand(
String.format("go mod edit -require=%s@%s", dependency.getKey(), dependency.getValue()),
manifest.getBaseDir());
}

CodegenUtils.runCommand(
String.format("go mod edit -go=%s", settings.getGoDirective()),
manifest.getBaseDir());
}

private static Map<String, String> getExternalDependencies(
Map<String, Map<String, SymbolDependency>> dependencies
) {
return dependencies.entrySet().stream()
.filter(entry -> !entry.getKey().equals("stdlib"))
.flatMap(entry -> entry.getValue().entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey, entry -> entry.getValue().getVersion(), (a, b) -> b, TreeMap::new));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.model.Model;
Expand All @@ -30,13 +31,21 @@
*/
public final class GoSettings {

private static final String DEFAULT_GO_DIRECTIVE = "1.15";

private static final String SERVICE = "service";
private static final String MODULE_NAME = "module";
private static final String MODULE_DESCRIPTION = "moduleDescription";
private static final String MODULE_VERSION = "moduleVersion";
private static final String GENERATE_GO_MOD = "generateGoMod";
private static final String GO_DIRECTIVE = "goDirective";

private ShapeId service;
private String moduleName;
private String moduleDescription = "";
private String moduleVersion;
private Boolean generateGoMod = false;
private String goDirective = DEFAULT_GO_DIRECTIVE;
private ShapeId protocol;

/**
Expand All @@ -47,13 +56,16 @@ public final class GoSettings {
*/
public static GoSettings from(ObjectNode config) {
GoSettings settings = new GoSettings();
config.warnIfAdditionalProperties(Arrays.asList(SERVICE, MODULE_NAME, MODULE_DESCRIPTION));
config.warnIfAdditionalProperties(
Arrays.asList(SERVICE, MODULE_NAME, MODULE_DESCRIPTION, MODULE_VERSION, GENERATE_GO_MOD, GO_DIRECTIVE));

settings.setService(config.expectStringMember(SERVICE).expectShapeId());
settings.setModuleName(config.expectStringMember(MODULE_NAME).getValue());
settings.setModuleDescription(config.getStringMemberOrDefault(
MODULE_DESCRIPTION, settings.getModuleName() + " client"));

settings.setModuleVersion(config.getStringMemberOrDefault(MODULE_VERSION, null));
settings.setGenerateGoMod(config.getBooleanMemberOrDefault(GENERATE_GO_MOD, false));
settings.setGoDirective(config.getStringMemberOrDefault(GO_DIRECTIVE, DEFAULT_GO_DIRECTIVE));
return settings;
}

Expand Down Expand Up @@ -129,6 +141,62 @@ public void setModuleDescription(String moduleDescription) {
this.moduleDescription = Objects.requireNonNull(moduleDescription);
}

/**
* Gets the optional module version for the module that will be generated.
*
* @return Returns the module version.
*/
public Optional<String> getModuleVersion() {
return Optional.ofNullable(moduleVersion);
}

/**
* Sets the version of the module to generate.
*
* @param moduleVersion The version of the module to generate.
*/
public void setModuleVersion(String moduleVersion) {
if (moduleVersion != null) {
this.moduleVersion = moduleVersion;
}
}

/**
* Gets the flag for generating go.mod file.
*
* @return Returns if go.mod will be generated (true) or not (false)
*/
public Boolean getGenerateGoMod() {
return generateGoMod;
}

/**
* Sets the flag for generating go.mod file.
*
* @param generateGoMod If go.mod will be generated (true) or not (false)
*/
public void setGenerateGoMod(Boolean generateGoMod) {
this.generateGoMod = Objects.requireNonNull(generateGoMod);
}

/**
* Gets the optional Go directive for the module that will be generated.
*
* @return Returns the Go directive.
*/
public String getGoDirective() {
return goDirective;
}

/**
* Sets the Go directive of the module to generate.
*
* @param goDirective The Go directive of the module to generate.
*/
public void setGoDirective(String goDirective) {
this.goDirective = Objects.requireNonNull(goDirective);
}

/**
* Gets the configured protocol to generate.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import software.amazon.smithy.build.FileManifest;
Expand All @@ -45,19 +46,22 @@
* and minimum dependencies required.
*/
public final class ManifestWriter {

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

private static final String GENERATED_JSON = "generated.json";

private final String moduleName;
private final FileManifest fileManifest;
private final List<SymbolDependency> dependencies;
private final Optional<String> minimumGoVersion;
private final String goDirective;
private final boolean isUnstable;

private ManifestWriter(Builder builder) {
moduleName = SmithyBuilder.requiredState("moduleName", builder.moduleName);
fileManifest = SmithyBuilder.requiredState("fileManifest", builder.fileManifest);
dependencies = builder.dependencies.copy();
minimumGoVersion = builder.minimumGoVersion;
goDirective = builder.goDirective;
isUnstable = builder.isUnstable;
}

Expand All @@ -79,6 +83,7 @@ public static void writeManifest(
.moduleName(settings.getModuleName())
.fileManifest(fileManifest)
.dependencies(dependencies)
.goDirective(settings.getGoDirective())
.isUnstable(settings.getService(model).getTrait(UnstableTrait.class).isPresent())
.build()
.writeManifest();
Expand All @@ -100,14 +105,16 @@ public void writeManifest() {
}
fileManifest.addFile(manifestFile);

LOGGER.fine("Creating manifest at path " + manifestFile.toString());

Node generatedJson = buildManifestFile();
fileManifest.writeFile(manifestFile.toString(), Node.prettyPrintJson(generatedJson) + "\n");

}

private Node buildManifestFile() {
List<SymbolDependency> nonStdLib = new ArrayList<>();
Optional<String> minimumGoVersion = this.minimumGoVersion;
Optional<String> minimumGoVersion = Optional.empty();

for (SymbolDependency dependency : dependencies) {
if (!dependency.getDependencyType().equals(GoDependency.Type.STANDARD_LIBRARY.toString())) {
Expand Down Expand Up @@ -142,8 +149,7 @@ private Node buildManifestFile() {
generatedFiles = generatedFiles.stream().sorted().collect(Collectors.toList());

manifestNodes.put(StringNode.from("module"), StringNode.from(moduleName));
minimumGoVersion.ifPresent(version -> manifestNodes.put(StringNode.from("go"),
StringNode.from(version)));
manifestNodes.put(StringNode.from("go"), StringNode.from(minimumGoVersion.orElse(this.goDirective)));
manifestNodes.put(StringNode.from("dependencies"), ObjectNode.objectNode(dependencyNodes));
manifestNodes.put(StringNode.from("files"), ArrayNode.fromStrings(generatedFiles));
manifestNodes.put(StringNode.from("unstable"), BooleanNode.from(isUnstable));
Expand All @@ -168,7 +174,7 @@ public static class Builder implements SmithyBuilder<ManifestWriter> {
private String moduleName;
private FileManifest fileManifest;
private final BuilderRef<List<SymbolDependency>> dependencies = BuilderRef.forList();
private Optional<String> minimumGoVersion = Optional.empty();
private String goDirective;
private boolean isUnstable;

public Builder moduleName(String moduleName) {
Expand All @@ -187,8 +193,8 @@ public Builder dependencies(List<SymbolDependency> dependencies) {
return this;
}

public Builder minimumGoVersion(String minimumGoVersion) {
this.minimumGoVersion = Optional.of(minimumGoVersion);
public Builder goDirective(String minimumGoVersion) {
this.goDirective = minimumGoVersion;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,17 @@ public static PluginContext buildMockPluginContext(
manifest,
serviceShapeId,
"example",
"0.0.1");
"0.0.1",
false);
}

public static PluginContext buildPluginContext(
Model model,
FileManifest manifest,
String serviceShapeId,
String moduleName,
String moduleVersion
String moduleVersion,
Boolean generateGoMod
) {
return PluginContext.builder()
.model(model)
Expand All @@ -88,6 +90,7 @@ public static PluginContext buildPluginContext(
serviceShapeId,
moduleName,
moduleVersion,
generateGoMod,
"Example"))
.build();
}
Expand All @@ -96,12 +99,14 @@ public static ObjectNode getSettingsNode(
String serviceShapeId,
String moduleName,
String moduleVersion,
Boolean generateGoMod,
String sdkId
) {
return Node.objectNodeBuilder()
.withMember("service", Node.from(serviceShapeId))
.withMember("module", Node.from(moduleName))
.withMember("moduleVersion", Node.from(moduleVersion))
.withMember("generateGoMod", Node.from(generateGoMod))
.withMember("homepage", Node.from("https://docs.amplify.aws/"))
.withMember("sdkId", Node.from(sdkId))
.withMember("author", Node.from("Amazon Web Services"))
Expand Down Expand Up @@ -157,4 +162,4 @@ public static void makeGoModule(Path path) throws Exception {
public static void testGoModule(Path path) throws Exception {
execute(path.toFile(), "go", "test", "-v", "./...");
}
}
}