-
Notifications
You must be signed in to change notification settings - Fork 35
feat: Create code generator build tool plugin #999
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
Changes from all commits
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"), | ||
| .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,19 @@ let package = Package( | |
| .target( | ||
| name: "SmithyWaitersAPI" | ||
| ), | ||
| .plugin( | ||
| name: "SmithyCodeGenerator", | ||
| capability: .buildTool(), | ||
| dependencies: [ | ||
| "SmithyCodegenCLI", | ||
| ] | ||
| ), | ||
| .executableTarget( | ||
| name: "SmithyCodegenCLI", | ||
| dependencies: [ | ||
| .product(name: "ArgumentParser", package: "swift-argument-parser"), | ||
| ] | ||
| ), | ||
|
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.
|
||
| .testTarget( | ||
| name: "ClientRuntimeTests", | ||
| dependencies: [ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| // | ||
| // 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 | ||
|
|
||
| @main | ||
| struct SmithyCodeGeneratorPlugin: BuildToolPlugin { | ||
|
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 the build tool plugin implementation.
Note that the plugin doesn't actually invoke the code generation tool. Rather, the Swift build system will insert the invocation into the build process and will perform it at the appropriate time. |
||
|
|
||
| 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( | ||
| name: target.name, | ||
| for: $0, | ||
| in: context.pluginWorkDirectory, | ||
| with: smithyCodegenCLITool.path | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| private func createBuildCommand( | ||
| name: String, | ||
| for inputPath: Path, | ||
| in outputDirectoryPath: Path, | ||
| with generatorToolPath: Path | ||
| ) throws -> Command? { | ||
| // Skip any file that isn't the smithy-model-info.json for this service. | ||
| guard inputPath.lastComponent == "smithy-model-info.json" else { return nil } | ||
|
|
||
| let currentWorkingDirectoryFileURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) | ||
|
|
||
| // 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 = currentWorkingDirectoryFileURL.appendingPathComponent(smithyModelInfo.path) | ||
| let modelPath = Path(modelPathURL.path) | ||
|
|
||
| // Construct the schemas.swift path. | ||
| let schemasSwiftPath = outputDirectoryPath.appending("\(name)Schemas.swift") | ||
|
|
||
| // Construct the build command that invokes SmithyCodegenCLI. | ||
| return .buildCommand( | ||
| displayName: "Generating Swift source files from model file \(smithyModelInfo.path)", | ||
| executable: generatorToolPath, | ||
| arguments: [ | ||
| "--schemas-path", schemasSwiftPath, | ||
| modelPath | ||
| ], | ||
| inputFiles: [inputPath, modelPath], | ||
| outputFiles: [schemasSwiftPath] | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /// 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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| // | ||
| // Copyright Amazon.com Inc. or its affiliates. | ||
| // All Rights Reserved. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
|
|
||
| import ArgumentParser | ||
| import Foundation | ||
|
|
||
| @main | ||
| struct SmithyCodegenCLI: AsyncParsableCommand { | ||
|
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 the CLI tool that will be invoked to perform code generation. This CLI tool is currently just a shell - it resolves file URLs for its parameters, then just creates an empty schemas Swift file as its output (the file must exist, even if empty, for the build to succeed, since the build plugin declares a schemas Swift file as its output). Actually implementing code generation will be a follow-on task. |
||
|
|
||
| @Argument(help: "The full or relative path to the JSON model file.") | ||
| var modelPath: String | ||
|
|
||
| @Option(help: "The full or relative path to write the schemas output file.") | ||
| var schemasPath: String? | ||
|
|
||
| func run() async throws { | ||
| let currentWorkingDirectoryFileURL = currentWorkingDirectoryFileURL() | ||
| print("Current working directory: \(currentWorkingDirectoryFileURL.path)") | ||
|
|
||
| // Create the model file URL | ||
| let modelFileURL = URL(fileURLWithPath: modelPath, relativeTo: currentWorkingDirectoryFileURL) | ||
| guard FileManager.default.fileExists(atPath: modelFileURL.path) else { | ||
| throw SmithyCodegenCLIError(localizedDescription: "no file at model path \(modelFileURL.path)") | ||
| } | ||
| print("Model file path: \(modelFileURL.path)") | ||
|
|
||
| // If --schemas-path was supplied, create the schema file URL | ||
| let schemasFileURL = resolve(paramName: "--schemas-path", path: schemasPath) | ||
|
|
||
| // All file URLs needed for code generation have now been resolved. | ||
| // Implement code generation here. | ||
| if let schemasFileURL { | ||
| print("Schemas file path: \(schemasFileURL)") | ||
| FileManager.default.createFile(atPath: schemasFileURL.path, contents: Data()) | ||
| } | ||
| } | ||
|
|
||
| private func currentWorkingDirectoryFileURL() -> URL { | ||
| // Get the current working directory as a file URL | ||
| var currentWorkingDirectoryPath = FileManager.default.currentDirectoryPath | ||
| if !currentWorkingDirectoryPath.hasSuffix("/") { | ||
| currentWorkingDirectoryPath.append("/") | ||
| } | ||
| return URL(fileURLWithPath: currentWorkingDirectoryPath) | ||
| } | ||
|
|
||
| private func resolve(paramName: String, path: String?) -> URL? { | ||
| if let path { | ||
| let fileURL = URL(fileURLWithPath: path, relativeTo: currentWorkingDirectoryFileURL()) | ||
| print("Resolved \(paramName): \(fileURL.path)") | ||
| return fileURL | ||
| } else { | ||
| print("\(paramName) not provided, skipping generation") | ||
| return nil | ||
| } | ||
| } | ||
| } | ||
|
|
||
| struct SmithyCodegenCLIError: Error { | ||
| let localizedDescription: String | ||
| } | ||
| 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. Invokes the model file info generator (see generator implementation immediately below.) |
||
|
|
||
| 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 file generates a file named Its contents are {"path":"relative/path/to/model.json"}where The Swift code generator will use this file to locate the model at build time. |
||
| 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 plugin is "published" from smithy-swift so that it can be installed into service clients that smithy-swift creates.
See awslabs/aws-sdk-swift#2058 to see how a Smithy-based SDK installs the plugin into a service client target.