Browse Source

Fix warnings in SwiftPM plugin (#1958)

Motivation:

SwiftPMs 'Path' was all but deprecated in Swift 6 resulting in numerous
warnings for the SwiftPM build plugin. It has largely been replaced by
URL.

Modifications:

- Add a wrapper type to the plugin which wraps Path or URL depending on
  the compiler version.

Result:

No build plugin warnings for Swift 6 compilers
George Barnett 1 year ago
parent
commit
d1aa3b8c44
1 changed files with 145 additions and 26 deletions
  1. 145 26
      Plugins/GRPCSwiftPlugin/plugin.swift

+ 145 - 26
Plugins/GRPCSwiftPlugin/plugin.swift

@@ -99,43 +99,42 @@ struct GRPCSwiftPlugin {
   ///   - tool: The tool method from the context.
   /// - Returns: The build commands configured based on the arguments.
   func createBuildCommands(
-    pluginWorkDirectory: PackagePlugin.Path,
+    pluginWorkDirectory: PathLike,
     sourceFiles: FileList,
     tool: (String) throws -> PackagePlugin.PluginContext.Tool
   ) throws -> [Command] {
-    guard
-      let configurationFilePath = sourceFiles.first(
-        where: {
-          $0.path.lastComponent == Self.configurationFileName
-        }
-      )?.path
-    else {
+    let maybeConfigFile = sourceFiles.map { PathLike($0) }.first {
+      $0.lastComponent == Self.configurationFileName
+    }
+
+    guard let configurationFilePath = maybeConfigFile else {
       throw PluginError.noConfigFound(Self.configurationFileName)
     }
 
-    let data = try Data(contentsOf: URL(fileURLWithPath: "\(configurationFilePath)"))
+    let data = try Data(contentsOf: URL(configurationFilePath))
     let configuration = try JSONDecoder().decode(Configuration.self, from: data)
 
     try self.validateConfiguration(configuration)
 
     let targetDirectory = configurationFilePath.removingLastComponent()
-    var importPaths: [Path] = [targetDirectory]
+    var importPaths: [PathLike] = [targetDirectory]
     if let configuredImportPaths = configuration.importPaths {
-      importPaths.append(contentsOf: configuredImportPaths.map { Path($0) })
+      importPaths.append(contentsOf: configuredImportPaths.map { PathLike($0) })
     }
 
     // We need to find the path of protoc and protoc-gen-grpc-swift
-    let protocPath: Path
+    let protocPath: PathLike
     if let configuredProtocPath = configuration.protocPath {
-      protocPath = Path(configuredProtocPath)
+      protocPath = PathLike(configuredProtocPath)
     } else if let environmentPath = ProcessInfo.processInfo.environment["PROTOC_PATH"] {
       // The user set the env variable, so let's take that
-      protocPath = Path(environmentPath)
+      protocPath = PathLike(environmentPath)
     } else {
       // The user didn't set anything so let's try see if SPM can find a binary for us
-      protocPath = try tool("protoc").path
+      protocPath = try PathLike(tool("protoc"))
     }
-    let protocGenGRPCSwiftPath = try tool("protoc-gen-grpc-swift").path
+
+    let protocGenGRPCSwiftPath = try PathLike(tool("protoc-gen-grpc-swift"))
 
     return configuration.invocations.map { invocation in
       self.invokeProtoc(
@@ -160,12 +159,12 @@ 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: Path,
+    directory: PathLike,
     invocation: Configuration.Invocation,
-    protocPath: Path,
-    protocGenGRPCSwiftPath: Path,
-    outputDirectory: Path,
-    importPaths: [Path]
+    protocPath: PathLike,
+    protocGenGRPCSwiftPath: PathLike,
+    outputDirectory: PathLike,
+    importPaths: [PathLike]
   ) -> Command {
     // Construct the `protoc` arguments.
     var protocArgs = [
@@ -202,12 +201,12 @@ struct GRPCSwiftPlugin {
       protocArgs.append("--grpc-swift_opt=_V2=\(v2)")
     }
 
-    var inputFiles = [Path]()
-    var outputFiles = [Path]()
+    var inputFiles = [PathLike]()
+    var outputFiles = [PathLike]()
 
     for var file in invocation.protoFiles {
       // Append the file to the protoc args so that it is used for generating
-      protocArgs.append("\(file)")
+      protocArgs.append(file)
       inputFiles.append(directory.appending(file))
 
       // The name of the output file is based on the name of the input file.
@@ -261,14 +260,128 @@ extension GRPCSwiftPlugin: BuildToolPlugin {
     guard let swiftTarget = target as? SwiftSourceModuleTarget else {
       throw PluginError.invalidTarget("\(type(of: target))")
     }
+
+    #if compiler(<6.0)
+    let workDirectory = PathLike(context.pluginWorkDirectory)
+    #else
+    let workDirectory = PathLike(context.pluginWorkDirectoryURL)
+    #endif
+
     return try self.createBuildCommands(
-      pluginWorkDirectory: context.pluginWorkDirectory,
+      pluginWorkDirectory: workDirectory,
       sourceFiles: swiftTarget.sourceFiles,
       tool: context.tool
     )
   }
 }
 
+// '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 {
+  #if compiler(<6.0)
+  typealias Value = Path
+  #else
+  typealias Value = URL
+  #endif
+
+  private(set) var value: Value
+
+  init(_ value: Value) {
+    self.value = value
+  }
+
+  init(_ path: String) {
+    #if compiler(<6.0)
+    self.value = Path(path)
+    #else
+    self.value = URL(fileURLWithPath: path)
+    #endif
+  }
+
+  init(_ element: FileList.Element) {
+    #if compiler(<6.0)
+    self.value = element.path
+    #else
+    self.value = element.url
+    #endif
+  }
+
+  init(_ element: PluginContext.Tool) {
+    #if compiler(<6.0)
+    self.value = element.path
+    #else
+    self.value = element.url
+    #endif
+  }
+
+  var description: String {
+    #if compiler(<6.0)
+    return String(describing: self.value)
+    #elseif canImport(Darwin)
+    return self.value.path()
+    #else
+    return self.value.path
+    #endif
+  }
+
+  var lastComponent: String {
+    #if compiler(<6.0)
+    return self.value.lastComponent
+    #else
+    return self.value.lastPathComponent
+    #endif
+  }
+
+  func removingLastComponent() -> Self {
+    var copy = self
+    #if compiler(<6.0)
+    copy.value = self.value.removingLastComponent()
+    #else
+    copy.value = self.value.deletingLastPathComponent()
+    #endif
+    return copy
+  }
+
+  func appending(_ path: String) -> Self {
+    var copy = self
+    #if compiler(<6.0)
+    copy.value = self.value.appending(path)
+    #else
+    copy.value = self.value.appendingPathComponent(path)
+    #endif
+    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) {
+    #if compiler(<6.0)
+    self = URL(fileURLWithPath: "\(pathLike.value)")
+    #else
+    self = pathLike.value
+    #endif
+  }
+}
+
 #if canImport(XcodeProjectPlugin)
 import XcodeProjectPlugin
 
@@ -277,8 +390,14 @@ extension GRPCSwiftPlugin: XcodeBuildToolPlugin {
     context: XcodePluginContext,
     target: XcodeTarget
   ) throws -> [Command] {
+    #if compiler(<6.0)
+    let workDirectory = PathLike(context.pluginWorkDirectory)
+    #else
+    let workDirectory = PathLike(URL(string: String(describing: context.pluginWorkDirectory))!)
+    #endif
+
     return try self.createBuildCommands(
-      pluginWorkDirectory: context.pluginWorkDirectory,
+      pluginWorkDirectory: workDirectory,
       sourceFiles: target.inputFiles,
       tool: context.tool
     )