| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- /*
- * 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
- import PackagePlugin
- extension GRPCProtobufGeneratorCommandPlugin: CommandPlugin {
- func performCommand(context: PluginContext, arguments: [String]) async throws {
- try self.performCommand(
- arguments: arguments,
- tool: context.tool,
- pluginWorkDirectoryURL: context.pluginWorkDirectoryURL
- )
- }
- }
- #if canImport(XcodeProjectPlugin)
- import XcodeProjectPlugin
- // Entry-point when using Xcode projects
- extension GRPCProtobufGeneratorCommandPlugin: XcodeCommandPlugin {
- func performCommand(context: XcodeProjectPlugin.XcodePluginContext, arguments: [String]) throws {
- try self.performCommand(
- arguments: arguments,
- tool: context.tool,
- pluginWorkDirectoryURL: context.pluginWorkDirectoryURL
- )
- }
- }
- #endif
- @main
- struct GRPCProtobufGeneratorCommandPlugin {
- /// Command plugin common code
- func performCommand(
- arguments: [String],
- tool: (String) throws -> PluginContext.Tool,
- pluginWorkDirectoryURL: URL
- ) throws {
- let flagsAndOptions: [String]
- let inputFiles: [String]
- let separatorCount = arguments.count { $0 == CommandConfig.parameterGroupSeparator }
- switch separatorCount {
- case 0:
- var argExtractor = ArgumentExtractor(arguments)
- // check if help requested
- if argExtractor.extractFlag(named: OptionsAndFlags.help.rawValue) > 0 {
- OptionsAndFlags.printHelp(requested: true)
- return
- }
- inputFiles = arguments
- flagsAndOptions = []
- case 1:
- let splitIndex = arguments.firstIndex(of: CommandConfig.parameterGroupSeparator)!
- flagsAndOptions = Array(arguments[..<splitIndex])
- inputFiles = Array(arguments[splitIndex.advanced(by: 1)...])
- default:
- throw CommandPluginError.tooManyParameterSeparators
- }
- var argExtractor = ArgumentExtractor(flagsAndOptions)
- // help requested
- if argExtractor.extractFlag(named: OptionsAndFlags.help.rawValue) > 0 {
- OptionsAndFlags.printHelp(requested: true)
- return
- }
- // MARK: Configuration
- let commandConfig: CommandConfig
- do {
- commandConfig = try CommandConfig.parse(
- argumentExtractor: &argExtractor,
- pluginWorkDirectory: pluginWorkDirectoryURL
- )
- } catch {
- throw error
- }
- if commandConfig.verbose {
- Stderr.print("InputFiles: \(inputFiles.joined(separator: ", "))")
- }
- let config = commandConfig.common
- let protocPath = try deriveProtocPath(using: config, tool: tool)
- let protocGenGRPCSwiftPath = try tool("protoc-gen-grpc-swift").url
- let protocGenSwiftPath = try tool("protoc-gen-swift").url
- let outputDirectory = URL(fileURLWithPath: config.outputPath)
- if commandConfig.verbose {
- Stderr.print(
- "Generated files will be written to: '\(outputDirectory.absoluteStringNoScheme)'"
- )
- }
- let inputFileURLs = inputFiles.map { URL(fileURLWithPath: $0) }
- // MARK: protoc-gen-grpc-swift
- if config.clients || config.servers {
- let arguments = constructProtocGenGRPCSwiftArguments(
- config: config,
- fileNaming: config.fileNaming,
- inputFiles: inputFileURLs,
- protoDirectoryPaths: config.importPaths,
- protocGenGRPCSwiftPath: protocGenGRPCSwiftPath,
- outputDirectory: outputDirectory
- )
- try executeProtocInvocation(
- executableURL: protocPath,
- arguments: arguments,
- verbose: commandConfig.verbose,
- dryRun: commandConfig.dryRun
- )
- if !commandConfig.dryRun, commandConfig.verbose {
- Stderr.print("Generated gRPC Swift files for \(inputFiles.joined(separator: ", ")).")
- }
- }
- // MARK: protoc-gen-swift
- if config.messages {
- let arguments = constructProtocGenSwiftArguments(
- config: config,
- fileNaming: config.fileNaming,
- inputFiles: inputFileURLs,
- protoDirectoryPaths: config.importPaths,
- protocGenSwiftPath: protocGenSwiftPath,
- outputDirectory: outputDirectory
- )
- try executeProtocInvocation(
- executableURL: protocPath,
- arguments: arguments,
- verbose: commandConfig.verbose,
- dryRun: commandConfig.dryRun
- )
- if !commandConfig.dryRun, commandConfig.verbose {
- Stderr.print(
- "Generated protobuf message Swift files for \(inputFiles.joined(separator: ", "))."
- )
- }
- }
- }
- }
- /// Execute a single invocation of `protoc`, printing output and if in verbose mode the invocation
- /// - Parameters:
- /// - executableURL: The path to the `protoc` executable.
- /// - arguments: The arguments to be passed to `protoc`.
- /// - verbose: Whether or not to print verbose output
- /// - dryRun: If this invocation is a dry-run, i.e. will not actually be executed
- func executeProtocInvocation(
- executableURL: URL,
- arguments: [String],
- verbose: Bool,
- dryRun: Bool
- ) throws {
- if verbose {
- Stderr.print("\(executableURL.absoluteStringNoScheme) \\")
- Stderr.print(" \(arguments.joined(separator: " \\\n "))")
- }
- if dryRun {
- return
- }
- let process = Process()
- process.executableURL = executableURL
- process.arguments = arguments
- let outputPipe = Pipe()
- let errorPipe = Pipe()
- process.standardOutput = outputPipe
- process.standardError = errorPipe
- do {
- try process.run()
- } catch {
- try printProtocOutput(outputPipe, verbose: verbose)
- let stdErr: String?
- if let errorData = try errorPipe.fileHandleForReading.readToEnd() {
- stdErr = String(decoding: errorData, as: UTF8.self)
- } else {
- stdErr = nil
- }
- throw CommandPluginError.generationFailure(
- errorDescription: "\(error)",
- executable: executableURL.absoluteStringNoScheme,
- arguments: arguments,
- stdErr: stdErr
- )
- }
- process.waitUntilExit()
- try printProtocOutput(outputPipe, verbose: verbose)
- if process.terminationReason == .exit && process.terminationStatus == 0 {
- return
- }
- let stdErr: String?
- if let errorData = try errorPipe.fileHandleForReading.readToEnd() {
- stdErr = String(decoding: errorData, as: UTF8.self)
- } else {
- stdErr = nil
- }
- let problem = "\(process.terminationReason):\(process.terminationStatus)"
- throw CommandPluginError.generationFailure(
- errorDescription: problem,
- executable: executableURL.absoluteStringNoScheme,
- arguments: arguments,
- stdErr: stdErr
- )
- }
- func printProtocOutput(_ stdOut: Pipe, verbose: Bool) throws {
- if verbose, let outputData = try stdOut.fileHandleForReading.readToEnd() {
- let output = String(decoding: outputData, as: UTF8.self)
- let lines = output.split { $0.isNewline }
- print("protoc output:")
- for line in lines {
- print("\t\(line)")
- }
- }
- }
|