/* * 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 GRPCCodeGen import GRPCProtobufCodeGen import SwiftProtobuf import SwiftProtobufPluginLibrary #if canImport(FoundationEssentials) import FoundationEssentials #else import Foundation #endif @main final class GenerateGRPC: SwiftProtobufPluginLibrary.CodeGenerator { var version: String? { Version.versionString } var projectURL: String { "https://github.com/grpc/grpc-swift" } var supportedFeatures: [Google_Protobuf_Compiler_CodeGeneratorResponse.Feature] { [.proto3Optional, .supportsEditions] } var supportedEditionRange: ClosedRange { Google_Protobuf_Edition.proto2 ... Google_Protobuf_Edition.edition2023 } // A count of generated files by desired name (actual name may differ to avoid collisions). private var generatedFileNames: [String: Int] = [:] func generate( files fileDescriptors: [FileDescriptor], parameter: any CodeGeneratorParameter, protoCompilerContext: any ProtoCompilerContext, generatorOutputs outputs: any GeneratorOutputs ) throws { let options = try GeneratorOptions(parameter: parameter) for descriptor in fileDescriptors { try self.generateV2Stubs(descriptor, options: options, outputs: outputs) } } private func generateV2Stubs( _ descriptor: FileDescriptor, options: GeneratorOptions, outputs: any GeneratorOutputs ) throws { let fileName = self.uniqueOutputFileName( fileDescriptor: descriptor, fileNamingOption: options.fileNaming ) let fileGenerator = ProtobufCodeGenerator(config: options.config) let contents = try fileGenerator.generateCode( fileDescriptor: descriptor, protoFileModuleMappings: options.protoToModuleMappings, extraModuleImports: options.extraModuleImports ) try outputs.add(fileName: fileName, contents: contents) } } extension GenerateGRPC { private func uniqueOutputFileName( fileDescriptor: FileDescriptor, fileNamingOption: FileNaming, component: String = "grpc", extension: String = "swift" ) -> String { let defaultName = outputFileName( component: component, fileDescriptor: fileDescriptor, fileNamingOption: fileNamingOption, extension: `extension` ) if let count = self.generatedFileNames[defaultName] { self.generatedFileNames[defaultName] = count + 1 return outputFileName( component: "\(count)." + component, fileDescriptor: fileDescriptor, fileNamingOption: fileNamingOption, extension: `extension` ) } else { self.generatedFileNames[defaultName] = 1 return defaultName } } private func outputFileName( component: String, fileDescriptor: FileDescriptor, fileNamingOption: FileNaming, extension: String ) -> String { let ext = "." + component + "." + `extension` let pathParts = splitPath(pathname: fileDescriptor.name) switch fileNamingOption { case .fullPath: return pathParts.dir + pathParts.base + ext case .pathToUnderscores: let dirWithUnderscores = pathParts.dir.replacing("/", with: "_") return dirWithUnderscores + pathParts.base + ext case .dropPath: return pathParts.base + ext } } } // from apple/swift-protobuf/Sources/protoc-gen-swift/StringUtils.swift private func splitPath(pathname: String) -> (dir: String, base: String, suffix: String) { var dir = "" var base = "" var suffix = "" for character in pathname { if character == "/" { dir += base + suffix + String(character) base = "" suffix = "" } else if character == "." { base += suffix suffix = String(character) } else { suffix += String(character) } } let validSuffix = suffix.isEmpty || suffix.first == "." if !validSuffix { base += suffix suffix = "" } return (dir: dir, base: base, suffix: suffix) }