-
Notifications
You must be signed in to change notification settings - Fork 6
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
Conversation
Motivation: To make code generation more convenient for adopters. Modifications: * New build plugin to generate gRPC services and protobuf messages Result: * Users will be able to make use of the build plugin.
Package.swift
Outdated
name: "GRPCGeneratorPlugin", | ||
targets: ["GRPCGeneratorPlugin"] |
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.
Having plugin in the name is weird. I think we also want to nod to it being the generator for protobuf, so can we call it GRPCProtobufGenerator
?
Package.swift
Outdated
name: "GRPCGeneratorPlugin", | ||
capability: .buildTool(), | ||
dependencies: [ | ||
"protoc-gen-grpc-swift", |
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.
nit, for consistency:
"protoc-gen-grpc-swift", | |
.target(name: "protoc-gen-grpc-swift"), |
*/ | ||
|
||
/// The configuration of the plugin. | ||
struct ConfigurationFile: Codable { |
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.
nit for consistency: gRPC uses "config" instead of "configuration".
"File" is also out of place here; we should nod to its use for generation here instead. "GeneratorConfig"? "PluginConfig"? "Config"?
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.
So "File" is in the name because it corresponds specifically to the format of the configuration file on disk which is why it is Codable
, there is an upcoming structure which knows about configuration which is passed as flags and the common configuration representation is already in this PR. I'm not sure how that fits into your existing naming schemes.
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.
It being on disk is incidental though. The name should indicate what it's config for not where the config comes from.
If plugins were (much) more flexible you could imagine getting the config from e.g. a config service. Using the name ConfigFile
for that type would be weird!
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 original intent was to separate the on-disk format from the internal representation but I think combined with the abstraction we'll have for the more general config when combined with the command plugin that would lead to too much layering in this small application since none of it is public API.
// /// Whether reflection data is generated. | ||
// var reflectionData: Bool? |
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.
We can get rid of this as it's commented out
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.
We can if you'd prefer - I didn't know how imminent the use of reflection data would be
) | ||
|
||
return Command.buildCommand( | ||
displayName: "Generating protobuf Swift files for \(inputFile.relativePath)", |
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.
displayName: "Generating protobuf Swift files for \(inputFile.relativePath)", | |
displayName: "Generating Swift Protobuf files for \(inputFile.relativePath)", |
// TODO: Don't currently support implementation only imports | ||
// // Add the implementation only imports flag if it was set | ||
// if let implementationOnlyImports = config.implementationOnlyImports { | ||
// protocArgs.append("--swift_opt=ImplementationOnlyImports=\(implementationOnlyImports)") | ||
// } |
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.
Let's remove this
|
||
if let importPaths = config.importPaths { | ||
for path in importPaths { | ||
protocArgs.append("-I") |
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.
-I
is a synonym for --proto_path
which we also set below, why do we have both and have two source of values for them? Also, the protoc-gen-swift args only have --proto_path
it seems. Somethin' ain't right here!
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 two sources of values is because one is user-specified and one is a default one which we add based on the path to the proto file we're currently processing. I've made them both use --proto_path
and fixed up the missing source.
// TODO: Don't currently support reflection data | ||
// if let generateReflectionData = config.reflectionData { | ||
// protocArgs.append("--grpc-swift_opt=ReflectionData=\(generateReflectionData)") | ||
// } |
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.
Let's remove this
/// - outputDirectory: The directory in which generated source files are created. | ||
/// - outputExtension: The file extension to be appended to generated files in-place of `.proto`. | ||
/// - Returns: The expected output file path. | ||
func deriveOutputFilePath( |
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.
IIRC the output file path is only needed by the build plugin which can only use the .fullPath
file naming. If so we can simplify this, right?
let defaultVisibility: GenerationConfig.Visibility = .internal | ||
let defaultServer = true | ||
let defaultClient = true | ||
let defaultMessage = true | ||
let defaultUseAccessLevelOnImports = false | ||
let defaultImportPaths: [String] = [] |
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.
We don't want to carry around the defaults on each instance, we should just have a static instance:
static let defaults = Self(...)
} | ||
|
||
extension GenerationConfig { | ||
init(configurationFile: BuildPluginConfig, configurationFilePath: URL, outputPath: URL) { |
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.
nit: s/configuration/config
// hard-code full-path to avoid collisions since this goes into a temporary directory anyway | ||
self.fileNaming = .fullPath | ||
self.useAccessLevelOnImports = configurationFile.useAccessLevelOnImports | ||
self.importPaths = [] |
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.
We set this below
/// Whether client code is generated. | ||
var client: Bool | ||
/// Whether message code is generated. | ||
var message: Bool |
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.
nit: wondering whether this should be messages
? WDYT?
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.
I have changed all of the generation options to be plural and restructured the config to have a nested structure.
{
"generate": {
"clients": true,
"servers": true,
"messages": true,
},
"generatedSource": {
"accessLevelOnImports": false,
"accessLevel": "internal",
}
"protoc": {
"executablePath": "/opt/homebrew/bin/protoc"
"importPaths": [
"../directory_1",
],
},
}
/// - Returns: The constructed arguments to be passed to `protoc` when invoking the `proto-gen-swift` `protoc` plugin. | ||
func constructProtocGenSwiftArguments( | ||
config: GenerationConfig, | ||
using fileNaming: GenerationConfig.FileNaming?, |
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.
using
in the context of constructing args feels way outta place. Let's just use fileNaming
here
Co-authored-by: George Barnett <[email protected]>
Co-authored-by: George Barnett <[email protected]>
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.
This is looking really good. Just a couple of minor things.
displayName: "Generating gRPC Swift files for \(inputFile.relativePath)", | ||
executable: protocPath, | ||
arguments: arguments, | ||
inputFiles: [inputFile, protocGenGRPCSwiftPath], |
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.
... still no config file here!
* limitations under the License. | ||
*/ | ||
|
||
import Foundation |
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.
This import is unused
func constructProtocGenSwiftArguments( | ||
config: GenerationConfig, | ||
fileNaming: GenerationConfig.FileNaming?, | ||
inputFiles: [URL], |
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.
I'm probably misremembering but I thought we landed on one input file per invocation? I don't remember the reasoning so chances are I am misremembering.
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.
We did but this code will be shared with the command plugin which doesn't have to work in that pattern.
#28 (comment) |
Co-authored-by: George Barnett <[email protected]>
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.
Great work, thanks a lot @rnro!
Overview
New build plugin to generate gRPC services and protobuf messages
The SwiftPM build plugin will locate protobuf files in the
Sources
directory (with the extension.proto
) and attempt to invoke both theprotoc-gen-swift
andprotoc-gen-grpc-swift
protoc
plugins on them to automatically generate Swift source. Behavior can be modified by specifying one or more configuration files (namedgrpc-swift-config.json
).Configuration
Adoption
Users must add the plugin to any target which wishes to make use of it
This PR is split out of #26