Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Generate service clients as modularized Swift packages #757

Merged
merged 12 commits into from
Jun 13, 2024

Conversation

jbelkins
Copy link
Contributor

@jbelkins jbelkins commented Jun 12, 2024

Issue #

awslabs/aws-sdk-swift#1222

Description of changes

  • Generates a simple Package.swift for service clients. A pre-existing PackageManifestGenerator has been updated to perform this. (The Package.swift is currently not copied to the SDK after codegen; it is reserved for later use.)
  • The structure of service client files has been changed to match the standard Swift package:
    • Source files are written to Sources/{service name}.
    • Tests are written to Tests/{service name}Tests.
  • SwiftWriter automatically uses written symbols to assemble a list of the needed dependencies for a service: if a referenced symbol requires a dependency, then it is added to Package.swift.
  • All Symbols have been updated with dependency and type information. As a result, roughly all import statements in service clients are now for imports of specific types.
  • Some static functions and extensions have been updated to make them easier to import and to hide them better from customers.

Scope

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

public extension URLRequest {
init(sdkRequest: SdkHttpRequest) async throws {
public extension SdkHttpRequest {
static func makeURLRequest(from sdkRequest: SdkHttpRequest) async throws -> URLRequest {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this to an extension on SdkHttpRequest to prevent polluting URLRequest with it.

@@ -83,101 +83,82 @@ public func timestampReadingClosure<Reader: SmithyReader>(format: TimestampForma
}
}

public extension String {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the following changes on ReadingClosures & WritingClosures:

  • Removed them as extensions on the types they read/write; instead they are static methods. This prevents them from polluting the namespace of Swift and Foundation types.

@@ -1,13 +1,16 @@
// Code generated by smithy-swift-codegen. DO NOT EDIT!

import ClientRuntime
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many WeatherSDK files changed below due to codegen changes.

You can look to see how service clients are affected, or you can skip past WeatherSDK entirely.

@@ -65,7 +65,7 @@ class HttpRequestTests: NetworkingTestUtils {

let httpBody = ByteStream.data(expectedMockRequestData)
let mockHttpRequest = SdkHttpRequest(method: .get, endpoint: endpoint, body: httpBody)
let urlRequest = try await URLRequest(sdkRequest: mockHttpRequest)
let urlRequest = try await SdkHttpRequest.makeURLRequest(from: mockHttpRequest)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calls the new method for converting SdkHttpRequest to URLRequest.

try writer["FlattenedListArg"].writeList(value.flattenedListArg, memberWritingClosure: String.write(value:to:), memberNodeInfo: "member", isFlattened: true)
try writer["ListArg"].writeList(value.listArg, memberWritingClosure: String.write(value:to:), memberNodeInfo: "member", isFlattened: false)
try writer["FlattenedListArg"].writeList(value.flattenedListArg, memberWritingClosure: WritingClosures.writeString(value:to:), memberNodeInfo: "member", isFlattened: true)
try writer["ListArg"].writeList(value.listArg, memberWritingClosure: WritingClosures.writeString(value:to:), memberNodeInfo: "member", isFlattened: false)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adapted to new writing closures

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several changes like this in source files and tests below. Will not comment

into("$sourcesDir/WeatherSDK")
exclude("Package.swift")
}
copy {
from("$outputDir/WeatherSDKTests")
from("$outputDir/Tests/WeatherSDKTests")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copies WeatherSDK files to the correct locations for a Swift package.

writePackageManifest(settings, fileManifest, dependencies, shouldGenerateTestTarget)
LOGGER.info("Flushing swift writers")
writers.flushWriters()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing the package manifest used to be done with a separate writer; IDK why. It is now done with a SwiftWriter from the same context as all other generated files.

val separator = if (typesToConformMiddlewareTo.count() == 1) "" else ", "
return typesToConformMiddlewareTo.joinToString(separator) { it.toString() }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed dead code

.map { writer.format("\$N", it) }
.joinToString(", ")
writer.openBlock("public struct \$L: \$L {", "}", middleware.typeName, inheritance) {
writer.write("public let id: \$N = \$S", SwiftTypes.String, middleware.id)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inherited types are now written with a writer so they are properly imported.

.name("${ClientRuntimeTypes.Middleware.OperationOutput}<$outputType>")
.addDependency(SwiftDependency.CLIENT_RUNTIME)
.addReference(ClientRuntimeTypes.Middleware.OperationOutput)
.addReference(outputType)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.addReference() allows a symbol to import another symbol along with itself. This is useful for when you want to have a symbol that has another in its definition, such as collection types.

writer.write("// swift-tools-version:\$L", ctx.settings.swiftVersion)
writer.write("")
writer.write("import PackageDescription")
writer.write("")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package.swift is an exception among generated files: it writes its own import statements, since it needs to have special content at the top of the file. Going forward, though, the only import in Package.swift should be PackageDescription.

val target = dependency.expectProperty("target", String::class.java)
writer.write("name: \"${target}\",")
writer.write("package: \"${dependency.packageName}\"")
dependenciesByURL.forEach { dependency ->
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the old logic for reading env vars in Package.swift is removed.

For now, Package.swift will get dependencies from local folders... in the future, it will point to published package versions.

writer.write(".library(name: \"${settings.moduleName}\", targets: [\"${settings.moduleName}\"])")
}
val externalDependencies = dependencies.filter { it.getProperty("url", String::class.java).get().isNotEmpty() }
val dependenciesByURL = externalDependencies.distinctBy { it.expectProperty("url", String::class.java) }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dependencies are provided by the writer delegator based on the symbols that were imported, and are separated into lists for writing to Package.swift.

ShapeType.BIG_DECIMAL -> {
writer.addImport(SwiftDependency.BIG.target)
writer.writeInline("Complex(\$L)", (node.value as Double).toBigDecimal())
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BigInteger and BigDecimal are unused codegen and are removed. Those types will use Swift Int and Double, respectively.

ShapeType.BIG_DECIMAL -> {
writer.addImport(SwiftDependency.BIG.target)
writer.writeInline("Complex(\$L)", (node.value as Double).toBigDecimal())
}
else -> throw CodegenException("unexpected shape type $currShape for numberNode")
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "types" files below were moved to the "swiftmodules" folder; Git just didn't track them

var indirectOrNot = ""
if (indirect) {
writer.addImport(ClientRuntimeTypes.Core.Indirect)
indirectOrNot = "@Indirect "
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClientRuntimeTypes.Core.Indirect is imported directly when the @Indirect property wrapper is referenced

"smithy-swift"
);

companion object {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These SwiftDependencies were changed from enum cases to static values

.addDependency(SwiftDependency.BIG)
.build()
}
override fun bigDecimalShape(shape: BigDecimalShape): Symbol = numberShape(shape, "Double", "0.0")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BigInteger and BigDecimal changed to Int and Double, as previously mentioned

@@ -97,14 +97,28 @@ class SwiftWriter(private val fullPackageName: String, swiftImportContainer: Swi
}

fun addImport(symbol: Symbol) {
symbol.references.forEach { addImport(it.symbol) }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports the referenced symbols for a symbol that is imported

} ?: run {
addImport(symbol.namespace, internalSPIName = spiName)
}
symbol.dependencies.forEach { addDependency(it) }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds the symbol's dependencies to the writer so they can be tracked for later writing to the Package.swift


constructor(
name: String,
type: Symbol,
default: String,
default: (SwiftWriter) -> String,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default is changed to a closure that takes a SwiftWriter so that any symbols that are part of the default value can be properly recorded.

// writer.write("self.logLevel = logLevel")
// }
// }
// writer.write("")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LogHandlerFactoy appears to be unused. If that's the case, I will remove this.

ctx.delegator.useTestFileWriter(testFilename, ctx.settings.moduleName) { writer ->
LOGGER.fine("Generating request protocol test cases for ${operation.id}")
for (import in imports) {
writer.addImport(import)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to use this means of importing anymore so it is removed

is MapShape -> {
private fun makeReadingClosure(shape: Shape, memberTimestampFormatTrait: TimestampFormatTrait? = null, isSparse: Boolean): String {
val base = when {
shape is MapShape -> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this logic a bit so that I can write the new reading closures correctly.

@@ -54,19 +52,3 @@ val ServiceShape.isRPCBound: Boolean
AWSProtocol.AWS_JSON_1_0, AWSProtocol.AWS_JSON_1_1, AWSProtocol.AWS_QUERY, AWSProtocol.EC2_QUERY -> true
else -> false
}

// Adds the imports needed for models of this protocol
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer needed

val base = when (shape) {
is MapShape -> {
val base = when {
shape is MapShape -> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is changed very similar to the ReadingClosure Utils above

.dependencies(SwiftDependency.CLIENT_RUNTIME).build()
.addReference(inputType)
.dependencies(SwiftDependency.CLIENT_RUNTIME)
.build()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the Symbol .addReferences() feature again

.putProperty("spiName", spiName)
.build()
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is removed, the symbol factory method does this for us now when the symbol is made

"$this"
}
fun Symbol.renderSwiftType(writer: SwiftWriter): String {
return writer.format("\$N", this)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writer has this same logic built-in and will do this for us

@@ -0,0 +1,16 @@
package software.amazon.smithy.swift.codegen.swiftmodules
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several "types" files below. All of them have SwiftDeclaration added to all symbols, and use the new helper method to construct the symbols.

@@ -10,14 +10,14 @@ class AuthSchemeResolverGeneratorTests {
@Test
fun `test auth scheme resolver generation`() {
val context = setupTests("auth-scheme-resolver-generator-test.smithy", "com.test#Example")
val contents = getFileContents(context.manifest, "/Example/AuthSchemeResolver.swift")
val contents = getFileContents(context.manifest, "Sources/Example/AuthSchemeResolver.swift")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many tests follow. In general, file locations are updated, and code is updated with namespaced symbols. No further comments on tests.

@jbelkins jbelkins merged commit 33e4ea4 into main Jun 13, 2024
17 checks passed
@jbelkins jbelkins deleted the jbe/package_swift branch June 13, 2024 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants