From 13ddcf3628a045cd952072f88b660839f955bef6 Mon Sep 17 00:00:00 2001 From: Rick Newton-Rogers Date: Fri, 17 Jan 2025 15:52:51 +0000 Subject: [PATCH] review comments, rethink config & imports --- .../BuildPluginConfig.swift | 246 ++++++++++++------ Plugins/GRPCProtobufGenerator/Plugin.swift | 173 ++++++------ Plugins/PluginsShared/GenerationConfig.swift | 24 +- Plugins/PluginsShared/PluginError.swift | 6 +- Plugins/PluginsShared/PluginUtils.swift | 64 ++--- 5 files changed, 311 insertions(+), 202 deletions(-) diff --git a/Plugins/GRPCProtobufGenerator/BuildPluginConfig.swift b/Plugins/GRPCProtobufGenerator/BuildPluginConfig.swift index 32bab7e..d8aa554 100644 --- a/Plugins/GRPCProtobufGenerator/BuildPluginConfig.swift +++ b/Plugins/GRPCProtobufGenerator/BuildPluginConfig.swift @@ -18,110 +18,184 @@ import Foundation let configFileName = "grpc-swift-proto-generator-config.json" -/// The configuration of the build plugin. +/// The config 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? + /// Config defining which components should be considered when generating source. + struct Generate { + /// Whether server code is generated. + /// + /// Defaults to `true`. + var servers: Bool + /// Whether client code is generated. + /// + /// Defaults to `true`. + var clients: Bool + /// Whether message code is generated. + /// + /// Defaults to `true`. + var messages: Bool + + static let defaults = Self( + servers: true, + clients: true, + messages: true + ) + + private init(servers: Bool, clients: Bool, messages: Bool) { + self.servers = servers + self.clients = clients + self.messages = messages + } + } + + /// Config relating to the generated code itself. + struct GeneratedSource { + /// The visibility of the generated files. + /// + /// Defaults to `Internal`. + var accessLevel: GenerationConfig.AccessLevel + /// Whether imports should have explicit access levels. + /// + /// Defaults to `false`. + var useAccessLevelOnImports: Bool + + static let defaults = Self( + accessLevel: .internal, + useAccessLevelOnImports: false + ) + + private init(accessLevel: GenerationConfig.AccessLevel, useAccessLevelOnImports: Bool) { + self.accessLevel = accessLevel + self.useAccessLevelOnImports = useAccessLevelOnImports + } + } + + /// Config relating to the protoc invocation. + struct Protoc { + /// 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` executable binary. + /// + /// If this is not set, Swift Package Manager will try to find the tool itself. + var executablePath: String? + + static let defaults = Self( + importPaths: [], + executablePath: nil + ) + + private init(importPaths: [String], executablePath: String?) { + self.importPaths = importPaths + self.executablePath = executablePath + } + } + + /// Config defining which components should be considered when generating source. + var generate: Generate + /// Config relating to the nature of the generated code. + var generatedSource: GeneratedSource + /// Config relating to the protoc invocation. + var protoc: Protoc + + static let defaults = Self( + generate: Generate.defaults, + generatedSource: GeneratedSource.defaults, + protoc: Protoc.defaults + ) + private init(generate: Generate, generatedSource: GeneratedSource, protoc: Protoc) { + self.generate = generate + self.generatedSource = generatedSource + self.protoc = protoc + } // Codable conformance with defaults enum CodingKeys: String, CodingKey { - case visibility - case server - case client - case message + case generate + case generatedSource + case protoc + } + + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.generate = try container.decodeIfPresent(Generate.self, forKey: .generate) ?? Self.defaults.generate + self.generatedSource = try container.decodeIfPresent(GeneratedSource.self, forKey: .generatedSource) ?? Self.defaults.generatedSource + self.protoc = try container.decodeIfPresent(Protoc.self, forKey: .protoc) ?? Self.defaults.protoc + } +} + +extension BuildPluginConfig.Generate: Codable { + // Codable conformance with defaults + enum CodingKeys: String, CodingKey { + case servers + case clients + case messages + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.servers = try container.decodeIfPresent(Bool.self, forKey: .servers) ?? Self.defaults.servers + self.clients = try container.decodeIfPresent(Bool.self, forKey: .clients) ?? Self.defaults.clients + self.messages = try container.decodeIfPresent(Bool.self, forKey: .messages) ?? Self.defaults.messages + } +} + +extension BuildPluginConfig.GeneratedSource: Codable { + // Codable conformance with defaults + enum CodingKeys: String, CodingKey { + case accessLevel 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] = [] + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.accessLevel = try container.decodeIfPresent(GenerationConfig.AccessLevel.self, forKey: .accessLevel) + ?? Self.defaults.accessLevel + self.useAccessLevelOnImports = try container.decodeIfPresent(Bool.self,forKey: .useAccessLevelOnImports) + ?? Self.defaults.useAccessLevelOnImports + } +} + +extension BuildPluginConfig.Protoc: Codable { + // Codable conformance with defaults + enum CodingKeys: String, CodingKey { + case importPaths + case executablePath + } 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) + self.importPaths = try container.decodeIfPresent([String].self,forKey: .importPaths) + ?? Self.defaults.importPaths + self.executablePath = try container.decodeIfPresent(String.self, forKey: .executablePath) } } extension GenerationConfig { - init(configurationFile: BuildPluginConfig, configurationFilePath: URL, outputPath: URL) { - self.visibility = configurationFile.visibility - self.server = configurationFile.server - self.client = configurationFile.client - self.message = configurationFile.message + init(buildPluginConfig: BuildPluginConfig, configFilePath: URL, outputPath: URL) { + self.server = buildPluginConfig.generate.servers + self.client = buildPluginConfig.generate.clients + self.message = buildPluginConfig.generate.messages // hard-code full-path to avoid collisions since this goes into a temporary directory anyway self.fileNaming = .fullPath - self.useAccessLevelOnImports = configurationFile.useAccessLevelOnImports - self.importPaths = [] - + self.visibility = buildPluginConfig.generatedSource.accessLevel + self.useAccessLevelOnImports = buildPluginConfig.generatedSource.useAccessLevelOnImports // 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 - } - 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 + self.importPaths = buildPluginConfig.protoc.importPaths.map { relativePath in + configFilePath.deletingLastPathComponent().absoluteStringNoScheme + "/" + relativePath } + self.protocPath = buildPluginConfig.protoc.executablePath + self.outputPath = outputPath.absoluteStringNoScheme } } diff --git a/Plugins/GRPCProtobufGenerator/Plugin.swift b/Plugins/GRPCProtobufGenerator/Plugin.swift index 0ed74b7..2aad4b3 100644 --- a/Plugins/GRPCProtobufGenerator/Plugin.swift +++ b/Plugins/GRPCProtobufGenerator/Plugin.swift @@ -17,6 +17,50 @@ import Foundation import PackagePlugin + +// Entry-point when using Package manifest +extension GRPCProtobufGenerator: BuildToolPlugin { + /// Create build commands, the entry-point when using a Package manifest. + func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { + guard let swiftTarget = target as? SwiftSourceModuleTarget else { + throw PluginError.incompatibleTarget(target.name) + } + let configFiles = swiftTarget.sourceFiles(withSuffix: configFileName).map { $0.url } + let inputFiles = swiftTarget.sourceFiles(withSuffix: ".proto").map { $0.url } + return try createBuildCommands( + pluginWorkDirectory: context.pluginWorkDirectoryURL, + tool: context.tool, + inputFiles: inputFiles, + configFiles: configFiles, + targetName: target.name + ) + } +} + +#if canImport(XcodeProjectPlugin) +import XcodeProjectPlugin + +// Entry-point when using Xcode projects +extension GRPCProtobufGenerator: XcodeBuildToolPlugin { + /// Create build commands, the entry-point when using an Xcode project. + func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] { + let configFiles = target.inputFiles.filter { + $0.url.lastPathComponent == configFileName + }.map { $0.url } + let inputFiles = target.inputFiles.filter { $0.url.lastPathComponent.hasSuffix(".proto") }.map { + $0.url + } + return try createBuildCommands( + pluginWorkDirectory: context.pluginWorkDirectoryURL, + tool: context.tool, + inputFiles: inputFiles, + configFiles: configFiles, + targetName: target.displayName + ) + } +} +#endif + @main struct GRPCProtobufGenerator { /// Build plugin code common to both invocation types: package manifest Xcode project @@ -27,7 +71,7 @@ struct GRPCProtobufGenerator { configFiles: [URL], targetName: String ) throws -> [Command] { - let configs = try readConfigurationFiles(configFiles, pluginWorkDirectory: pluginWorkDirectory) + let configs = try readConfigFiles(configFiles, pluginWorkDirectory: pluginWorkDirectory) let protocGenGRPCSwiftPath = try tool("protoc-gen-grpc-swift").url let protocGenSwiftPath = try tool("protoc-gen-swift").url @@ -35,18 +79,24 @@ struct GRPCProtobufGenerator { var commands: [Command] = [] for inputFile in inputFiles { guard let (configFilePath, config) = configs.findApplicableConfig(for: inputFile) else { - throw PluginError.noConfigurationFilesFound + throw PluginError.noConfigFilesFound } let protocPath = try deriveProtocPath(using: config, tool: tool) - let protoDirectoryPath = inputFile.deletingLastPathComponent() + let protoDirectoryPaths: [String] + if !config.importPaths.isEmpty { + protoDirectoryPaths = config.importPaths + } else { + protoDirectoryPaths = [configFilePath.deletingLastPathComponent().absoluteStringNoScheme] + } // unless *explicitly* opted-out if config.client || config.server { let grpcCommand = try protocGenGRPCSwiftCommand( inputFile: inputFile, config: config, - protoDirectoryPath: protoDirectoryPath, + baseDirectoryPath: configFilePath.deletingLastPathComponent(), + protoDirectoryPaths: protoDirectoryPaths, protocPath: protocPath, protocGenGRPCSwiftPath: protocGenGRPCSwiftPath ) @@ -54,11 +104,12 @@ struct GRPCProtobufGenerator { } // unless *explicitly* opted-out - if config.message != false { + if config.message { let protoCommand = try protocGenSwiftCommand( inputFile: inputFile, config: config, - protoDirectoryPath: protoDirectoryPath, + baseDirectoryPath: configFilePath.deletingLastPathComponent(), + protoDirectoryPaths: protoDirectoryPaths, protocPath: protocPath, protocGenSwiftPath: protocGenSwiftPath, configFilePath: configFilePath @@ -71,22 +122,22 @@ struct GRPCProtobufGenerator { } } -/// Reads the configuration files at the supplied URLs into memory -/// - Parameter configurationFiles: URLs from which to load configuration -/// - Returns: A map of source URLs to loaded configuration -func readConfigurationFiles( +/// Reads the config files at the supplied URLs into memory +/// - Parameter configFilePaths: URLs from which to load config +/// - Returns: A map of source URLs to loaded config +func readConfigFiles( _ configFilePaths: [URL], pluginWorkDirectory: URL ) throws -> [URL: GenerationConfig] { var configs: [URL: GenerationConfig] = [:] for configFilePath in configFilePaths { - let data = try Data(contentsOf: configFile) + let data = try Data(contentsOf: configFilePath) let config = try JSONDecoder().decode(BuildPluginConfig.self, from: data) // the output directory mandated by the plugin system configs[configFilePath] = GenerationConfig( - configurationFile: config, - configurationFilePath: configFilePath, + buildPluginConfig: config, + configFilePath: configFilePath, outputPath: pluginWorkDirectory ) } @@ -114,18 +165,20 @@ extension [URL: GenerationConfig] { } } -/// Construct the command to invoke `protoc` with the `proto-gen-grpc-swift` plugin. +/// Construct the command to invoke `protoc` with the `protoc-gen-grpc-swift` plugin. /// - Parameters: /// - inputFile: The input `.proto` file. -/// - config: The configuration for this operation. -/// - protoDirectoryPath: The root path to the source `.proto` files used as the reference for relative path naming schemes. +/// - config: The config for this operation. +/// - baseDirectoryPath: The root path to the source `.proto` files used as the reference for relative path naming schemes. +/// - protoDirectoryPaths: The paths passed to `protoc` in which to look for imported proto files. /// - protocPath: The path to `protoc` -/// - protocGenGRPCSwiftPath: The path to `proto-gen-grpc-swift`. -/// - Returns: The command to invoke `protoc` with the `proto-gen-grpc-swift` plugin. +/// - protocGenGRPCSwiftPath: The path to `protoc-gen-grpc-swift`. +/// - Returns: The command to invoke `protoc` with the `protoc-gen-grpc-swift` plugin. func protocGenGRPCSwiftCommand( inputFile: URL, config: GenerationConfig, - protoDirectoryPath: URL, + baseDirectoryPath: URL, + protoDirectoryPaths: [String], protocPath: URL, protocGenGRPCSwiftPath: URL ) throws -> PackagePlugin.Command { @@ -133,22 +186,22 @@ func protocGenGRPCSwiftCommand( let outputFilePath = deriveOutputFilePath( for: inputFile, - protoDirectoryPath: protoDirectoryPath, + baseDirectoryPath: baseDirectoryPath, outputDirectory: outputPathURL, outputExtension: "grpc.swift" ) let arguments = constructProtocGenGRPCSwiftArguments( config: config, - using: config.fileNaming, + fileNaming: config.fileNaming, inputFiles: [inputFile], - protoDirectoryPaths: [protoDirectoryPath], + protoDirectoryPaths: protoDirectoryPaths, protocGenGRPCSwiftPath: protocGenGRPCSwiftPath, outputDirectory: outputPathURL ) return Command.buildCommand( - displayName: "Generating gRPC Swift files for \(inputFile.relativePath)", + displayName: "Generating gRPC Swift files for \(inputFile.absoluteStringNoScheme)", executable: protocPath, arguments: arguments, inputFiles: [inputFile, protocGenGRPCSwiftPath], @@ -156,19 +209,21 @@ func protocGenGRPCSwiftCommand( ) } -/// Construct the command to invoke `protoc` with the `proto-gen-swift` plugin. +/// Construct the command to invoke `protoc` with the `protoc-gen-swift` plugin. /// - Parameters: /// - inputFile: The input `.proto` file. -/// - config: The configuration for this operation. -/// - protoDirectoryPath: The root path to the source `.proto` files used as the reference for relative path naming schemes. +/// - config: The config for this operation. +/// - baseDirectoryPath: The root path to the source `.proto` files used as the reference for relative path naming schemes. +/// - protoDirectoryPaths: The paths passed to `protoc` in which to look for imported proto files. /// - protocPath: The path to `protoc` -/// - protocGenSwiftPath: The path to `proto-gen-grpc-swift`. +/// - protocGenSwiftPath: The path to `protoc-gen-grpc-swift`. /// - configFilePath: The path to the config file in use. -/// - Returns: The command to invoke `protoc` with the `proto-gen-swift` plugin. +/// - Returns: The command to invoke `protoc` with the `protoc-gen-swift` plugin. func protocGenSwiftCommand( inputFile: URL, config: GenerationConfig, - protoDirectoryPath: URL, + baseDirectoryPath: URL, + protoDirectoryPaths: [String], protocPath: URL, protocGenSwiftPath: URL, configFilePath: URL @@ -177,22 +232,22 @@ func protocGenSwiftCommand( let outputFilePath = deriveOutputFilePath( for: inputFile, - protoDirectoryPath: protoDirectoryPath, + baseDirectoryPath: baseDirectoryPath, outputDirectory: outputPathURL, outputExtension: "pb.swift" ) let arguments = constructProtocGenSwiftArguments( config: config, - using: config.fileNaming, + fileNaming: config.fileNaming, inputFiles: [inputFile], - protoDirectoryPaths: [protoDirectoryPath], + protoDirectoryPaths: protoDirectoryPaths, protocGenSwiftPath: protocGenSwiftPath, outputDirectory: outputPathURL ) return Command.buildCommand( - displayName: "Generating Swift Protobuf files for \(inputFile.relativePath)", + displayName: "Generating Swift Protobuf files for \(inputFile.absoluteStringNoScheme)", executable: protocPath, arguments: arguments, inputFiles: [ @@ -204,16 +259,17 @@ func protocGenSwiftCommand( ) } -/// Derive the expected output file path to match the behavior of the `proto-gen-swift` and `proto-gen-grpc-swift` `protoc` plugins. +/// Derive the expected output file path to match the behavior of the `protoc-gen-swift` and `protoc-gen-grpc-swift` `protoc` plugins +/// when using the `FullPath` naming scheme. /// - Parameters: /// - inputFile: The input `.proto` file. -/// - protoDirectoryPath: The root path to the source `.proto` files used as the reference for relative path naming schemes. +/// - baseDirectoryPath: The root path to the source `.proto` files used as the reference for relative path naming schemes. /// - 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( for inputFile: URL, - protoDirectoryPath: URL, + baseDirectoryPath: URL, outputDirectory: URL, outputExtension: String ) -> URL { @@ -225,7 +281,7 @@ func deriveOutputFilePath( // find the inputFile path relative to the proto directory var relativePathComponents = inputFile.deletingLastPathComponent().pathComponents - for protoDirectoryPathComponent in protoDirectoryPath.pathComponents { + for protoDirectoryPathComponent in baseDirectoryPath.pathComponents { if relativePathComponents.first == protoDirectoryPathComponent { relativePathComponents.removeFirst() } else { @@ -240,46 +296,3 @@ func deriveOutputFilePath( } return outputFilePath } - -// Entry-point when using Package manifest -extension GRPCProtobufGenerator: BuildToolPlugin { - /// Create build commands, the entry-point when using a Package manifest. - func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { - guard let swiftTarget = target as? SwiftSourceModuleTarget else { - throw PluginError.incompatibleTarget(target.name) - } - let configFiles = swiftTarget.sourceFiles(withSuffix: configFileName).map { $0.url } - let inputFiles = swiftTarget.sourceFiles(withSuffix: ".proto").map { $0.url } - return try createBuildCommands( - pluginWorkDirectory: context.pluginWorkDirectoryURL, - tool: context.tool, - inputFiles: inputFiles, - configFiles: configFiles, - targetName: target.name - ) - } -} - -#if canImport(XcodeProjectPlugin) -import XcodeProjectPlugin - -// Entry-point when using Xcode projects -extension GRPCProtobufGenerator: XcodeBuildToolPlugin { - /// Create build commands, the entry-point when using an Xcode project. - func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] { - let configFiles = target.inputFiles.filter { - $0.url.lastPathComponent == configFileName - }.map { $0.url } - let inputFiles = target.inputFiles.filter { $0.url.lastPathComponent.hasSuffix(".proto") }.map { - $0.url - } - return try createBuildCommands( - pluginWorkDirectory: context.pluginWorkDirectoryURL, - tool: context.tool, - inputFiles: inputFiles, - configFiles: configFiles, - targetName: target.displayName - ) - } -} -#endif diff --git a/Plugins/PluginsShared/GenerationConfig.swift b/Plugins/PluginsShared/GenerationConfig.swift index 56993fa..9309f50 100644 --- a/Plugins/PluginsShared/GenerationConfig.swift +++ b/Plugins/PluginsShared/GenerationConfig.swift @@ -14,10 +14,10 @@ * limitations under the License. */ -/// The configuration used when generating code whether called from the build or command plugin. +/// The config used when generating code whether called from the build or command plugin. struct GenerationConfig { - /// The visibility of the generated files. - enum Visibility: String { + /// The access level (i.e. visibility) of the generated files. + enum AccessLevel: String { /// The generated files should have `internal` access level. case `internal` = "Internal" /// The generated files should have `public` access level. @@ -25,6 +25,7 @@ struct GenerationConfig { /// The generated files should have `package` access level. case `package` = "Package" } + /// The naming of output files with respect to the path of the source file. /// /// For an input of `foo/bar/baz.proto` the following output file will be generated: @@ -41,7 +42,7 @@ struct GenerationConfig { } /// The visibility of the generated files. - var visibility: Visibility + var visibility: AccessLevel /// Whether server code is generated. var server: Bool /// Whether client code is generated. @@ -67,3 +68,18 @@ struct GenerationConfig { /// The path into which the generated source files are created. var outputPath: String } + +extension GenerationConfig.AccessLevel: Codable { + init?(rawValue: String) { + switch rawValue.lowercased() { + case "internal": + self = .internal + case "public": + self = .public + case "package": + self = .package + default: + return nil + } + } +} diff --git a/Plugins/PluginsShared/PluginError.swift b/Plugins/PluginsShared/PluginError.swift index 714f39c..5d88c12 100644 --- a/Plugins/PluginsShared/PluginError.swift +++ b/Plugins/PluginsShared/PluginError.swift @@ -19,7 +19,7 @@ import Foundation enum PluginError: Error { // Build plugin case incompatibleTarget(String) - case noConfigurationFilesFound + case noConfigFilesFound } extension PluginError: CustomStringConvertible { @@ -27,8 +27,8 @@ extension PluginError: CustomStringConvertible { switch self { case .incompatibleTarget(let string): "Build plugin applied to incompatible target." - case .noConfigurationFilesFound: - "No configuration files found." + case .noConfigFilesFound: + "No config files found. The build plugin relies on the existence of one or more '\(configFileName)' files in the target source." } } } diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift index 5e8fc17..8cd49cc 100644 --- a/Plugins/PluginsShared/PluginUtils.swift +++ b/Plugins/PluginsShared/PluginUtils.swift @@ -19,7 +19,7 @@ import PackagePlugin /// Derive the path to the instance of `protoc` to be used. /// - Parameters: -/// - config: The supplied configuration. If no path is supplied then one is discovered using the `PROTOC_PATH` environment variable or the `findTool`. +/// - config: The supplied config. If no path is supplied then one is discovered using the `PROTOC_PATH` environment variable or the `findTool`. /// - findTool: The context-supplied tool which is used to attempt to discover the path to a `protoc` binary. /// - Returns: The path to the instance of `protoc` to be used. func deriveProtocPath( @@ -37,67 +37,64 @@ func deriveProtocPath( } } -/// Construct the arguments to be passed to `protoc` when invoking the `proto-gen-swift` `protoc` plugin. +/// Construct the arguments to be passed to `protoc` when invoking the `protoc-gen-swift` `protoc` plugin. /// - Parameters: -/// - config: The configuration for this operation. +/// - config: The config for this operation. /// - fileNaming: The file naming scheme to be used. /// - inputFiles: The input `.proto` files. -/// - protoDirectoryPaths: The directories in which `protoc` will search for imports. -/// - protocGenSwiftPath: The path to the `proto-gen-swift` `protoc` plugin. +/// - protoDirectoryPaths: The directories in which `protoc` will look for imports. +/// - protocGenSwiftPath: The path to the `protoc-gen-swift` `protoc` plugin. /// - outputDirectory: The directory in which generated source files are created. -/// - Returns: The constructed arguments to be passed to `protoc` when invoking the `proto-gen-swift` `protoc` plugin. +/// - Returns: The constructed arguments to be passed to `protoc` when invoking the `protoc-gen-swift` `protoc` plugin. func constructProtocGenSwiftArguments( config: GenerationConfig, - using fileNaming: GenerationConfig.FileNaming?, + fileNaming: GenerationConfig.FileNaming?, inputFiles: [URL], - protoDirectoryPaths: [URL], + protoDirectoryPaths: [String], protocGenSwiftPath: URL, outputDirectory: URL ) -> [String] { var protocArgs = [ - "--plugin=protoc-gen-swift=\(protocGenSwiftPath.relativePath)", - "--swift_out=\(outputDirectory.relativePath)", + "--plugin=protoc-gen-swift=\(protocGenSwiftPath.absoluteStringNoScheme)", + "--swift_out=\(outputDirectory.absoluteStringNoScheme)", ] - for path in config.importPaths { - protocArgs.append("--proto_path") - protocArgs.append("\(path)") + for path in protoDirectoryPaths { + protocArgs.append("--proto_path=\(path)") } protocArgs.append("--swift_opt=Visibility=\(config.visibility.rawValue)") protocArgs.append("--swift_opt=FileNaming=\(config.fileNaming.rawValue)") protocArgs.append("--swift_opt=UseAccessLevelOnImports=\(config.useAccessLevelOnImports)") - protocArgs.append(contentsOf: protoDirectoryPaths.map { "--proto_path=\($0.relativePath)" }) - protocArgs.append(contentsOf: inputFiles.map { $0.relativePath }) + protocArgs.append(contentsOf: inputFiles.map { $0.absoluteStringNoScheme }) return protocArgs } -/// Construct the arguments to be passed to `protoc` when invoking the `proto-gen-grpc-swift` `protoc` plugin. +/// Construct the arguments to be passed to `protoc` when invoking the `protoc-gen-grpc-swift` `protoc` plugin. /// - Parameters: -/// - config: The configuration for this operation. +/// - config: The config for this operation. /// - fileNaming: The file naming scheme to be used. /// - inputFiles: The input `.proto` files. -/// - protoDirectoryPaths: The directories in which `protoc` will search for imports. -/// - protocGenGRPCSwiftPath: The path to the `proto-gen-grpc-swift` `protoc` plugin. +/// - protoDirectoryPaths: The directories in which `protoc` will look for imports. +/// - protocGenGRPCSwiftPath: The path to the `protoc-gen-grpc-swift` `protoc` plugin. /// - outputDirectory: The directory in which generated source files are created. -/// - Returns: The constructed arguments to be passed to `protoc` when invoking the `proto-gen-grpc-swift` `protoc` plugin. +/// - Returns: The constructed arguments to be passed to `protoc` when invoking the `protoc-gen-grpc-swift` `protoc` plugin. func constructProtocGenGRPCSwiftArguments( config: GenerationConfig, - using fileNaming: GenerationConfig.FileNaming?, + fileNaming: GenerationConfig.FileNaming?, inputFiles: [URL], - protoDirectoryPaths: [URL], + protoDirectoryPaths: [String], protocGenGRPCSwiftPath: URL, outputDirectory: URL ) -> [String] { var protocArgs = [ - "--plugin=protoc-gen-grpc-swift=\(protocGenGRPCSwiftPath.relativePath)", - "--grpc-swift_out=\(outputDirectory.relativePath)", + "--plugin=protoc-gen-grpc-swift=\(protocGenGRPCSwiftPath.absoluteStringNoScheme)", + "--grpc-swift_out=\(outputDirectory.absoluteStringNoScheme)", ] - for path in config.importPaths { - protocArgs.append("--proto_path") - protocArgs.append("\(path)") + for path in protoDirectoryPaths { + protocArgs.append("--proto_path=\(path)") } protocArgs.append("--grpc-swift_opt=Visibility=\(config.visibility.rawValue.capitalized)") @@ -105,8 +102,17 @@ func constructProtocGenGRPCSwiftArguments( protocArgs.append("--grpc-swift_opt=Client=\(config.client)") protocArgs.append("--grpc-swift_opt=FileNaming=\(config.fileNaming.rawValue)") protocArgs.append("--grpc-swift_opt=UseAccessLevelOnImports=\(config.useAccessLevelOnImports)") - protocArgs.append(contentsOf: protoDirectoryPaths.map { "--proto_path=\($0.relativePath)" }) - protocArgs.append(contentsOf: inputFiles.map { $0.relativePath }) + protocArgs.append(contentsOf: inputFiles.map { $0.absoluteStringNoScheme }) return protocArgs } + + +extension URL { + /// Returns `URL.absoluteString` with the `file://` scheme prefix removed + var absoluteStringNoScheme: String { + var absoluteString = self.absoluteString + absoluteString.trimPrefix("file://") + return absoluteString + } +}