Skip to content

Commit

Permalink
Add support for creating a manifest description of generated artifacts.
Browse files Browse the repository at this point in the history
  • Loading branch information
skmcgrail committed Apr 7, 2021
1 parent cbcfb40 commit c66217f
Show file tree
Hide file tree
Showing 6 changed files with 693 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,10 @@ void execute() {
writers.flushWriters();

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

LOGGER.fine("Generating build manifest file");
ManifestWriter.writeManifest(settings, fileManifest, dependencies);

LOGGER.fine("Running go fmt");
CodegenUtils.runCommand("gofmt -w -s .", fileManifest.getBaseDir());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.codegen.core.SymbolDependency;
import software.amazon.smithy.codegen.core.SymbolDependencyContainer;
import software.amazon.smithy.utils.SetUtils;
Expand Down Expand Up @@ -54,6 +55,34 @@ private GoDependency(Builder builder) {
.build();
}

/**
* Given two {@link SymbolDependency} referring to the same package, return the minimum dependency version using
* minimum version selection. The version strings must be semver compatible.
*
* @param dx the first dependency
* @param dy the second dependency
* @return the minimum dependency
*/
public static SymbolDependency mergeByMinimumVersionSelection(SymbolDependency dx, SymbolDependency dy) {
SemanticVersion sx = SemanticVersion.parseVersion(dx.getVersion());
SemanticVersion sy = SemanticVersion.parseVersion(dy.getVersion());

// This *shouldn't* happen in Go since the Go module import path must end with the major version component.
// Exception is the case where the major version is 0 or 1.
if (sx.getMajor() != sy.getMajor() && !(sx.getMajor() == 0 || sy.getMajor() == 0)) {
throw new CodegenException(String.format("Dependency %s has conflicting major versions",
dx.getPackageName()));
}

int cmp = sx.compareTo(sy);
if (cmp < 0) {
return dy;
} else if (cmp > 0) {
return dx;
}
return dx;
}

/**
* Get the the set of {@link GoDependency} required by this dependency.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import software.amazon.smithy.build.FileManifest;
import software.amazon.smithy.codegen.core.CodegenException;
Expand All @@ -32,12 +32,13 @@
*/
final class GoModGenerator {

private GoModGenerator() {}
private GoModGenerator() {
}

static void writeGoMod(
GoSettings settings,
FileManifest manifest,
Map<String, Map<String, SymbolDependency>> dependencies
List<SymbolDependency> dependencies
) {
Path goModFile = manifest.getBaseDir().resolve("go.mod");

Expand All @@ -51,6 +52,8 @@ static void writeGoMod(
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);
Expand All @@ -61,13 +64,11 @@ static void writeGoMod(
}
}

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));
private static Map<String, String> getExternalDependencies(List<SymbolDependency> dependencies) {
return SymbolDependency.gatherDependencies(dependencies.stream()
.filter(s -> !s.getDependencyType().equals(GoDependency.Type.STANDARD_LIBRARY.toString())),
GoDependency::mergeByMinimumVersionSelection)
.entrySet().stream().flatMap(e -> e.getValue().entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getVersion()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright 2021 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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import software.amazon.smithy.build.FileManifest;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.codegen.core.SymbolDependency;
import software.amazon.smithy.model.node.ArrayNode;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.node.StringNode;

/**
* Generates a manifest description of the generated code, minimum go version, and minimum dependencies required.
*/
public final class ManifestWriter {
private static final String GENERATED_JSON = "generated.json";

private ManifestWriter() {
}

/**
* Write the manifest description of the generated code.
*
* @param settings the go settings
* @param fileManifest the file manifest
* @param dependencies the list of symbol dependencies
*/
public static void writeManifest(
GoSettings settings,
FileManifest fileManifest,
List<SymbolDependency> dependencies
) {
Path manifestFile = fileManifest.getBaseDir().resolve(GENERATED_JSON);

if (Files.exists(manifestFile)) {
try {
Files.delete(manifestFile);
} catch (IOException e) {
throw new CodegenException("Failed to delete existing " + GENERATED_JSON + " file", e);
}
}
fileManifest.addFile(manifestFile);

Node generatedJson = buildManifestFile(settings, fileManifest, dependencies);
fileManifest.writeFile(manifestFile.toString(), Node.prettyPrintJson(generatedJson));
}

private static Node buildManifestFile(
GoSettings settings,
FileManifest fileManifest,
List<SymbolDependency> dependencies
) {
List<SymbolDependency> nonStdLib = new ArrayList<>();
Optional<SymbolDependency> minStandard = Optional.empty();

for (SymbolDependency dependency : dependencies) {
if (!dependency.getDependencyType().equals(GoDependency.Type.STANDARD_LIBRARY.toString())) {
nonStdLib.add(dependency);
} else {
if (minStandard.isPresent()) {
if (minStandard.get().getVersion().compareTo(dependency.getVersion()) < 0) {
minStandard = Optional.of(dependency);
}
} else {
minStandard = Optional.of(dependency);
}
}
}

Map<StringNode, Node> manifestNodes = new HashMap<>();

Map<String, String> minimumDependencies = gatherMinimumDependencies(nonStdLib.stream());

Map<StringNode, Node> dependencyNodes = new HashMap<>();
for (Map.Entry<String, String> entry : minimumDependencies.entrySet()) {
dependencyNodes.put(StringNode.from(entry.getKey()),
StringNode.from(entry.getValue()));
}

Collection<String> generatedFiles = new ArrayList<>();
Path baseDir = fileManifest.getBaseDir();
for (Path filePath : fileManifest.getFiles()) {
generatedFiles.add(baseDir.relativize(filePath).toString());
}
generatedFiles = generatedFiles.stream().sorted().collect(Collectors.toList());

manifestNodes.put(StringNode.from("module"), StringNode.from(settings.getModuleName()));
minStandard.ifPresent(symbolDependency ->
manifestNodes.put(StringNode.from("go"), StringNode.from(symbolDependency.getVersion())));
manifestNodes.put(StringNode.from("dependencies"), ObjectNode.objectNode(dependencyNodes));
manifestNodes.put(StringNode.from("files"), ArrayNode.fromStrings(generatedFiles));

return ObjectNode.objectNode(manifestNodes).withDeepSortedKeys();
}

private static Map<String, String> gatherMinimumDependencies(
Stream<SymbolDependency> symbolStream
) {
return SymbolDependency.gatherDependencies(symbolStream, GoDependency::mergeByMinimumVersionSelection)
.entrySet().stream()
.flatMap(entry -> entry.getValue().entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey, entry -> entry.getValue().getVersion(), (a, b) -> b, TreeMap::new));
}

}
Loading

0 comments on commit c66217f

Please sign in to comment.