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 command plugin #40

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Conversation

rnro
Copy link
Collaborator

@rnro rnro commented Jan 30, 2025

Motivation:

To make it simpler to generate gRPC stubs with protoc-gen-grpc-swift and protoc-gen-swift.

Modifications:

  • Add a new command plugin
  • Refactor some errors

The command plugin can be invoked from the CLI as:

swift package generate-grpc-code-from-protos /path/to/Protos/HelloWorld.proto --import-path /path/to/Protos

The plugin has flexible configuration:

❯ swift package generate-grpc-code-from-protos --help
Usage: swift package generate-grpc-code-from-protos [flags] [input files]

Flags:

  --servers                   Whether server code is generated. Defaults to true.
  --clients                   Whether client code is generated. Defaults to true.
  --messages                  Whether message code is generated. Defaults to true.
  --file-naming               The naming scheme for output files [fullPath/pathToUnderscores/dropPath]. Defaults to fullPath.
  --access-level              The access level of the generated source [internal/public/package]. Defaults to internal.
  --access-level-on-imports   Whether imports should have explicit access levels. Defaults to false.
  --import-path               The directory in which to search for imports.
  --protoc-path               The path to the `protoc` binary.
  --output                    The path into which the generated source files are created.
  --dry-run                   Print but do not execute the protoc commands.
  --help                      Print this help.
  • When executing, the command prints the protoc invocations it uses for ease of debugging. The --dry-run flag can be supplied for this purpose or so that they may be extracted and used separately e.g. in a script.
  • If no protoc path is supplied then Swift Package Manager will attempt to locate it.
  • If no output directory is supplied then generated files are placed a Swift Package Manager build directory.

Result:

More convenient code generation

This PR is split out of #26

Motivation:

To make it simpler to generate gRPC stubs with `protoc-gen-grpc-swift` and `protoc-gen-swift`.

Modifications:

* Add a new command plugin
  `swift package generate-grpc-code-from-protos/path/to/Protos/HelloWorld.proto --import-path /path/to/Protos`
* Refactor some errors

Result:

More convenient code generation
@rnro rnro added the 🔨 semver/patch No public API change. label Jan 30, 2025
@rnro rnro requested a review from glbrntt January 30, 2025 09:04
Copy link
Collaborator

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

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

This looks good so far! Most of the comments are around the UX.

]
),
dependencies: [
"protoc-gen-grpc-swift",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"protoc-gen-grpc-swift",
.target(name: "protoc-gen-grpc-swift"),


// Code generator SwiftPM command
.plugin(
name: "GRPCGeneratorCommand",
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should include "Protobuf" in the name. I find "Command" a weird suffix but that stems more from me finding the "command plugin" naming weird as well.

Suggested change
name: "GRPCGeneratorCommand",
name: "GRPCProtobufGeneratorCommand",

struct CommandConfig {
var common: GenerationConfig

var dryRun: Bool
Copy link
Collaborator

Choose a reason for hiding this comment

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

Love this, great idea!

case "package":
config.common.accessLevel = .`package`
default:
Diagnostics.error("Unknown accessLevel \(value)")
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should parrot out the name of the argument as passed by the user ("--access-level") rather than the property name.

switch flag {
case .accessLevel:
let accessLevel = argExtractor.extractOption(named: flag.rawValue)
if let value = accessLevel.first {
Copy link
Collaborator

Choose a reason for hiding this comment

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

If there's more than one value we should either warn or throw an error.

Copy link
Collaborator

Choose a reason for hiding this comment

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

(This feedback applies to all of these options.)

let inputFileURLs = inputFiles.map { URL(fileURLWithPath: $0) }

// MARK: protoc-gen-grpc-swift
if config.clients != false || config.servers != false {
Copy link
Collaborator

Choose a reason for hiding this comment

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

These should be non-optional now so can be more idiomatic:

Suggested change
if config.clients != false || config.servers != false {
if config.clients || config.servers {

}

// MARK: protoc-gen-swift
if config.messages != false {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if config.messages != false {
if config.messages {

/// - arguments: The arguments to be passed to `protoc`.
func printProtocInvocation(_ executableURL: URL, _ arguments: [String]) {
print("protoc invocation:")
print(" \(executableURL.absoluteStringNoScheme) \\")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
print(" \(executableURL.absoluteStringNoScheme) \\")
print("\(executableURL.absoluteStringNoScheme) \\")

/// - executableURL: The path to the `protoc` executable.
/// - arguments: The arguments to be passed to `protoc`.
func printProtocInvocation(_ executableURL: URL, _ arguments: [String]) {
print("protoc invocation:")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure this is necessary; protoc ... will make it obvious.

func printProtocInvocation(_ executableURL: URL, _ arguments: [String]) {
print("protoc invocation:")
print(" \(executableURL.absoluteStringNoScheme) \\")
for argument in arguments[..<arguments.count.advanced(by: -1)] {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't you just do print(arguments.joined(separator: " \\\n "))?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔨 semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants