Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
113 changes: 57 additions & 56 deletions Plugins/GRPCProtobufGeneratorCommand/CommandConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,67 +185,68 @@ enum OptionsAndFlags: String, CaseIterable {
}

extension OptionsAndFlags {
func usageDescription() -> String {
switch self {
case .servers:
return "Generate server code. Generated by default."
case .noServers:
return "Do not generate server code. Generated by default."
case .clients:
return "Generate client code. Generated by default."
case .noClients:
return "Do not generate client code. Generated by default."
case .messages:
return "Generate message code. Generated by default."
case .noMessages:
return "Do not generate message code. Generated by default."
case .fileNaming:
return
"The naming scheme for output files [fullPath/pathToUnderscores/dropPath]. Defaults to fullPath."
case .accessLevel:
return
"The access level of the generated source [internal/public/package]. Defaults to internal."
case .accessLevelOnImports:
return "Whether imports should have explicit access levels. Defaults to false."
case .importPath:
return
"The directory in which to search for imports. May be specified multiple times. If none are specified the current working directory is used."
case .protocPath:
return "The path to the protoc binary."
case .dryRun:
return "Print but do not execute the protoc commands."
case .outputPath:
return "The directory into which the generated source files are created."
case .verbose:
return "Emit verbose output."
case .help:
return "Print this help."
}
static var helpText: String {
"""
USAGE: swift package generate-grpc-code-from-protos [[<options> [--]] <inputs> ...

ARGUMENTS:
<inputs> The '.proto' files or directories containing them.

OPTIONS:
--servers/--no-servers Generate server code (default: --servers)
--clients/--no-clients Generate client code (default: --clients)
--messages/--no-messages Generate message code (default: --messages)
--access-level <access> Access level of generated code (internal/public/package)
(default: internal)
--access-level-on-imports Whether imports have explicit access levels
--protoc-path <path> Path to the protoc binary
--import-path <path> Directory to search for imports, may be specified
multiple times. If none are specified the current
working directory is used.
--file-naming <naming> The naming scheme for generated files
(fullPath/pathToUnderscores/dropPath)
(default: fullPath).
--output-path <path> Directory to generate files into
--verbose Emit verbose output
--dry-run Print but don't execute the protoc commands
--help Print this help

EXAMPLES:

swift package generate-grpc-code-from-protos service.proto
Generates servers, clients, and messages from 'service.proto' into
the current working directory.

swift package generate-grpc-code-from-protos --no-clients --no-messages -- service1.proto service2.proto
Generate only servers from service1.proto and service2.proto into the
current working directory.

swift package generate-grpc-code-from-protos --output-path Generated --access-level public -- Protos
Generate server, clients, and messages from all .proto files contained
within the 'Protos' directory into the 'Generated' directory at the
public access level.

swift package --allow-writing-to-package-directory generate-grpc-code-from-protos --output-path Sources/Generated -- service.proto
Generates code from service.proto into the Sources/Generated directory
within a Swift Package without asking for permission to do so.

PERMISSIONS:
Swift Package Manager command plugins require permission to create files.
You'll be prompted to give generate-grpc-code-from-protos permission
when running it.

You can grant permissions by specifying --allow-writing-to-package-directory
or --allow-writing-to-directory to the swift package command.

See swift package plugin --help for more info.
"""
}

static func printHelp(requested: Bool) {
let printMessage: (String) -> Void
if requested {
printMessage = { message in print(message) }
print(Self.helpText)
} else {
printMessage = Stderr.print
}

printMessage(
"Usage: swift package generate-grpc-code-from-protos [flags] [\(CommandConfig.parameterGroupSeparator)] [input files]"
)
printMessage("")
printMessage("Flags:")
printMessage("")

let spacing = 3
let maxLength =
(OptionsAndFlags.allCases.map(\.rawValue).max(by: { $0.count < $1.count })?.count ?? 0)
+ spacing
for flag in OptionsAndFlags.allCases {
printMessage(
" --\(flag.rawValue.padding(toLength: maxLength, withPad: " ", startingAt: 0))\(flag.usageDescription())"
)
Stderr.print(Self.helpText)
}
}
}
6 changes: 3 additions & 3 deletions Plugins/GRPCProtobufGeneratorCommand/CommandPluginError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ extension CommandPluginError: CustomStringConvertible {

case .invalidInputFiles(let files):
var lines: [String] = []
lines.append("Invalid input file(s)")
lines.append("Found \(files.count) invalid input file(s)")
lines.append("")
lines.append("Found \(files.count) input(s) not ending in '.proto':")
lines.append("The following don't exist or aren't '.proto' files or directories:")
for file in files {
lines.append("- \(file)")
}
lines.append("")
lines.append("All options must be before '--', and all input files must be")
lines.append("after '--'. Input files must end in '.proto'.")
lines.append("after '--'. Inputs be '.proto' files or directories.")
lines.append("")
lines.append("See --help for more information.")
return lines.joined(separator: "\n")
Expand Down
55 changes: 48 additions & 7 deletions Plugins/GRPCProtobufGeneratorCommand/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,10 @@ struct GRPCProtobufGeneratorCommandPlugin {
}

// Split the input into options and input files.
let (flagsAndOptions, inputFiles) = self.splitArgs(arguments)
let (flagsAndOptions, inputs) = self.splitArgs(arguments)

// Check the input files all look like protos
let nonProtoInputs = inputFiles.filter { !$0.hasSuffix(".proto") }
if !nonProtoInputs.isEmpty {
throw CommandPluginError.invalidInputFiles(nonProtoInputs)
}
let (inputFiles, extraImportPaths) = try self.checkAndExpandInputs(inputs)

if inputFiles.isEmpty {
throw CommandPluginError.missingInputFile
Expand Down Expand Up @@ -101,7 +98,7 @@ struct GRPCProtobufGeneratorCommandPlugin {
config: config,
fileNaming: config.fileNaming,
inputFiles: inputFileURLs,
protoDirectoryPaths: config.importPaths,
protoDirectoryPaths: config.importPaths + extraImportPaths,
protocGenGRPCSwiftPath: protocGenGRPCSwiftPath,
outputDirectory: outputDirectory
)
Expand All @@ -124,7 +121,7 @@ struct GRPCProtobufGeneratorCommandPlugin {
config: config,
fileNaming: config.fileNaming,
inputFiles: inputFileURLs,
protoDirectoryPaths: config.importPaths,
protoDirectoryPaths: config.importPaths + extraImportPaths,
protocGenSwiftPath: protocGenSwiftPath,
outputDirectory: outputDirectory
)
Expand Down Expand Up @@ -159,6 +156,50 @@ struct GRPCProtobufGeneratorCommandPlugin {

return (options, inputs)
}

private func checkAndExpandInputs(
_ files: [String]
) throws -> (protos: [String], directories: [String]) {
let fileManager = FileManager.default
var invalidInputFiles = [String]()
var protos = [String]()
var dirs = [String]()

for file in files {
// Check that each file:
// a) exists, and
// b) is either a '.proto' file or a directory.
//
// If the file is a directory then all '.proto' files within the directory
// are added as input files.
var isDirectory = ObjCBool(false)
let exists = fileManager.fileExists(atPath: file, isDirectory: &isDirectory)

if !exists {
invalidInputFiles.append(file)
} else if isDirectory.boolValue {
dirs.append(file)
// Do a deep traversal of the directory.
if var enumerator = fileManager.enumerator(atPath: file) {
while let path = enumerator.nextObject() as? String {
if path.hasSuffix(".proto") {
protos.append(path)
}
}
}
} else if file.hasSuffix(".proto") {
protos.append(file)
} else {
invalidInputFiles.append(file)
}
}

if !invalidInputFiles.isEmpty {
throw CommandPluginError.invalidInputFiles(invalidInputFiles)
}

return (protos, dirs)
}
}

/// Execute a single invocation of `protoc`, printing output and if in verbose mode the invocation
Expand Down