Skip to content
Merged
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
128 changes: 31 additions & 97 deletions Plugins/GRPCSwiftPlugin/plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,42 +99,42 @@ struct GRPCSwiftPlugin {
/// - tool: The tool method from the context.
/// - Returns: The build commands configured based on the arguments.
func createBuildCommands(
pluginWorkDirectory: PathLike,
pluginWorkDirectory: URL,
sourceFiles: FileList,
tool: (String) throws -> PackagePlugin.PluginContext.Tool
) throws -> [Command] {
let maybeConfigFile = sourceFiles.map { PathLike($0) }.first {
$0.lastComponent == Self.configurationFileName
let maybeConfigFile = sourceFiles.map { $0.url }.first {
$0.lastPathComponent == Self.configurationFileName
}

guard let configurationFilePath = maybeConfigFile else {
throw PluginError.noConfigFound(Self.configurationFileName)
}

let data = try Data(contentsOf: URL(configurationFilePath))
let data = try Data(contentsOf: configurationFilePath)
let configuration = try JSONDecoder().decode(Configuration.self, from: data)

try self.validateConfiguration(configuration)

let targetDirectory = configurationFilePath.removingLastComponent()
var importPaths: [PathLike] = [targetDirectory]
let targetDirectory = configurationFilePath.deletingLastPathComponent()
var importPaths: [URL] = [targetDirectory]
if let configuredImportPaths = configuration.importPaths {
importPaths.append(contentsOf: configuredImportPaths.map { PathLike($0) })
importPaths.append(contentsOf: configuredImportPaths.map { URL(fileURLWithPath: $0) })
}

// We need to find the path of protoc and protoc-gen-grpc-swift
let protocPath: PathLike
let protocPath: URL
if let configuredProtocPath = configuration.protocPath {
protocPath = PathLike(configuredProtocPath)
protocPath = URL(fileURLWithPath: configuredProtocPath)
} else if let environmentPath = ProcessInfo.processInfo.environment["PROTOC_PATH"] {
// The user set the env variable, so let's take that
protocPath = PathLike(environmentPath)
protocPath = URL(fileURLWithPath: environmentPath)
} else {
// The user didn't set anything so let's try see if SPM can find a binary for us
protocPath = try PathLike(tool("protoc"))
protocPath = try tool("protoc").url
}

let protocGenGRPCSwiftPath = try PathLike(tool("protoc-gen-grpc-swift"))
let protocGenGRPCSwiftPath = try tool("protoc-gen-grpc-swift").url

return configuration.invocations.map { invocation in
self.invokeProtoc(
Expand All @@ -159,22 +159,22 @@ struct GRPCSwiftPlugin {
/// - importPaths: List of paths to pass with "-I <path>" to `protoc`.
/// - Returns: The build command configured based on the arguments
private func invokeProtoc(
directory: PathLike,
directory: URL,
invocation: Configuration.Invocation,
protocPath: PathLike,
protocGenGRPCSwiftPath: PathLike,
outputDirectory: PathLike,
importPaths: [PathLike]
protocPath: URL,
protocGenGRPCSwiftPath: URL,
outputDirectory: URL,
importPaths: [URL]
) -> Command {
// Construct the `protoc` arguments.
var protocArgs = [
"--plugin=protoc-gen-grpc-swift=\(protocGenGRPCSwiftPath)",
"--grpc-swift_out=\(outputDirectory)",
"--plugin=protoc-gen-grpc-swift=\(protocGenGRPCSwiftPath.path())",
"--grpc-swift_out=\(outputDirectory.path())",
]

importPaths.forEach { path in
protocArgs.append("-I")
protocArgs.append("\(path)")
protocArgs.append(path.path())
}

if let visibility = invocation.visibility {
Expand All @@ -201,20 +201,20 @@ struct GRPCSwiftPlugin {
protocArgs.append("--grpc-swift_opt=_V2=\(v2)")
}

var inputFiles = [PathLike]()
var outputFiles = [PathLike]()
var inputFiles = [URL]()
var outputFiles = [URL]()

for var file in invocation.protoFiles {
// Append the file to the protoc args so that it is used for generating
protocArgs.append(file)
inputFiles.append(directory.appending(file))
inputFiles.append(directory.appending(path: file, directoryHint: .notDirectory))

// The name of the output file is based on the name of the input file.
// We validated in the beginning that every file has the suffix of .proto
// This means we can just drop the last 5 elements and append the new suffix
file.removeLast(5)
file.append("grpc.swift")
let protobufOutputPath = outputDirectory.appending(file)
let protobufOutputPath = outputDirectory.appending(path: file, directoryHint: .notDirectory)

// Add the outputPath as an output file
outputFiles.append(protobufOutputPath)
Expand All @@ -223,15 +223,18 @@ struct GRPCSwiftPlugin {
// Remove .swift extension and add .reflection extension
file.removeLast(5)
file.append("reflection")
let reflectionOutputPath = outputDirectory.appending(file)
let reflectionOutputPath = outputDirectory.appending(
path: file,
directoryHint: .notDirectory
)
outputFiles.append(reflectionOutputPath)
}
}

// Construct the command. Specifying the input and output paths lets the build
// system know when to invoke the command. The output paths are passed on to
// the rule engine in the build system.
return Command.buildCommand(
return .buildCommand(
displayName: "Generating gRPC Swift files from proto files",
executable: protocPath,
arguments: protocArgs,
Expand Down Expand Up @@ -261,7 +264,7 @@ extension GRPCSwiftPlugin: BuildToolPlugin {
throw PluginError.invalidTarget("\(type(of: target))")
}

let workDirectory = PathLike(context.pluginWorkDirectory)
let workDirectory = context.pluginWorkDirectoryURL

return try self.createBuildCommands(
pluginWorkDirectory: workDirectory,
Expand All @@ -271,75 +274,6 @@ extension GRPCSwiftPlugin: BuildToolPlugin {
}
}

// 'Path' was effectively deprecated in Swift 6 in favour of URL. ('Effectively' because all
// methods, properties, and conformances have been deprecated but the type hasn't.) This type wraps
// either depending on the compiler version.
struct PathLike: CustomStringConvertible {
typealias Value = Path

private(set) var value: Value

init(_ value: Value) {
self.value = value
}

init(_ path: String) {
self.value = Path(path)
}

init(_ element: FileList.Element) {
self.value = element.path
}

init(_ element: PluginContext.Tool) {
self.value = element.path
}

var description: String {
return String(describing: self.value)
}

var lastComponent: String {
return self.value.lastComponent
}

func removingLastComponent() -> Self {
var copy = self
copy.value = self.value.removingLastComponent()
return copy
}

func appending(_ path: String) -> Self {
var copy = self
copy.value = self.value.appending(path)
return copy
}
}

extension Command {
static func buildCommand(
displayName: String?,
executable: PathLike,
arguments: [String],
inputFiles: [PathLike],
outputFiles: [PathLike]
) -> PackagePlugin.Command {
return Self.buildCommand(
displayName: displayName,
executable: executable.value,
arguments: arguments,
inputFiles: inputFiles.map { $0.value },
outputFiles: outputFiles.map { $0.value }
)
}
}

extension URL {
init(_ pathLike: PathLike) {
self = URL(fileURLWithPath: "\(pathLike.value)")
}
}

#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin

Expand All @@ -348,7 +282,7 @@ extension GRPCSwiftPlugin: XcodeBuildToolPlugin {
context: XcodePluginContext,
target: XcodeTarget
) throws -> [Command] {
let workDirectory = PathLike(context.pluginWorkDirectory)
let workDirectory = context.pluginWorkDirectoryURL

return try self.createBuildCommands(
pluginWorkDirectory: workDirectory,
Expand Down