From 8c1e53f1eb55a4266f5fce9905c3892f4d616702 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Tue, 6 Apr 2021 15:31:56 -0700 Subject: [PATCH 1/5] Update checkstyle.xml to allow any 4 year date. --- codegen/config/checkstyle/checkstyle.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen/config/checkstyle/checkstyle.xml b/codegen/config/checkstyle/checkstyle.xml index 0d2e6c299..7f2abdde2 100644 --- a/codegen/config/checkstyle/checkstyle.xml +++ b/codegen/config/checkstyle/checkstyle.xml @@ -30,9 +30,9 @@ - + + value="/*\n * Copyright \d\d\d\d Amazon.com, Inc. or its affiliates. All Rights Reserved.\n"/> From d71faa0f9f00597c2a7a0d2dc239b4b47d76b7ef Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Tue, 6 Apr 2021 15:32:30 -0700 Subject: [PATCH 2/5] Add support for creating a manifest description of generated artifacts. --- .../smithy/go/codegen/CodegenVisitor.java | 5 +- .../smithy/go/codegen/GoDependency.java | 29 ++ .../smithy/go/codegen/GoModGenerator.java | 23 +- .../smithy/go/codegen/ManifestWriter.java | 132 ++++++ .../smithy/go/codegen/SemanticVersion.java | 382 ++++++++++++++++++ .../go/codegen/SemanticVersionTest.java | 134 ++++++ 6 files changed, 693 insertions(+), 12 deletions(-) create mode 100644 codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java create mode 100644 codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SemanticVersion.java create mode 100644 codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/SemanticVersionTest.java diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java index 556c93ebc..4d413c2fb 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java @@ -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()); diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoDependency.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoDependency.java index ad8a17100..882c1986d 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoDependency.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoDependency.java @@ -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; @@ -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. * diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoModGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoModGenerator.java index 79f2a9852..1ec793fcb 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoModGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoModGenerator.java @@ -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; @@ -32,12 +32,13 @@ */ final class GoModGenerator { - private GoModGenerator() {} + private GoModGenerator() { + } static void writeGoMod( GoSettings settings, FileManifest manifest, - Map> dependencies + List dependencies ) { Path goModFile = manifest.getBaseDir().resolve("go.mod"); @@ -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 externalDependencies = getExternalDependencies(dependencies); @@ -61,13 +64,11 @@ static void writeGoMod( } } - private static Map getExternalDependencies( - Map> 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 getExternalDependencies(List 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())); } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java new file mode 100644 index 000000000..932b74aa0 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java @@ -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 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)+"\n"); + } + + private static Node buildManifestFile( + GoSettings settings, + FileManifest fileManifest, + List dependencies + ) { + List nonStdLib = new ArrayList<>(); + Optional 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 manifestNodes = new HashMap<>(); + + Map minimumDependencies = gatherMinimumDependencies(nonStdLib.stream()); + + Map dependencyNodes = new HashMap<>(); + for (Map.Entry entry : minimumDependencies.entrySet()) { + dependencyNodes.put(StringNode.from(entry.getKey()), + StringNode.from(entry.getValue())); + } + + Collection 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 gatherMinimumDependencies( + Stream 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)); + } + +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SemanticVersion.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SemanticVersion.java new file mode 100644 index 000000000..bc55c1604 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SemanticVersion.java @@ -0,0 +1,382 @@ +/* + * 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.util.Comparator; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import software.amazon.smithy.utils.SmithyBuilder; + +/** + * A semantic version parser that allows for prefixes to be compatible with Go version tags. + */ +public final class SemanticVersion { + // Regular Expression from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + private static final Pattern SEMVER_PATTERN = Pattern.compile("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)" + + "(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + + "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); + + private final String prefix; + private final int major; + private final int minor; + private final int patch; + private final String preRelease; + private final String build; + + private SemanticVersion(Builder builder) { + prefix = builder.prefix; + major = builder.major; + minor = builder.minor; + patch = builder.patch; + preRelease = builder.preRelease; + build = builder.build; + } + + /** + * The semantic version prefix present before the major version. + * + * @return the optional prefix + */ + public Optional getPrefix() { + return Optional.ofNullable(prefix); + } + + /** + * The major version number. + * + * @return the major version + */ + public int getMajor() { + return major; + } + + /** + * The minor version number. + * + * @return the minor version + */ + public int getMinor() { + return minor; + } + + /** + * The patch version number. + * + * @return the patch version + */ + public int getPatch() { + return patch; + } + + public Optional getPreRelease() { + return Optional.ofNullable(preRelease); + } + + public Optional getBuild() { + return Optional.ofNullable(build); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + + if (getPrefix().isPresent()) { + builder.append(getPrefix().get()); + } + + builder.append(getMajor()); + builder.append('.'); + builder.append(getMinor()); + builder.append('.'); + builder.append(getPatch()); + if (getPreRelease().isPresent()) { + builder.append('-'); + builder.append(getPreRelease().get()); + } + if (getBuild().isPresent()) { + builder.append('+'); + builder.append(getBuild().get()); + } + + return builder.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SemanticVersion that = (SemanticVersion) o; + return getMajor() == that.getMajor() + && getMinor() == that.getMinor() + && getPatch() == that.getPatch() + && getPrefix().equals(that.getPrefix()) + && getPreRelease().equals(that.getPreRelease()) + && getBuild().equals(that.getBuild()); + } + + @Override + public int hashCode() { + return Objects.hash(getPrefix(), getMajor(), getMinor(), getPatch(), getPreRelease(), getBuild()); + } + + /** + * Parse a semantic version string into a {@link SemanticVersion}. + * + * @param version the semantic version string + * @return the SemanticVersion representing the parsed value + */ + public static SemanticVersion parseVersion(String version) { + char[] parseArr = version.toCharArray(); + StringBuilder prefixBuilder = new StringBuilder(); + int position = 0; + while (position < parseArr.length && !Character.isDigit(parseArr[position])) { + prefixBuilder.append(parseArr[position]); + position++; + } + + String prefix = null; + if (prefixBuilder.length() > 0) { + prefix = prefixBuilder.toString(); + } + + Matcher matcher = SEMVER_PATTERN.matcher(version.substring(position)); + + if (!matcher.matches()) { + throw newInvalidSemanticVersion(version); + } + + return builder() + .prefix(prefix) + .major(Integer.parseInt(matcher.group(1))) + .minor(Integer.parseInt(matcher.group(2))) + .patch(Integer.parseInt(matcher.group(3))) + .preRelease(matcher.group(4)) + .build(matcher.group(5)) + .build(); + } + + private static IllegalArgumentException newInvalidSemanticVersion(String version) { + return new IllegalArgumentException("Invalid semantic version string: " + version); + } + + /** + * Get a {@link SemanticVersion} builder. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Return a builder for this {@link SemanticVersion}. + * + * @return the builder + */ + public Builder toBuilder() { + return builder() + .prefix(this.prefix) + .major(this.major) + .minor(this.minor) + .patch(this.patch) + .preRelease(this.preRelease) + .build(this.build); + } + + /** + * Compare two {@link SemanticVersion}, ignoring prefix strings. To validate that prefix strings match + * see the overloaded function signature. + * + * @param o the {@link SemanticVersion} to be compared. + * @return the value {@code 0} if this {@code SemanticVersion} is + * equal to the argument {@code SemanticVersion}; a value less than + * {@code 0} if this {@code SemanticVersion} is less + * than the argument {@code SemanticVersion}; and a value greater + * than {@code 0} if this {@code SemanticVersion} is + * greater than the argument {@code SemanticVersion}. + */ + public int compareTo(SemanticVersion o) { + return compareTo(o, (o1, o2) -> 0); + } + + /** + * Compare two {@link SemanticVersion}, using the prefixComparator for comparing the prefix strings. + * + * @param o the {@link SemanticVersion} to be compared. + * @param prefixComparator the comparator for comparing prefixes + * @return the value {@code 0} if this {@code SemanticVersion} is + * equal to the argument {@code SemanticVersion}; a value less than + * {@code 0} if this {@code SemanticVersion} is less + * than the argument {@code SemanticVersion}; and a value greater + * than {@code 0} if this {@code SemanticVersion} is + * greater than the argument {@code SemanticVersion}. + */ + public int compareTo( + SemanticVersion o, + Comparator> prefixComparator + ) { + int cmp = prefixComparator.compare(getPrefix(), o.getPrefix()); + if (cmp != 0) { + return cmp; + } + + cmp = Integer.compare(getMajor(), o.getMajor()); + if (cmp != 0) { + return cmp; + } + + cmp = Integer.compare(getMinor(), o.getMinor()); + if (cmp != 0) { + return cmp; + } + + cmp = Integer.compare(getPatch(), o.getPatch()); + if (cmp != 0) { + return cmp; + } + + if (!getPreRelease().isPresent() && !o.getPreRelease().isPresent()) { + return 0; + } + + if (!getPreRelease().isPresent()) { + return 1; + } + + if (!o.getPreRelease().isPresent()) { + return -1; + } + + return comparePreRelease(getPreRelease().get(), o.getPreRelease().get()); + } + + private static int comparePreRelease(String x, String y) { + String[] xIdentifiers = x.split("\\."); + String[] yIdentifiers = y.split("\\."); + + int cmp = 0; + int xPos = 0; + int yPos = 0; + + while (xPos < xIdentifiers.length && yPos < yIdentifiers.length && cmp == 0) { + Optional xInt = parsePositiveInteger(xIdentifiers[xPos]); + Optional yInt = parsePositiveInteger(yIdentifiers[yPos]); + + if (xInt.isPresent() && yInt.isPresent()) { + cmp = Integer.compare(xInt.get(), yInt.get()); + continue; + } + + if (xInt.isPresent()) { + cmp = -1; + continue; + } + + if (yInt.isPresent()) { + cmp = 1; + continue; + } + + cmp = xIdentifiers[xPos].compareTo(yIdentifiers[yPos]); + + xPos++; + yPos++; + } + + if (cmp != 0) { + return cmp; + } + + int xRemaining = xIdentifiers.length - 1 - xPos; + int yRemaining = yIdentifiers.length - 1 - yPos; + + if (xRemaining == yRemaining) { + return 0; + } + + return (xRemaining < yRemaining) ? -1 : 1; + } + + private static Optional parsePositiveInteger(String value) { + try { + int i = Integer.parseInt(value); + + if (i < 0) { + return Optional.empty(); + } + + return Optional.of(i); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + + /** + * Builder for {@link SemanticVersion}. + */ + public static final class Builder implements SmithyBuilder { + private String prefix; + private int major; + private int minor; + private int patch; + private String preRelease; + private String build; + + private Builder() { + } + + public Builder prefix(String prefix) { + this.prefix = prefix; + return this; + } + + public Builder major(int major) { + this.major = major; + return this; + } + + public Builder minor(int minor) { + this.minor = minor; + return this; + } + + public Builder patch(int patch) { + this.patch = patch; + return this; + } + + public Builder preRelease(String preRelease) { + this.preRelease = preRelease; + return this; + } + + public Builder build(String build) { + this.build = build; + return this; + } + + @Override + public SemanticVersion build() { + return new SemanticVersion(this); + } + } +} diff --git a/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/SemanticVersionTest.java b/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/SemanticVersionTest.java new file mode 100644 index 000000000..e0a1aed63 --- /dev/null +++ b/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/SemanticVersionTest.java @@ -0,0 +1,134 @@ +/* + * 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 static org.hamcrest.MatcherAssert.assertThat; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +public class SemanticVersionTest { + + @Test + public void testSemanticVersion() { + SemanticVersion version = SemanticVersion.parseVersion("1.2.3"); + assertThat(version.toString(), Matchers.equalTo("1.2.3")); + } + + @Test + public void testSemanticVersionWithPrefix() { + SemanticVersion version = SemanticVersion.parseVersion("v1.2.3"); + assertThat(version.toString(), Matchers.equalTo("v1.2.3")); + } + + @Test + public void testSemanticVersionWithPreRelease() { + SemanticVersion version = SemanticVersion.parseVersion("1.2.3-alpha"); + assertThat(version.toString(), Matchers.equalTo("1.2.3-alpha")); + } + + @Test + public void testSemanticVersionWithBuild() { + SemanticVersion version = SemanticVersion.parseVersion("1.2.3+1234"); + assertThat(version.toString(), Matchers.equalTo("1.2.3+1234")); + } + + @Test + public void testSemanticVersionWithPreReleaseBuild() { + SemanticVersion version = SemanticVersion.parseVersion("1.2.3-alpha+1234"); + assertThat(version.toString(), Matchers.equalTo("1.2.3-alpha+1234")); + } + + @Test + public void testSemanticVersionWithPrefixPreReleaseBuild() { + SemanticVersion version = SemanticVersion.parseVersion("v1.2.3-alpha+1234"); + assertThat(version.toString(), Matchers.equalTo("v1.2.3-alpha+1234")); + } + + @Test + public void testCompareTo() { + assertThat(SemanticVersion.parseVersion("v1.0.0").compareTo( + SemanticVersion.parseVersion("v2.0.0")), + Matchers.lessThan(0)); + + assertThat(SemanticVersion.parseVersion("v2.0.0").compareTo( + SemanticVersion.parseVersion("v2.1.0")), + Matchers.lessThan(0)); + + assertThat(SemanticVersion.parseVersion("v2.1.0").compareTo( + SemanticVersion.parseVersion("v2.1.1")), + Matchers.lessThan(0)); + + assertThat(SemanticVersion.parseVersion("v1.2.3").compareTo( + SemanticVersion.parseVersion("v1.2.3")), + Matchers.equalTo(0)); + + // Build metadata is ignored + assertThat(SemanticVersion.parseVersion("v1.2.3-alpha+102030").compareTo( + SemanticVersion.parseVersion("v1.2.3-alpha+405060")), + Matchers.equalTo(0)); + + // 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0 + assertThat(SemanticVersion.parseVersion("1.0.0-alpha").compareTo( + SemanticVersion.parseVersion("1.0.0-alpha.1")), Matchers.lessThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-alpha.1").compareTo( + SemanticVersion.parseVersion("1.0.0-alpha.beta")), Matchers.lessThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-alpha.beta").compareTo( + SemanticVersion.parseVersion("1.0.0-beta")), Matchers.lessThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-beta").compareTo( + SemanticVersion.parseVersion("1.0.0-beta.2")), Matchers.lessThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-beta.2").compareTo( + SemanticVersion.parseVersion("1.0.0-beta.11")), Matchers.lessThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-beta.11").compareTo( + SemanticVersion.parseVersion("1.0.0-rc.1")), Matchers.lessThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-rc.1").compareTo( + SemanticVersion.parseVersion("1.0.0")), Matchers.lessThan(0)); + + // Reversed direction + assertThat(SemanticVersion.parseVersion("1.0.0-alpha.1").compareTo( + SemanticVersion.parseVersion("1.0.0-alpha")), Matchers.greaterThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-beta").compareTo( + SemanticVersion.parseVersion("1.0.0-alpha.alpha.1")), Matchers.greaterThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-beta").compareTo( + SemanticVersion.parseVersion("1.0.0-alpha.beta")), Matchers.greaterThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-beta.2").compareTo( + SemanticVersion.parseVersion("1.0.0-beta")), Matchers.greaterThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-beta.11").compareTo( + SemanticVersion.parseVersion("1.0.0-beta.2")), Matchers.greaterThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0-rc.1").compareTo( + SemanticVersion.parseVersion("1.0.0-beta.11")), Matchers.greaterThan(0)); + + assertThat(SemanticVersion.parseVersion("1.0.0").compareTo( + SemanticVersion.parseVersion("1.0.0-rc.1")), Matchers.greaterThan(0)); + } + + @Test + public void testCompareToWithGoPseudoVersions() { + assertThat(SemanticVersion.parseVersion("v1.2.3-20200518203908-8018eb2c26ba").compareTo( + SemanticVersion.parseVersion("v1.2.3-20191204190536-9bdfabe68543")), Matchers.greaterThan(0)); + } +} From e11111153dc0443eb8d9c4587548537f8287f2f1 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Tue, 13 Apr 2021 15:57:24 -0700 Subject: [PATCH 3/5] Remove Go Module Generation and Formatting --- codegen/smithy-go-codegen-test/build.gradle.kts | 8 -------- .../software/amazon/smithy/go/codegen/CodegenVisitor.java | 6 ------ .../software/amazon/smithy/go/codegen/ManifestWriter.java | 2 +- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/codegen/smithy-go-codegen-test/build.gradle.kts b/codegen/smithy-go-codegen-test/build.gradle.kts index 02bbb06a4..28ba7b6c1 100644 --- a/codegen/smithy-go-codegen-test/build.gradle.kts +++ b/codegen/smithy-go-codegen-test/build.gradle.kts @@ -31,11 +31,3 @@ dependencies { implementation("software.amazon.smithy:smithy-protocol-test-traits:[1.2.0,2.0.0[") implementation(project(":smithy-go-codegen")) } - -// ensure built artifacts are put into the SDK's folders -tasks.create("verifyGoCodegen") { - dependsOn ("build") - workingDir("$buildDir/smithyprojections/smithy-go-codegen-test/source/go-codegen") - commandLine ("go", "test", "-mod", "mod", "-run", "NONE", "./...") -} -tasks["build"].finalizedBy(tasks["verifyGoCodegen"]) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java index 4d413c2fb..7740a0323 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java @@ -218,14 +218,8 @@ void execute() { List dependencies = writers.getDependencies(); writers.flushWriters(); - LOGGER.fine("Generating go.mod file"); - 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()); } @Override diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java index 932b74aa0..2aa7802f9 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java @@ -68,7 +68,7 @@ public static void writeManifest( fileManifest.addFile(manifestFile); Node generatedJson = buildManifestFile(settings, fileManifest, dependencies); - fileManifest.writeFile(manifestFile.toString(), Node.prettyPrintJson(generatedJson)+"\n"); + fileManifest.writeFile(manifestFile.toString(), Node.prettyPrintJson(generatedJson) + "\n"); } private static Node buildManifestFile( From 15d97b84c39f44a1523f8066f86dab2f2f7327ca Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Fri, 23 Apr 2021 12:48:56 -0700 Subject: [PATCH 4/5] Added Unstable information to manifest --- .../smithy/go/codegen/CodegenVisitor.java | 2 +- .../smithy/go/codegen/GoModGenerator.java | 74 ------------------- .../smithy/go/codegen/ManifestWriter.java | 11 ++- 3 files changed, 11 insertions(+), 76 deletions(-) delete mode 100644 codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoModGenerator.java diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java index 7740a0323..de82a55bf 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java @@ -219,7 +219,7 @@ void execute() { writers.flushWriters(); LOGGER.fine("Generating build manifest file"); - ManifestWriter.writeManifest(settings, fileManifest, dependencies); + ManifestWriter.writeManifest(settings, model, fileManifest, dependencies); } @Override diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoModGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoModGenerator.java deleted file mode 100644 index 1ec793fcb..000000000 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoModGenerator.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 java.util.List; -import java.util.Map; -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. - * - *

See here for more information on the format: https://github.com/golang/go/wiki/Modules#gomod - */ -final class GoModGenerator { - - private GoModGenerator() { - } - - static void writeGoMod( - GoSettings settings, - FileManifest manifest, - List dependencies - ) { - 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); - } - } - manifest.addFile(goModFile); - - CodegenUtils.runCommand("go mod init " + settings.getModuleName(), manifest.getBaseDir()); - - Map externalDependencies = getExternalDependencies(dependencies); - for (Map.Entry dependency : externalDependencies.entrySet()) { - CodegenUtils.runCommand( - String.format("go mod edit -require=%s@%s", dependency.getKey(), dependency.getValue()), - manifest.getBaseDir()); - } - } - - private static Map getExternalDependencies(List 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())); - } -} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java index 2aa7802f9..6a9c33256 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ManifestWriter.java @@ -30,10 +30,13 @@ 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.Model; import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.BooleanNode; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.node.StringNode; +import software.amazon.smithy.model.traits.UnstableTrait; /** * Generates a manifest description of the generated code, minimum go version, and minimum dependencies required. @@ -48,11 +51,13 @@ private ManifestWriter() { * Write the manifest description of the generated code. * * @param settings the go settings + * @param model the smithy model * @param fileManifest the file manifest * @param dependencies the list of symbol dependencies */ public static void writeManifest( GoSettings settings, + Model model, FileManifest fileManifest, List dependencies ) { @@ -67,15 +72,17 @@ public static void writeManifest( } fileManifest.addFile(manifestFile); - Node generatedJson = buildManifestFile(settings, fileManifest, dependencies); + Node generatedJson = buildManifestFile(settings, model, fileManifest, dependencies); fileManifest.writeFile(manifestFile.toString(), Node.prettyPrintJson(generatedJson) + "\n"); } private static Node buildManifestFile( GoSettings settings, + Model model, FileManifest fileManifest, List dependencies ) { + List nonStdLib = new ArrayList<>(); Optional minStandard = Optional.empty(); @@ -115,6 +122,8 @@ private static Node buildManifestFile( 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)); + manifestNodes.put(StringNode.from("unstable"), + BooleanNode.from(settings.getService(model).getTrait(UnstableTrait.class).isPresent())); return ObjectNode.objectNode(manifestNodes).withDeepSortedKeys(); } From 44b6a8a5b55c8d9beca5e1cca36a286a3b94de11 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Tue, 4 May 2021 17:56:39 -0700 Subject: [PATCH 5/5] Remove moduleVersion configuration, correct package name declaration to support vN module version paths. --- .../amazon/smithy/go/codegen/GoSettings.java | 24 +------------------ .../amazon/smithy/go/codegen/GoWriter.java | 16 ++++++++++++- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoSettings.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoSettings.java index 4456ca600..819569047 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoSettings.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoSettings.java @@ -34,11 +34,9 @@ public final class GoSettings { 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 ShapeId service; private String moduleName; - private String moduleVersion; private String moduleDescription = ""; private ShapeId protocol; @@ -50,11 +48,10 @@ public final class GoSettings { */ public static GoSettings from(ObjectNode config) { GoSettings settings = new GoSettings(); - config.warnIfAdditionalProperties(Arrays.asList(SERVICE, MODULE_NAME, MODULE_DESCRIPTION, MODULE_VERSION)); + config.warnIfAdditionalProperties(Arrays.asList(SERVICE, MODULE_NAME, MODULE_DESCRIPTION)); settings.setService(config.expectStringMember(SERVICE).expectShapeId()); settings.setModuleName(config.expectStringMember(MODULE_NAME).getValue()); - settings.setModuleVersion(config.expectStringMember(MODULE_VERSION).getValue()); settings.setModuleDescription(config.getStringMemberOrDefault( MODULE_DESCRIPTION, settings.getModuleName() + " client")); @@ -115,25 +112,6 @@ public void setModuleName(String moduleName) { this.moduleName = Objects.requireNonNull(moduleName); } - /** - * Gets the required module version for the module that will be generated. - * - * @return The version of the module that will be generated. - * @throws NullPointerException if the module version has not been set. - */ - public String getModuleVersion() { - return Objects.requireNonNull(moduleVersion, MODULE_VERSION + " not set"); - } - - /** - * Sets the required module version for the module that will be generated. - * - * @param moduleVersion The version of the module that will be generated. - */ - public void setModuleVersion(String moduleVersion) { - this.moduleVersion = Objects.requireNonNull(moduleVersion); - } - /** * Gets the optional module description for the module that will be generated. * diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java index 1ebd064a3..2e72df371 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java @@ -356,8 +356,22 @@ public String toString() { String[] packageParts = fullPackageName.split("/"); String header = String.format("// Code generated by smithy-go-codegen DO NOT EDIT.%n%n"); + String packageName = packageParts[packageParts.length - 1]; + if (packageName.startsWith("v") && packageParts.length >= 2) { + String remaining = packageName.substring(1); + try { + int value = Integer.parseInt(remaining); + packageName = packageParts[packageParts.length - 2]; + if (value == 0 || value == 1) { + throw new CodegenException("module paths vN version component must only be N >= 2"); + } + } catch (NumberFormatException ne) { + // Do nothing + } + } + String packageDocs = this.packageDocs.toString(); - String packageStatement = String.format("package %s%n%n", packageParts[packageParts.length - 1]); + String packageStatement = String.format("package %s%n%n", packageName); String importString = imports.toString(); String strippedContents = StringUtils.stripStart(contents, null);