-
Notifications
You must be signed in to change notification settings - Fork 35
feat: Add Swift-native codegen plugin #993
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
base: jbe/add_schema
Are you sure you want to change the base?
Changes from all commits
aa823be
e817d55
6a9c537
33cc093
6b54575
ae474f1
70f7669
3d623e6
d535e66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,10 +53,12 @@ let package = Package( | |
| .library(name: "SmithyCBOR", targets: ["SmithyCBOR"]), | ||
| .library(name: "SmithyWaitersAPI", targets: ["SmithyWaitersAPI"]), | ||
| .library(name: "SmithyTestUtil", targets: ["SmithyTestUtil"]), | ||
| .plugin(name: "SmithyCodeGenerator", targets: ["SmithyCodeGenerator"]), | ||
| ], | ||
| dependencies: { | ||
| var dependencies: [Package.Dependency] = [ | ||
| .package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.54.2"), | ||
| .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.0"), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), | ||
| .package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "1.13.0"), | ||
| ] | ||
|
|
@@ -258,6 +260,23 @@ let package = Package( | |
| .target( | ||
| name: "SmithyWaitersAPI" | ||
| ), | ||
| .plugin( | ||
| name: "SmithyCodeGenerator", | ||
| capability: .buildTool(), | ||
| dependencies: [ | ||
| "SmithyCodegenCLI", | ||
| ] | ||
| ), | ||
| .executableTarget( | ||
| name: "SmithyCodegenCLI", | ||
| dependencies: [ | ||
| "SmithyCodegenCore", | ||
| .product(name: "ArgumentParser", package: "swift-argument-parser"), | ||
| ] | ||
| ), | ||
| .target( | ||
| name: "SmithyCodegenCore" | ||
| ), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3 targets are added above:
|
||
| .testTarget( | ||
| name: "ClientRuntimeTests", | ||
| dependencies: [ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| // | ||
| // Copyright Amazon.com Inc. or its affiliates. | ||
| // All Rights Reserved. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
|
|
||
| import struct Foundation.Data | ||
| import class Foundation.FileManager | ||
| import class Foundation.JSONDecoder | ||
| import struct Foundation.URL | ||
| import PackagePlugin | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This type implements the Swift build tool plugin. Essentially, all the build plugin does is:
|
||
| @main | ||
| struct SmithyCodeGeneratorPlugin: BuildToolPlugin { | ||
|
|
||
| func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { | ||
| // This plugin only runs for package targets that can have source files. | ||
| guard let sourceFiles = target.sourceModule?.sourceFiles else { return [] } | ||
|
|
||
| // Retrieve the `SmithyCodegenCLI` tool from the plugin's tools. | ||
| let smithyCodegenCLITool = try context.tool(named: "SmithyCodegenCLI") | ||
|
|
||
| // Construct a build command for each source file with a particular suffix. | ||
| return try sourceFiles.map(\.path).compactMap { | ||
| try createBuildCommand(for: $0, in: context.pluginWorkDirectory, with: smithyCodegenCLITool.path) | ||
| } | ||
| } | ||
|
|
||
| private func createBuildCommand( | ||
| for inputPath: Path, | ||
| in outputDirectoryPath: Path, | ||
| with generatorToolPath: Path | ||
| ) throws -> Command? { | ||
| let currentWorkingDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) | ||
|
|
||
| // Skip any file that isn't the smithy-model-info.json for this service. | ||
| guard inputPath.lastComponent == "smithy-model-info.json" else { return nil } | ||
|
|
||
| // Get the smithy model path. | ||
| let modelInfoData = try Data(contentsOf: URL(fileURLWithPath: inputPath.string)) | ||
| let smithyModelInfo = try JSONDecoder().decode(SmithyModelInfo.self, from: modelInfoData) | ||
| let modelPathURL = currentWorkingDirectoryURL.appendingPathComponent(smithyModelInfo.path) | ||
| let modelPath = Path(modelPathURL.path) | ||
|
|
||
| // Return a command that will run during the build to generate the output file. | ||
| let modelCountSwiftPath = outputDirectoryPath.appending("ModelCount.swift") | ||
| return .buildCommand( | ||
| displayName: "Generating Swift source files from \(smithyModelInfo.path)", | ||
| executable: generatorToolPath, | ||
| arguments: [modelPath, modelCountSwiftPath], | ||
| inputFiles: [inputPath, modelPath], | ||
| outputFiles: [modelCountSwiftPath] | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /// Codable structure for reading the contents of `smithy-model-info.json` | ||
| private struct SmithyModelInfo: Decodable { | ||
| /// The path to the model, from the root of the target's project. Required. | ||
| let path: String | ||
| } | ||
|
|
||
| struct Err: Error { | ||
| var localizedDescription: String { "boom" } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // | ||
| // Copyright Amazon.com Inc. or its affiliates. | ||
| // All Rights Reserved. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
|
|
||
| import ArgumentParser | ||
| import Foundation | ||
| import SmithyCodegenCore | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a very simple code generator that:
While this example codegen is trivial and not useful in itself, it does prove that:
Follow-on development would code-generate more substantial content from the model. |
||
| @main | ||
| struct SmithyCodegenCLI: AsyncParsableCommand { | ||
|
|
||
| @Argument(help: "The full path to the JSON model file.") | ||
| var modelPath: String | ||
|
|
||
| @Argument(help: "The full path to write the output file. Intermediate directories will be created as needed.") | ||
| var outputPath: String | ||
|
|
||
| func run() async throws { | ||
| guard FileManager.default.fileExists(atPath: modelPath) else { | ||
| throw SmithySchemaCodegenToolError(localizedDescription: "no file at model path") | ||
| } | ||
| let model = try SmithyModel(modelFileURL: URL(fileURLWithPath: modelPath)) | ||
|
|
||
| let outputFileURL = URL(fileURLWithPath: outputPath) | ||
| let contents = """ | ||
| // | ||
| // Copyright Amazon.com Inc. or its affiliates. | ||
| // All Rights Reserved. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
|
|
||
| // Code generated by SmithyCodegenCLI. DO NOT EDIT! | ||
|
|
||
| /// The count of bytes in the model. | ||
| public let modelCount = \(model.count) | ||
| """ | ||
| try FileManager.default.createDirectory( | ||
| at: outputFileURL.deletingLastPathComponent(), | ||
| withIntermediateDirectories: true | ||
| ) | ||
| try Data(contents.utf8).write(to: outputFileURL) | ||
| } | ||
| } | ||
|
|
||
| struct SmithySchemaCodegenToolError: Error { | ||
| let localizedDescription: String | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // | ||
| // Copyright Amazon.com Inc. or its affiliates. | ||
| // All Rights Reserved. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
|
|
||
| import struct Foundation.Data | ||
| import struct Foundation.URL | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All this type does right now is record the number of bytes in the model file. In the future it would read the model into an in-memory tree for use during code generation. |
||
| public struct SmithyModel { | ||
| public let count: Int | ||
|
|
||
| public init(modelFileURL: URL) throws { | ||
| let modelData = try Data(contentsOf: modelFileURL) | ||
| self.count = modelData.count | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -107,6 +107,9 @@ class DirectedSwiftCodegen( | |
| DependencyJSONGenerator(ctx).writePackageJSON(writers.dependencies) | ||
| } | ||
|
|
||
| LOGGER.info("Generating Smithy model file info") | ||
| SmithyModelFileInfoGenerator(ctx).writeSmithyModelFileInfo() | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initiates generation of the |
||
| LOGGER.info("Flushing swift writers") | ||
| writers.flushWriters() | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package software.amazon.smithy.swift.codegen | ||
|
|
||
| import software.amazon.smithy.aws.traits.ServiceTrait | ||
| import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator | ||
| import software.amazon.smithy.swift.codegen.model.getTrait | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This just generates a very simple JSON file named {"path":"codegen/sdk-codegen/aws-models/s3.json"} |
||
| class SmithyModelFileInfoGenerator( | ||
| val ctx: ProtocolGenerator.GenerationContext, | ||
| ) { | ||
| fun writeSmithyModelFileInfo() { | ||
| ctx.service.getTrait<ServiceTrait>()?.let { serviceTrait -> | ||
| val filename = "Sources/${ctx.settings.moduleName}/smithy-model-info.json" | ||
| val modelFileName = | ||
| serviceTrait | ||
| .sdkId | ||
| .lowercase() | ||
| .replace(",", "") | ||
| .replace(" ", "-") | ||
| val contents = "codegen/sdk-codegen/aws-models/$modelFileName.json" | ||
| ctx.delegator.useFileWriter(filename) { writer -> | ||
| writer.write("{\"path\":\"$contents\"}") | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
SmithyCodeGeneratorplugin is exported so that service clients inaws-sdk-swiftcan add it to their target definitions.