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

Code generation build plugin #28

Merged
merged 10 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ let products: [Product] = [
name: "protoc-gen-grpc-swift",
targets: ["protoc-gen-grpc-swift"]
),
.plugin(
name: "GRPCProtobufGenerator",
targets: ["GRPCProtobufGenerator"]
),
]

let dependencies: [Package.Dependency] = [
Expand Down Expand Up @@ -101,6 +105,16 @@ let targets: [Target] = [
],
swiftSettings: defaultSwiftSettings
),

// Code generator build plugin
.plugin(
name: "GRPCProtobufGenerator",
capability: .buildTool(),
dependencies: [
.target(name: "protoc-gen-grpc-swift"),
.product(name: "protoc-gen-swift", package: "swift-protobuf"),
]
),
]

let package = Package(
Expand Down
127 changes: 127 additions & 0 deletions Plugins/GRPCProtobufGenerator/BuildPluginConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright 2024, gRPC Authors 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/

import Foundation

let configFileName = "grpc-swift-proto-generator-config.json"

/// The configuration of the build plugin.
struct BuildPluginConfig: Codable {
/// The visibility of the generated files.
///
/// Defaults to `Internal`.
var visibility: GenerationConfig.Visibility
/// Whether server code is generated.
///
/// Defaults to `true`.
var server: Bool
/// Whether client code is generated.
///
/// Defaults to `true`.
var client: Bool
/// Whether message code is generated.
///
/// Defaults to `true`.
var message: Bool
/// Whether imports should have explicit access levels.
///
/// Defaults to `false`.
var useAccessLevelOnImports: Bool

/// Specify the directory in which to search for imports.
///
/// Paths are relative to the location of the specifying config file.
/// Build plugins only have access to files within the target's source directory.
/// May be specified multiple times; directories will be searched in order.
/// The target source directory is always appended
/// to the import paths.
var importPaths: [String]

/// The path to the `protoc` binary.
///
/// If this is not set, Swift Package Manager will try to find the tool itself.
var protocPath: String?

// Codable conformance with defaults
enum CodingKeys: String, CodingKey {
case visibility
case server
case client
case message
case useAccessLevelOnImports
case importPaths
case protocPath
}

let defaultVisibility: GenerationConfig.Visibility = .internal
let defaultServer = true
let defaultClient = true
let defaultMessage = true
let defaultUseAccessLevelOnImports = false
let defaultImportPaths: [String] = []
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't want to carry around the defaults on each instance, we should just have a static instance:

static let defaults = Self(...)


init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.visibility =
try container.decodeIfPresent(GenerationConfig.Visibility.self, forKey: .visibility)
?? defaultVisibility
self.server = try container.decodeIfPresent(Bool.self, forKey: .server) ?? defaultServer
self.client = try container.decodeIfPresent(Bool.self, forKey: .client) ?? defaultClient
self.message = try container.decodeIfPresent(Bool.self, forKey: .message) ?? defaultMessage
self.useAccessLevelOnImports =
try container.decodeIfPresent(Bool.self, forKey: .useAccessLevelOnImports)
?? defaultUseAccessLevelOnImports
self.importPaths =
try container.decodeIfPresent([String].self, forKey: .importPaths) ?? defaultImportPaths
self.protocPath = try container.decodeIfPresent(String.self, forKey: .protocPath)
}
}

extension GenerationConfig {
init(configurationFile: BuildPluginConfig, configurationFilePath: URL, outputPath: URL) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: s/configuration/config

self.visibility = configurationFile.visibility
self.server = configurationFile.server
self.client = configurationFile.client
self.message = configurationFile.message
// hard-code full-path to avoid collisions since this goes into a temporary directory anyway
self.fileNaming = .fullPath
self.useAccessLevelOnImports = configurationFile.useAccessLevelOnImports
self.importPaths = []
Copy link
Collaborator

Choose a reason for hiding this comment

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

We set this below


// Generate absolute paths for the imports relative to the config file in which they are specified
self.importPaths = configurationFile.importPaths.map { relativePath in
configurationFilePath.deletingLastPathComponent().relativePath + "/" + relativePath
glbrntt marked this conversation as resolved.
Show resolved Hide resolved
}
self.protocPath = configurationFile.protocPath
self.outputPath = outputPath.relativePath
}
}

extension GenerationConfig.Visibility: Codable {
init?(rawValue: String) {
switch rawValue.lowercased() {
case "internal":
self = .internal
case "public":
self = .public
case "package":
self = .package
default:
return nil
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this move to where GenerationConfig.Visibility is declared? Where possible it's best to keep conformances and extensions near their declaration.

Loading
Loading