From 7a0878e83f3878ab5de3628a132d235c63f18a7a Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 24 Oct 2024 15:53:16 -0700 Subject: [PATCH] fix: Support Windows properly (#662) *Issue #, if available:* Resolves #317 (AFAICT - the logs from the linked run have expired. But the CI now exercises codegen on Windows, which gives me confidence it should work. We can open new issues if we discover new issues) *Description of changes:* Addresses a handful of portability issues, so that Windows is supported: 1. Remove `check_dafny_version.sh` and extract/check the Dafny version in Java code instead (and updated `DafnyVersion` to handle the `+build` part of the semver 2.0). `--dafny-version` is now an optional parameter, provided just in case you want to override it. 2. Avoid the false error about the `project.properties` path containing a `:` (which it usually does on Windows), despite it not being a templated path. Also renamed an overload to the more specific `evalTemplateResource` to make the distinction clearer. 3. Replace `shell pwd` in the makefile with the more portable `$(CURDIR)`. Added building a target on the SimpleString test model on Windows as a sanity check. I attempted building all models on all platforms, but besides this being expensive, my last attempt saw windows jobs hang for some reason. Would like to revisit that but later, as this PR definitely moves us forward. Big thanks to @MikaelMayer for helping to figure out some of these fixes. :) --- .github/workflows/smithy-polymorph.yml | 12 +- SmithyDafnyMakefile.mk | 7 +- .../StandardLibrary/Makefile | 1 - .../software/amazon/polymorph/CodegenCli.java | 18 ++- .../amazon/polymorph/CodegenEngine.java | 46 +++++-- .../polymorph/smithydafny/DafnyVersion.java | 79 +++++++++--- .../generator/AbstractRustShimGenerator.java | 7 +- .../generator/RustAwsSdkShimGenerator.java | 16 +-- .../generator/RustLibraryShimGenerator.java | 122 ++++++++---------- .../amazon/polymorph/utils/IOUtils.java | 8 +- .../DafnyClientCodegenPluginSettings.java | 18 +-- scripts/check_dafny_version.sh | 17 --- 12 files changed, 191 insertions(+), 160 deletions(-) delete mode 100755 scripts/check_dafny_version.sh diff --git a/.github/workflows/smithy-polymorph.yml b/.github/workflows/smithy-polymorph.yml index a77ca5a0b..d77cadfea 100644 --- a/.github/workflows/smithy-polymorph.yml +++ b/.github/workflows/smithy-polymorph.yml @@ -21,15 +21,10 @@ jobs: with: distribution: "corretto" java-version: "17" - - uses: actions/setup-java@v3 - with: - distribution: "corretto" - java-version: "17" - name: Setup Dafny uses: dafny-lang/setup-dafny-action@v1.7.0 with: - # Matching the hard-coded version for the "2023" edition for now - dafny-version: 4.1.0 + dafny-version: 4.8.1 - name: Install Smithy-Dafny codegen dependencies uses: ./.github/actions/install_smithy_dafny_codegen_dependencies @@ -46,6 +41,11 @@ jobs: arguments: :smithy-dafny-codegen:test build-root-directory: codegen + - name: Build a test model (just to test multiple OS') + shell: bash + working-directory: TestModels/SimpleTypes/SimpleString + run: make polymorph_dafny + - name: not-grep if: matrix.os == 'ubuntu-latest' uses: mattsb42-meta/not-grep@1.0.0 diff --git a/SmithyDafnyMakefile.mk b/SmithyDafnyMakefile.mk index 10eed77b7..a7cbef1a5 100644 --- a/SmithyDafnyMakefile.mk +++ b/SmithyDafnyMakefile.mk @@ -39,7 +39,7 @@ VERIFY_TIMEOUT := 100 # This evaluates to the path of the current working directory. # i.e. The specific library under consideration. -LIBRARY_ROOT := $(shell pwd) +LIBRARY_ROOT := $(CURDIR) # Smithy Dafny code gen needs to know # where the smithy model is. # This is generally in the same directory as the library. @@ -56,8 +56,6 @@ SMITHY_MODEL_ROOT := $(LIBRARY_ROOT)/Model CODEGEN_CLI_ROOT := $(SMITHY_DAFNY_ROOT)/codegen/smithy-dafny-codegen-cli GRADLEW := $(SMITHY_DAFNY_ROOT)/codegen/gradlew -DAFNY_VERSION := $(shell $(SMITHY_DAFNY_ROOT)/scripts/check_dafny_version.sh) - include $(SMITHY_DAFNY_ROOT)/SmithyDafnySedMakefile.mk # This flag enables pre-processing on extern module names. @@ -276,7 +274,6 @@ _polymorph: mvn_local_deploy_polymorph_dependencies _polymorph: cd $(CODEGEN_CLI_ROOT); \ ./../gradlew run --args="\ - --dafny-version $(DAFNY_VERSION) \ --library-root $(LIBRARY_ROOT) \ --patch-files-dir $(if $(DIR_STRUCTURE_V2),$(LIBRARY_ROOT)/codegen-patches/$(SERVICE),$(LIBRARY_ROOT)/codegen-patches) \ --properties-file $(LIBRARY_ROOT)/project.properties \ @@ -303,7 +300,6 @@ _polymorph_wrapped: @: $(if ${CODEGEN_CLI_ROOT},,$(error You must pass the path CODEGEN_CLI_ROOT: CODEGEN_CLI_ROOT=/path/to/smithy-dafny/codegen/smithy-dafny-codegen-cli)); cd $(CODEGEN_CLI_ROOT); \ ./../gradlew run --args="\ - --dafny-version $(DAFNY_VERSION) \ --library-root $(LIBRARY_ROOT) \ --properties-file $(LIBRARY_ROOT)/project.properties \ $(INPUT_DAFNY) \ @@ -604,7 +600,6 @@ _patch_after_transpile_rust: cd $(CODEGEN_CLI_ROOT); \ ./../gradlew run --args="\ patch-after-transpile \ - --dafny-version $(DAFNY_VERSION) \ --library-root $(LIBRARY_ROOT) \ $(OUTPUT_RUST) \ --model $(if $(DIR_STRUCTURE_V2), $(LIBRARY_ROOT)/dafny/$(SERVICE)/Model, $(SMITHY_MODEL_ROOT)) \ diff --git a/TestModels/dafny-dependencies/StandardLibrary/Makefile b/TestModels/dafny-dependencies/StandardLibrary/Makefile index 5157bbad3..5d989fb45 100644 --- a/TestModels/dafny-dependencies/StandardLibrary/Makefile +++ b/TestModels/dafny-dependencies/StandardLibrary/Makefile @@ -26,7 +26,6 @@ endif polymorph_dafny : cd $(CODEGEN_CLI_ROOT); \ $(GRADLEW) run --args="\ - --dafny-version $(DAFNY_VERSION) \ --library-root $(LIBRARY_ROOT) \ --properties-file $(PROJECT_ROOT)/$(STD_LIBRARY)/project.properties \ --model $(PROJECT_ROOT)/$(STD_LIBRARY)/Model \ diff --git a/codegen/smithy-dafny-codegen-cli/src/main/java/software/amazon/polymorph/CodegenCli.java b/codegen/smithy-dafny-codegen-cli/src/main/java/software/amazon/polymorph/CodegenCli.java index 1499c542c..9087ff011 100644 --- a/codegen/smithy-dafny-codegen-cli/src/main/java/software/amazon/polymorph/CodegenCli.java +++ b/codegen/smithy-dafny-codegen-cli/src/main/java/software/amazon/polymorph/CodegenCli.java @@ -588,17 +588,15 @@ static Optional parse(String[] args) throws ParseException { } } - DafnyVersion dafnyVersion; + DafnyVersion dafnyVersion = null; String versionStr = commandLine.getOptionValue("dafny-version"); - if (versionStr == null) { - LOGGER.error("--dafny-version option is required"); - System.exit(-1); - } - try { - dafnyVersion = DafnyVersion.parse(versionStr.trim()); - } catch (IllegalArgumentException ex) { - LOGGER.error("Could not parse --dafny-version: {}", versionStr); - throw ex; + if (versionStr != null) { + try { + dafnyVersion = DafnyVersion.parse(versionStr.trim()); + } catch (IllegalArgumentException ex) { + LOGGER.error("Could not parse --dafny-version: {}", versionStr); + throw ex; + } } Optional propertiesFile = Optional diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/CodegenEngine.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/CodegenEngine.java index 08b3a2ac8..4b9d14208 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/CodegenEngine.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/CodegenEngine.java @@ -5,9 +5,7 @@ import static software.amazon.smithy.utils.CaseUtils.toSnakeCase; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.MoreCollectors; import com.google.common.collect.Streams; import com.squareup.javapoet.ClassName; import java.io.IOException; @@ -52,9 +50,7 @@ import software.amazon.polymorph.smithypython.awssdk.extensions.DafnyPythonAwsSdkClientCodegenPlugin; import software.amazon.polymorph.smithypython.localservice.extensions.DafnyPythonLocalServiceClientCodegenPlugin; import software.amazon.polymorph.smithypython.wrappedlocalservice.extensions.DafnyPythonWrappedLocalServiceClientCodegenPlugin; -import software.amazon.polymorph.smithyrust.generator.AbstractRustShimGenerator; import software.amazon.polymorph.smithyrust.generator.MergedServicesGenerator; -import software.amazon.polymorph.smithyrust.generator.RustAwsSdkShimGenerator; import software.amazon.polymorph.smithyrust.generator.RustLibraryShimGenerator; import software.amazon.polymorph.traits.LocalServiceTrait; import software.amazon.polymorph.utils.DafnyNameResolverHelpers; @@ -76,6 +72,10 @@ public class CodegenEngine { CodegenEngine.class ); + private static final DafnyVersion MIN_DAFNY_VERSION = DafnyVersion.parse( + "4.5" + ); + // Used to distinguish different conventions between the CLI // and the Smithy build plugin, such as where .NET project files live. private final boolean fromSmithyBuildPlugin; @@ -220,7 +220,13 @@ private void generateProjectPropertiesFile(final Path outputPath) "dafnyVersion", dafnyVersionString ); - writeTemplatedFile("project.properties", outputPath.toString(), parameters); + // Don't use writeTemplatedFile since outputPath is an absolute path + final String propertiesFileContent = IOUtils.evalTemplateResource( + getClass(), + "project.properties", + parameters + ); + IOUtils.writeToFile(propertiesFileContent, outputPath.toFile()); } private void generateDafny(final Path outputDir) { @@ -932,7 +938,7 @@ private record CommandResult(int exitCode, String output) {} /** * Runs the given command and throws an exception if the exit code is nonzero. */ - private String runCommandOrThrow(Path workingDir, String... args) { + private static String runCommandOrThrow(Path workingDir, String... args) { final CommandResult result = runCommand(workingDir, args); if (result.exitCode != 0) { throw new RuntimeException( @@ -945,7 +951,7 @@ private String runCommandOrThrow(Path workingDir, String... args) { /** * Runs the given command. */ - private CommandResult runCommand(Path workingDir, String... args) { + private static CommandResult runCommand(Path workingDir, String... args) { final List argsList = List.of(args); final StringBuilder output = new StringBuilder(); final int exitCode = IoUtils.runCommand( @@ -1003,7 +1009,7 @@ public static class Builder { Collections.emptyMap(); private Map targetLangTestOutputDirs = Collections.emptyMap(); - private DafnyVersion dafnyVersion = new DafnyVersion(4, 1, 0); + private DafnyVersion dafnyVersion; private Path propertiesFile; private AwsSdkVersion javaAwsSdkVersion = AwsSdkVersion.V2; private Path includeDafnyFile; @@ -1235,9 +1241,18 @@ public CodegenEngine build() { final Map targetLangTestOutputDirs = ImmutableMap.copyOf(targetLangTestOutputDirsRaw); - final DafnyVersion dafnyVersion = Objects.requireNonNull( - this.dafnyVersion - ); + final DafnyVersion dafnyVersion = Optional + .ofNullable(this.dafnyVersion) + .orElseGet(CodegenEngine::getDafnyVersionFromDafny); + if (dafnyVersion.compareTo(MIN_DAFNY_VERSION) < 0) { + throw new IllegalStateException( + "A minimum Dafny version of " + + MIN_DAFNY_VERSION.unparse() + + " is required, but found " + + dafnyVersion.unparse() + ); + } + final Optional propertiesFile = Optional .ofNullable(this.propertiesFile) .map(path -> path.toAbsolutePath().normalize()); @@ -1301,6 +1316,15 @@ public CodegenEngine build() { } } + private static DafnyVersion getDafnyVersionFromDafny() { + String versionString = runCommandOrThrow( + Path.of("."), + "dafny", + "--version" + ); + return DafnyVersion.parse(versionString.trim()); + } + public enum TargetLanguage { DAFNY, JAVA, diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithydafny/DafnyVersion.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithydafny/DafnyVersion.java index a6be0bb12..98c3b3c87 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithydafny/DafnyVersion.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithydafny/DafnyVersion.java @@ -20,7 +20,9 @@ public class DafnyVersion implements Comparable { private final int minor; private final int patch; // Will be non-null only if there was a pre-release suffix - private final String suffix; + private final String prerelease; + // Will be non-null only if there was a build suffix + private final String build; // Anything with a pre-release suffix should be considered less // than a matching version without one. @@ -28,33 +30,49 @@ public class DafnyVersion implements Comparable { Comparator.nullsLast(Comparator.naturalOrder()); public static DafnyVersion parse(String versionString) { - if (!versionString.matches("[0-9\\.A-Za-z\\-]*")) { + if (!versionString.matches("[0-9.A-Za-z\\-+]*")) { throw new IllegalArgumentException(); } - int firstHyphenIndex = versionString.indexOf("-"); String majorMinorPatch = versionString; String suffix = null; + String build = null; + + int plusIndex = majorMinorPatch.indexOf("+"); + if (plusIndex >= 0) { + build = majorMinorPatch.substring(plusIndex + 1); + majorMinorPatch = versionString.substring(0, plusIndex); + } + + int firstHyphenIndex = majorMinorPatch.indexOf("-"); if (firstHyphenIndex >= 0) { - majorMinorPatch = versionString.substring(0, firstHyphenIndex); - suffix = versionString.substring(firstHyphenIndex + 1); + suffix = majorMinorPatch.substring(firstHyphenIndex + 1); + majorMinorPatch = majorMinorPatch.substring(0, firstHyphenIndex); } String[] splitByDots = majorMinorPatch.split("\\."); switch (splitByDots.length) { case 1: - return new DafnyVersion(Integer.parseInt(splitByDots[0]), 0, 0, suffix); + return new DafnyVersion( + Integer.parseInt(splitByDots[0]), + 0, + 0, + suffix, + build + ); case 2: return new DafnyVersion( Integer.parseInt(splitByDots[0]), Integer.parseInt(splitByDots[1]), 0, - suffix + suffix, + build ); case 3: return new DafnyVersion( Integer.parseInt(splitByDots[0]), Integer.parseInt(splitByDots[1]), Integer.parseInt(splitByDots[2]), - suffix + suffix, + build ); default: throw new IllegalArgumentException(); @@ -65,11 +83,22 @@ public DafnyVersion(int major, int minor, int patch) { this(major, minor, patch, null); } - public DafnyVersion(int major, int minor, int patch, String suffix) { + public DafnyVersion(int major, int minor, int patch, String prerelease) { + this(major, minor, patch, prerelease, null); + } + + public DafnyVersion( + int major, + int minor, + int patch, + String prerelease, + String build + ) { this.major = requireNonNegative(major); this.minor = requireNonNegative(minor); this.patch = requireNonNegative(patch); - this.suffix = suffix; + this.prerelease = prerelease; + this.build = build; } private int requireNonNegative(int value) { @@ -91,8 +120,12 @@ public int getPatch() { return patch; } - public String getSuffix() { - return suffix; + public String getPrerelease() { + return prerelease; + } + + public String getBuild() { + return build; } @Override @@ -108,13 +141,14 @@ public boolean equals(Object o) { major == that.major && minor == that.minor && patch == that.patch && - Objects.equals(suffix, that.suffix) + Objects.equals(prerelease, that.prerelease) && + Objects.equals(build, that.build) ); } @Override public int hashCode() { - return Objects.hash(major, minor, patch, suffix); + return Objects.hash(major, minor, patch, prerelease, build); } @Override @@ -134,7 +168,7 @@ public int compareTo(DafnyVersion other) { return patchComparison; } - return SUFFIX_COMPARATOR.compare(this.suffix, other.suffix); + return SUFFIX_COMPARATOR.compare(this.prerelease, other.prerelease); } public String unparse() { @@ -144,9 +178,13 @@ public String unparse() { builder.append(minor); builder.append('.'); builder.append(patch); - if (suffix != null) { + if (prerelease != null) { builder.append('-'); - builder.append(suffix); + builder.append(prerelease); + } + if (build != null) { + builder.append('+'); + builder.append(build); } return builder.toString(); } @@ -161,8 +199,11 @@ public String toString() { minor + ", patch=" + patch + - ", suffix='" + - suffix + + ", prerelease='" + + prerelease + + '\'' + + ", build='" + + build + '\'' + '}' ); diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/AbstractRustShimGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/AbstractRustShimGenerator.java index cc76e98cb..44f4f49a8 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/AbstractRustShimGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/AbstractRustShimGenerator.java @@ -1,6 +1,7 @@ package software.amazon.polymorph.smithyrust.generator; import static software.amazon.polymorph.utils.IOUtils.evalTemplate; +import static software.amazon.polymorph.utils.IOUtils.evalTemplateResource; import static software.amazon.smithy.rust.codegen.core.util.StringsKt.toPascalCase; import static software.amazon.smithy.rust.codegen.core.util.StringsKt.toSnakeCase; @@ -1080,7 +1081,7 @@ protected RustFile unionConversionModule(final UnionShape unionShape) { perMemberVariables .stream() .map(memberVariables -> - IOUtils.evalTemplate( + evalTemplate( """ $qualifiedRustUnionName:L::$rustUnionMemberName:L(x) => crate::r#$dafnyTypesModuleName:L::$dafnyUnionName:L::$unionMemberName:L { @@ -1097,7 +1098,7 @@ protected RustFile unionConversionModule(final UnionShape unionShape) { perMemberVariables .stream() .map(memberVariables -> - IOUtils.evalTemplate( + evalTemplate( """ crate::r#$dafnyTypesModuleName:L::$dafnyUnionName:L::$unionMemberName:L { $dafnyUnionMemberName:L: x @ _, @@ -1109,7 +1110,7 @@ protected RustFile unionConversionModule(final UnionShape unionShape) { .collect(Collectors.joining("\n")) ); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/conversions/union.rs", variables diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/RustAwsSdkShimGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/RustAwsSdkShimGenerator.java index dd5228160..30c197d59 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/RustAwsSdkShimGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/RustAwsSdkShimGenerator.java @@ -1,6 +1,7 @@ package software.amazon.polymorph.smithyrust.generator; import static software.amazon.polymorph.utils.IOUtils.evalTemplate; +import static software.amazon.polymorph.utils.IOUtils.evalTemplateResource; import static software.amazon.smithy.rust.codegen.core.util.StringsKt.toPascalCase; import static software.amazon.smithy.rust.codegen.core.util.StringsKt.toSnakeCase; @@ -15,7 +16,6 @@ import software.amazon.polymorph.CodegenEngine; import software.amazon.polymorph.traits.DafnyUtf8BytesTrait; import software.amazon.polymorph.utils.BoundOperationShape; -import software.amazon.polymorph.utils.IOUtils; import software.amazon.polymorph.utils.MapUtils; import software.amazon.polymorph.utils.ModelUtils; import software.amazon.polymorph.utils.TokenTree; @@ -263,7 +263,7 @@ private TokenTree operationClientFunction( protected RustFile conversionsClientModule() { TokenTree clientConversionFunctions = TokenTree.of( - evalTemplate( + evalTemplateResource( getClass(), "runtimes/rust/conversions/client_awssdk.rs", serviceVariables() @@ -290,7 +290,7 @@ protected RustFile conversionsErrorModule() { final String toDafnyArms = allErrorShapes() .map(errorShape -> - IOUtils.evalTemplate( + evalTemplate( """ $qualifiedRustServiceErrorType:L::$rustErrorName:L { error } => $rustRootModuleName:L::conversions::error::$snakeCaseErrorName:L::to_dafny(error), @@ -303,7 +303,7 @@ protected RustFile conversionsErrorModule() { final String fromDafnyArms = allErrorShapes() .map(errorShape -> - IOUtils.evalTemplate( + evalTemplate( """ crate::r#$dafnyTypesModuleName:L::Error::$errorName:L { $errorMessageMemberName:L, .. } => $qualifiedRustServiceErrorType:L::$rustErrorName:L { @@ -319,14 +319,14 @@ protected RustFile conversionsErrorModule() { variables.put("fromDafnyArms", fromDafnyArms); final TokenTree sdkContent = TokenTree.of( - IOUtils.evalTemplate( + evalTemplateResource( getClass(), "runtimes/rust/conversions/error_awssdk.rs", variables ) ); final TokenTree toDafnyOpaqueErrorFunctions = TokenTree.of( - IOUtils.evalTemplate( + evalTemplateResource( getClass(), "runtimes/rust/conversions/error_common.rs", variables @@ -685,7 +685,7 @@ private RustFile typesErrorModule() { final Map variables = serviceVariables(); final String directErrorVariants = allErrorShapes() .map(errorShape -> - IOUtils.evalTemplate( + evalTemplate( """ $rustErrorName:L { error: $sdkCrate:L::types::error::$rustErrorName:L, @@ -697,7 +697,7 @@ private RustFile typesErrorModule() { .collect(Collectors.joining("\n\n")); variables.put("modeledErrorVariants", directErrorVariants); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/types/error_awssdk.rs", variables diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/RustLibraryShimGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/RustLibraryShimGenerator.java index ff5e59fd0..d67f4cfe6 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/RustLibraryShimGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithyrust/generator/RustLibraryShimGenerator.java @@ -1,6 +1,7 @@ package software.amazon.polymorph.smithyrust.generator; import static software.amazon.polymorph.utils.IOUtils.evalTemplate; +import static software.amazon.polymorph.utils.IOUtils.evalTemplateResource; import static software.amazon.smithy.rust.codegen.core.util.StringsKt.toPascalCase; import static software.amazon.smithy.rust.codegen.core.util.StringsKt.toSnakeCase; @@ -20,7 +21,6 @@ import software.amazon.polymorph.traits.PositionalTrait; import software.amazon.polymorph.utils.BoundOperationShape; import software.amazon.polymorph.utils.ConstrainTraitUtils; -import software.amazon.polymorph.utils.IOUtils; import software.amazon.polymorph.utils.MapUtils; import software.amazon.polymorph.utils.ModelUtils; import software.amazon.polymorph.utils.Token; @@ -40,7 +40,6 @@ import software.amazon.smithy.model.traits.EnumTrait; import software.amazon.smithy.model.traits.LengthTrait; import software.amazon.smithy.model.traits.RangeTrait; -import software.amazon.smithy.model.traits.ReadonlyTrait; import software.amazon.smithy.model.traits.UnitTypeTrait; /** @@ -237,7 +236,7 @@ private RustFile clientModule() { ) .collect(Collectors.joining("\n\n")) ); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/client.rs", variables @@ -250,7 +249,7 @@ private RustFile clientModule() { protected RustFile conversionsClientModule() { TokenTree clientConversionFunctions = TokenTree.of( - evalTemplate( + evalTemplateResource( getClass(), "runtimes/rust/conversions/client_localservice.rs", serviceVariables() @@ -286,7 +285,7 @@ private RustFile boundOperationClientBuilder( "outputDoc", operationClientOutputDoc(bindingShape, operationShape) ); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/operation/operation_builder.rs", variables @@ -327,7 +326,7 @@ private String operationClientBuilderSettersDoc( opVariables, structureMemberVariables(memberShape) ); - return IOUtils.evalTemplate(template, variables); + return evalTemplate(template, variables); }) .collect(Collectors.joining("\n")); } @@ -357,7 +356,7 @@ private String operationClientOutputDoc( opVariables, structureMemberVariables(memberShape) ); - return IOUtils.evalTemplate(template, variables); + return evalTemplate(template, variables); }) .collect(Collectors.joining("\n")); } @@ -369,7 +368,7 @@ private RustFile typesModule() { final String resourceModules = streamResourcesToGenerateTraitsFor() .filter(o -> o.getId().getNamespace().equals(namespace)) .map(resourceShape -> - IOUtils.evalTemplate( + evalTemplate( """ pub mod $snakeCaseResourceName:L; pub use $snakeCaseResourceName:L::$rustResourceName:L; @@ -383,7 +382,7 @@ private RustFile typesModule() { final String structureModules = streamStructuresToGenerateStructsFor() .filter(o -> o.getId().getNamespace().equals(namespace)) .map(structureShape -> - IOUtils.evalTemplate( + evalTemplate( """ mod _$snakeCaseStructureName:L; pub use $rustTypesModuleName:L::_$snakeCaseStructureName:L::$rustStructureName:L; @@ -398,7 +397,7 @@ private RustFile typesModule() { .streamEnumShapes(model, service.getId().getNamespace()) .filter(o -> o.getId().getNamespace().equals(namespace)) .map(enumShape -> - IOUtils.evalTemplate( + evalTemplate( """ mod _$snakeCaseEnumName:L; pub use $rustTypesModuleName:L::_$snakeCaseEnumName:L::$rustEnumName:L; @@ -415,7 +414,7 @@ private RustFile typesModule() { .filter(this::shouldGenerateEnumForUnion) .filter(o -> o.getId().getNamespace().equals(namespace)) .map(unionShape -> - IOUtils.evalTemplate( + evalTemplate( """ mod _$snakeCaseUnionName:L; pub use $rustTypesModuleName:L::_$snakeCaseUnionName:L::$rustUnionName:L; @@ -426,7 +425,7 @@ private RustFile typesModule() { .collect(Collectors.joining("\n")); variables.put("unionModules", unionModules); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/types.rs", variables @@ -447,7 +446,7 @@ private RustFile typesConfigModule() { standardStructureVariables(configShape), structureModuleVariables(configShape) ); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/types/config.rs", variables @@ -463,7 +462,7 @@ private RustFile typesBuildersModule() { final String content = streamStructuresToGenerateStructsFor() .filter(s -> ModelUtils.isInServiceNamespace(s, service)) .map(structureShape -> - IOUtils.evalTemplate( + evalTemplate( "pub use $rustTypesModuleName:L::_$snakeCaseStructureName:L::$rustStructureName:LBuilder;", MapUtils.merge(variables, structureVariables(structureShape)) ) @@ -479,7 +478,7 @@ private RustFile typesErrorModule() { final Map variables = serviceVariables(); final Stream directErrorVariants = allErrorShapes() .map(errorShape -> - IOUtils.evalTemplate( + evalTemplate( docFromShape(errorShape) + "\n" + """ @@ -494,7 +493,7 @@ private RustFile typesErrorModule() { ModelUtils.streamLocalServiceDependencies(model, service); final Stream dependencyErrorVariants = dependencies.map(dependentService -> { - return IOUtils.evalTemplate( + return evalTemplate( """ $rustErrorName:L { error: $rustDependentRootModuleName:L::types::error::Error, @@ -511,7 +510,7 @@ private RustFile typesErrorModule() { .collect(Collectors.joining("\n\n")); variables.put("modeledErrorVariants", modeledErrorVariants); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/types/error.rs", variables @@ -542,7 +541,7 @@ private RustFile enumTypeModule(final EnumShape enumShape) { final String displayVariants = memberNames .stream() .map(memberName -> - IOUtils.evalTemplate( + evalTemplate( "$rustEnumName:L::$rustEnumMemberName:L => write!(f, \"$enumMemberName:L\"),", MapUtils.merge(variables, enumMemberVariables(memberName)) ) @@ -550,7 +549,7 @@ private RustFile enumTypeModule(final EnumShape enumShape) { .collect(Collectors.joining("\n")); variables.put("displayVariants", displayVariants); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/types/enum.rs", variables @@ -576,7 +575,7 @@ private RustFile unionTypeModule(final UnionShape unionShape) { final String variants = memberShapes .stream() .map(memberName -> - IOUtils.evalTemplate( + evalTemplate( docFromShape(memberName) + "\n" + """ @@ -591,7 +590,7 @@ private RustFile unionTypeModule(final UnionShape unionShape) { final String asImpls = memberShapes .stream() .map(memberName -> - IOUtils.evalTemplate( + evalTemplate( """ /// Tries to convert the enum instance into [`$rustUnionMemberName:L`]($qualifiedRustUnionName:L::$rustUnionMemberName:L), extracting the inner [`$unionMemberType:L`]($unionMemberType:L). /// Returns `Err(&Self)` if it can't be converted. @@ -612,7 +611,7 @@ private RustFile unionTypeModule(final UnionShape unionShape) { final String isImpls = memberShapes .stream() .map(memberName -> - IOUtils.evalTemplate( + evalTemplate( """ /// Returns true if this is a [`$rustUnionMemberName:L`]($qualifiedRustUnionName:L::$rustUnionMemberName:L). pub fn is_$snakeCaseUnionMemberName:L(&self) -> ::std::primitive::bool { @@ -625,7 +624,7 @@ private RustFile unionTypeModule(final UnionShape unionShape) { .collect(Collectors.joining("\n")); variables.put("isImpls", isImpls); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/types/union.rs", variables @@ -656,7 +655,7 @@ private RustFile resourceTypeModule(final ResourceShape resourceShape) { variables, operationVariables(resourceShape, operationShape) ); - return IOUtils.evalTemplate( + return evalTemplateResource( getClass(), "runtimes/rust/types/resource_operation.rs", operationVariables @@ -676,7 +675,7 @@ private RustFile resourceTypeModule(final ResourceShape resourceShape) { .collect(Collectors.joining("\n\n")) ); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/types/resource.rs", variables @@ -715,7 +714,7 @@ private Stream operationModuleDeclarationForBindingShape( .getOperations(bindingShape) .stream() .map(o -> operationVariables(bindingShape, o.operationShape())) - .map(opVariables -> IOUtils.evalTemplate(opTemplate, opVariables)); + .map(opVariables -> evalTemplate(opTemplate, opVariables)); } private Set allOperationImplementationModules() { @@ -813,7 +812,7 @@ private RustFile operationOuterModule( variables.put( "operationSendBody", - IOUtils.evalTemplate( + evalTemplateResource( getClass(), "runtimes/rust/operation/outer_send_body.rs", variables @@ -832,7 +831,7 @@ private RustFile operationOuterModule( ); } - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/operation/outer.rs", variables @@ -862,7 +861,7 @@ class InputValidationGenerator @Override protected String validateRequired(final MemberShape memberShape) { - return IOUtils.evalTemplate( + return evalTemplate( """ if input.$fieldName:L.is_none() { return ::std::result::Result::Err(::aws_smithy_types::error::operation::BuildError::missing_field( @@ -897,7 +896,7 @@ protected String validateRange( max.map(val -> "=" + val).orElse("") ); final var rangeDescription = describeMinMax(min, max); - return IOUtils.evalTemplate( + return evalTemplate( """ if matches!(input.$fieldName:L, Some(x) if %s) { return ::std::result::Result::Err(::aws_smithy_types::error::operation::BuildError::invalid_field( @@ -942,7 +941,7 @@ protected String validateLength( len ); final var rangeDescription = describeMinMax(min, max); - return IOUtils.evalTemplate( + return evalTemplate( """ if matches!(input.$fieldName:L, Some(ref x) if %s) { return ::std::result::Result::Err(::aws_smithy_types::error::operation::BuildError::invalid_field( @@ -993,7 +992,7 @@ private RustFile operationStructureModule( structureVariables(structureShape), structureModuleVariables(structureShape) ); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/operation/structure.rs", variables @@ -1011,7 +1010,7 @@ private RustFile standardStructureModule( standardStructureVariables(structureShape), structureModuleVariables(structureShape) ); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/types/structure.rs", variables @@ -1065,10 +1064,7 @@ private String structureField(final MemberShape memberShape) { """ pub $fieldName:L: ::std::option::Option<$fieldType:L>, """; - return IOUtils.evalTemplate( - template, - structureMemberVariables(memberShape) - ); + return evalTemplate(template, structureMemberVariables(memberShape)); } private String structureGetter(final MemberShape memberShape) { @@ -1081,11 +1077,11 @@ private String structureGetter(final MemberShape memberShape) { &self.$fieldName:L } """; - return IOUtils.evalTemplate(template, variables); + return evalTemplate(template, variables); } private String structureBuilderField(final MemberShape memberShape) { - return IOUtils.evalTemplate( + return evalTemplate( "pub(crate) $fieldName:L: ::std::option::Option<$fieldType:L>,", structureMemberVariables(memberShape) ); @@ -1116,14 +1112,11 @@ private String structureBuilderAccessors(final MemberShape memberShape) { &self.$fieldName:L } """; - return IOUtils.evalTemplate( - template, - structureMemberVariables(memberShape) - ); + return evalTemplate(template, structureMemberVariables(memberShape)); } private String structureBuilderAssignment(final MemberShape memberShape) { - return IOUtils.evalTemplate( + return evalTemplate( "$fieldName:L: self.$fieldName:L,", structureMemberVariables(memberShape) ); @@ -1148,7 +1141,7 @@ private RustFile operationBuildersModule( ); variables.put("accessors", accessors); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/operation/builders.rs", variables @@ -1185,14 +1178,11 @@ private String operationFluentBuilderFieldAccessors( self.inner.get_$fieldName:L() } """; - return IOUtils.evalTemplate( - template, - structureMemberVariables(memberShape) - ); + return evalTemplate(template, structureMemberVariables(memberShape)); } private RustFile errorModule() { - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/error.rs", Map.of() @@ -1204,7 +1194,7 @@ private RustFile errorModule() { } private RustFile sealedUnhandledErrorModule() { - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/error/sealed_unhandled.rs", Map.of() @@ -1220,7 +1210,7 @@ protected RustFile conversionsErrorModule() { final Stream directToDafnyArms = allErrorShapes() .map(errorShape -> - IOUtils.evalTemplate( + evalTemplate( """ $qualifiedRustErrorVariant:L { message } => crate::r#$dafnyTypesModuleName:L::Error::$errorName:L { @@ -1233,7 +1223,7 @@ protected RustFile conversionsErrorModule() { final Stream dependencyToDafnyArms = ModelUtils .streamLocalServiceDependencies(model, service) .map(dependentService -> - IOUtils.evalTemplate( + evalTemplate( """ $qualifiedRustErrorVariant:L { error } => crate::r#$dafnyTypesModuleName:L::Error::$errorName:L { @@ -1253,7 +1243,7 @@ protected RustFile conversionsErrorModule() { final Stream directFromDafnyArms = allErrorShapes() .map(errorShape -> - IOUtils.evalTemplate( + evalTemplate( """ crate::r#$dafnyTypesModuleName:L::Error::$errorName:L { message } => $qualifiedRustErrorVariant:L { @@ -1266,7 +1256,7 @@ protected RustFile conversionsErrorModule() { final Stream dependencyFromDafnyArms = ModelUtils .streamLocalServiceDependencies(model, service) .map(dependentService -> - IOUtils.evalTemplate( + evalTemplate( """ crate::r#$dafnyTypesModuleName:L::Error::$errorName:L { $errorName:L } => $qualifiedRustErrorVariant:L { @@ -1284,12 +1274,12 @@ protected RustFile conversionsErrorModule() { .collect(Collectors.joining("\n")); variables.put("fromDafnyArms", fromDafnyArms); - final String libraryContent = IOUtils.evalTemplate( + final String libraryContent = evalTemplateResource( getClass(), "runtimes/rust/conversions/error_library.rs", variables ); - final String commonContent = IOUtils.evalTemplate( + final String commonContent = evalTemplateResource( getClass(), "runtimes/rust/conversions/error_common.rs", variables @@ -1319,7 +1309,7 @@ private Set configConversionModules() { ); final String snakeCaseConfigName = variables.get("snakeCaseConfigName"); - final String outerContent = IOUtils.evalTemplate( + final String outerContent = evalTemplateResource( getClass(), "runtimes/rust/conversions/config.rs", variables @@ -1332,7 +1322,7 @@ private Set configConversionModules() { TokenTree.of(outerContent) ); - final String innerContent = IOUtils.evalTemplate( + final String innerContent = evalTemplateResource( getClass(), "runtimes/rust/conversions/config/_config.rs", variables @@ -1364,7 +1354,7 @@ private RustFile standardStructureConversionModule( "fluentMemberSetters", fluentMemberSettersForStructure(structureShape).toString() ); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/conversions/standard_structure.rs", variables @@ -1418,7 +1408,7 @@ private RustFile resourceConversionModule(final ResourceShape resourceShape) { .collect(Collectors.joining("\n\n")) ); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/conversions/resource.rs", variables @@ -1482,7 +1472,7 @@ private String resourceOperationWrapperImpl( ); } - return IOUtils.evalTemplate( + return evalTemplateResource( getClass(), "runtimes/rust/conversions/resource_wrapper_operation.rs", variables @@ -1543,7 +1533,7 @@ private String resourceOperationDafnyWrapperImpl( ); } - return IOUtils.evalTemplate( + return evalTemplateResource( getClass(), "runtimes/rust/conversions/resource_dafny_wrapper_operation.rs", variables @@ -1676,7 +1666,7 @@ pub fn from_dafny( } private RustFile wrappedModule() { - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/wrapped.rs", serviceVariables() @@ -1697,7 +1687,7 @@ private RustFile wrappedClientModule() { .map(o -> wrappedClientOperationImpl(service, o.operationShape())) .collect(Collectors.joining("\n\n")) ); - final String content = IOUtils.evalTemplate( + final String content = evalTemplateResource( getClass(), "runtimes/rust/wrapped/client.rs", variables @@ -1788,7 +1778,7 @@ private String wrappedClientOperationImpl( ); } - return IOUtils.evalTemplate( + return evalTemplateResource( getClass(), "runtimes/rust/wrapped/client_operation_impl.part.rs", variables diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/utils/IOUtils.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/utils/IOUtils.java index 73d1b8f47..2ca660a12 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/utils/IOUtils.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/utils/IOUtils.java @@ -82,7 +82,11 @@ public static void writeTemplatedFile( String templateOutputPath, Map parameters ) { - final String content = evalTemplate(klass, templatePath, parameters); + final String content = evalTemplateResource( + klass, + templatePath, + parameters + ); final Path outputPath = rootPath.resolve( safeEvalPathTemplate(templateOutputPath, parameters) ); @@ -121,7 +125,7 @@ public static String safeEvalPathTemplate( * See {@link software.amazon.smithy.utils.AbstractCodeWriter} for documentation * on the templating syntax. */ - public static String evalTemplate( + public static String evalTemplateResource( Class klass, String templatePath, Map context diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/smithy/dafny/codegen/DafnyClientCodegenPluginSettings.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/smithy/dafny/codegen/DafnyClientCodegenPluginSettings.java index 9c22e98b1..4d44f39a1 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/smithy/dafny/codegen/DafnyClientCodegenPluginSettings.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/smithy/dafny/codegen/DafnyClientCodegenPluginSettings.java @@ -60,7 +60,6 @@ class DafnyClientCodegenPluginSettings { * This is used to ensure both Dafny source compatibility * and compatibility with the Dafny compiler and runtime internals, * which shim code generation currently depends on. - * Required when the edition is 2023.10 or later. */ public final DafnyVersion dafnyVersion; @@ -144,15 +143,12 @@ static Optional fromObject( ); } - final String dafnyVersionString; - if ( - edition.ordinal() >= DafnyClientCodegenEdition.EDITION_2023_10.ordinal() - ) { - // Required from this edition on - dafnyVersionString = node.expectStringMember("dafnyVersion").getValue(); - } else { - dafnyVersionString = node.getStringMemberOrDefault("dafnyVersion", "4.1"); - } + // This is now optional since we can get it from dafny itself + final DafnyVersion dafnyVersionString = node + .getStringMember("dafnyVersion") + .map(StringNode::getValue) + .map(DafnyVersion::parse) + .orElse(null); return Optional.of( new DafnyClientCodegenPluginSettings( @@ -160,7 +156,7 @@ static Optional fromObject( serviceId, targetLanguages, includeDafnyFileNormalized, - DafnyVersion.parse(dafnyVersionString) + dafnyVersionString ) ); } diff --git a/scripts/check_dafny_version.sh b/scripts/check_dafny_version.sh deleted file mode 100755 index ea465c7fc..000000000 --- a/scripts/check_dafny_version.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -REQUIRED_VERSION=4.5.0 - -INSTALLED_VERSION=$(dafny --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') - -if [ "$INSTALLED_VERSION" = "" ]; then - echo "Error: Unable to fetch dafny version" - exit 1 -fi - -if [ "$(printf "$INSTALLED_VERSION\n$REQUIRED_VERSION" | sort -V | head -n1)" != "$REQUIRED_VERSION" ]; then - echo "Error: Installed Dafny version does not meet the required $REQUIRED_VERSION version. Upgrade to $REQUIRED_VERSION or newer."; - exit 1; -else - echo "$INSTALLED_VERSION" -fi \ No newline at end of file