From be805755ff4fd7afbc83d73b319742e9c5bce0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danny=20M=C3=B6sch?= Date: Tue, 13 Jan 2026 00:03:56 +0100 Subject: [PATCH] Update to Swift Tools Version 6.1 --- Package.swift | 4 +- .../Path+Helpers.swift | 34 -------------- .../SwiftLintBuildToolPlugin.swift | 46 +++++++++---------- .../SwiftLintBuildToolPluginError.swift | 7 +-- .../URL+Helpers.swift | 37 +++++++++++++++ .../CommandContext.swift | 45 +++++++++--------- .../SwiftLintCommandPlugin.swift | 16 +++++-- 7 files changed, 101 insertions(+), 88 deletions(-) delete mode 100644 Plugins/SwiftLintBuildToolPlugin/Path+Helpers.swift create mode 100644 Plugins/SwiftLintBuildToolPlugin/URL+Helpers.swift diff --git a/Package.swift b/Package.swift index d348d23a0a..26aa9c39c7 100644 --- a/Package.swift +++ b/Package.swift @@ -1,8 +1,10 @@ -// swift-tools-version:5.9 +// swift-tools-version:6.1 import CompilerPluginSupport import PackageDescription let swiftFeatures: [SwiftSetting] = [ + .swiftLanguageMode(.v5), + .enableUpcomingFeature("ConciseMagicFile"), .enableUpcomingFeature("ExistentialAny"), .enableUpcomingFeature("ForwardTrailingClosures"), diff --git a/Plugins/SwiftLintBuildToolPlugin/Path+Helpers.swift b/Plugins/SwiftLintBuildToolPlugin/Path+Helpers.swift deleted file mode 100644 index 8de05a75fd..0000000000 --- a/Plugins/SwiftLintBuildToolPlugin/Path+Helpers.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation -import PackagePlugin - -extension Path { - var directoryContainsConfigFile: Bool { - FileManager.default.fileExists(atPath: "\(self)/.swiftlint.yml") - } - - var depth: Int { - URL(fileURLWithPath: "\(self)").pathComponents.count - } - - func isDescendant(of path: Path) -> Bool { - "\(self)".hasPrefix("\(path)") - } - - func resolveWorkingDirectory(in directory: Path) throws -> Path { - guard "\(self)".hasPrefix("\(directory)") else { - throw SwiftLintBuildToolPluginError.pathNotInDirectory(path: self, directory: directory) - } - - let path: Path? = sequence(first: self) { path in - let path: Path = path.removingLastComponent() - guard "\(path)".hasPrefix("\(directory)") else { - return nil - } - return path - } - .reversed() - .first(where: \.directoryContainsConfigFile) - - return path ?? directory - } -} diff --git a/Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPlugin.swift b/Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPlugin.swift index 5e70a37c32..4447cc79a6 100644 --- a/Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPlugin.swift +++ b/Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPlugin.swift @@ -10,14 +10,14 @@ struct SwiftLintBuildToolPlugin: BuildToolPlugin { try makeCommand(executable: context.tool(named: "swiftlint"), swiftFiles: (target as? SourceModuleTarget).flatMap(swiftFiles) ?? [], environment: environment(context: context, target: target), - pluginWorkDirectory: context.pluginWorkDirectory) + pluginWorkDirectory: context.pluginWorkDirectoryURL) } /// Collects the paths of the Swift files to be linted. - private func swiftFiles(target: SourceModuleTarget) -> [Path] { + private func swiftFiles(target: SourceModuleTarget) -> [URL] { target .sourceFiles(withSuffix: "swift") - .map(\.path) + .map(\.url) } /// Creates an environment dictionary containing a value for the `BUILD_WORKSPACE_DIRECTORY` key. @@ -35,15 +35,15 @@ struct SwiftLintBuildToolPlugin: BuildToolPlugin { context: PluginContext, target: Target ) throws -> [String: String] { - let workingDirectory: Path = try target.directory.resolveWorkingDirectory(in: context.package.directory) - return ["BUILD_WORKSPACE_DIRECTORY": "\(workingDirectory)"] + let workingDirectory = try target.directoryURL.resolvedWorkingDirectory(in: context.package.directoryURL) + return ["BUILD_WORKSPACE_DIRECTORY": "\(workingDirectory.filepath)"] } private func makeCommand( executable: PluginContext.Tool, - swiftFiles: [Path], + swiftFiles: [URL], environment: [String: String], - pluginWorkDirectory path: Path + pluginWorkDirectory path: URL ) throws -> [Command] { // Don't lint anything if there are no Swift source files in this target guard !swiftFiles.isEmpty else { @@ -66,17 +66,17 @@ struct SwiftLintBuildToolPlugin: BuildToolPlugin { if ProcessInfo.processInfo.environment["CI_XCODE_CLOUD"] == "TRUE" { cacheArguments = ["--no-cache"] } else { - let cachePath: Path = path.appending("Cache") - try FileManager.default.createDirectory(atPath: cachePath.string, withIntermediateDirectories: true) + let cachePath = path.appending(path: "Cache", directoryHint: .isDirectory) + try FileManager.default.createDirectory(atPath: cachePath.filepath, withIntermediateDirectories: true) cacheArguments = ["--cache-path", "\(cachePath)"] } - let outputPath: Path = path.appending("Output") - try FileManager.default.createDirectory(atPath: outputPath.string, withIntermediateDirectories: true) + let outputPath = path.appending(path: "Output", directoryHint: .isDirectory) + try FileManager.default.createDirectory(atPath: outputPath.filepath, withIntermediateDirectories: true) return [ .prebuildCommand( displayName: "SwiftLint", - executable: executable.path, - arguments: arguments + cacheArguments + swiftFiles.map(\.string), + executable: executable.url, + arguments: arguments + cacheArguments + swiftFiles.map(\.filepath), environment: environment, outputFilesDirectory: outputPath), ] @@ -96,15 +96,15 @@ extension SwiftLintBuildToolPlugin: XcodeBuildToolPlugin { try makeCommand(executable: context.tool(named: "swiftlint"), swiftFiles: swiftFiles(target: target), environment: environment(context: context, target: target), - pluginWorkDirectory: context.pluginWorkDirectory) + pluginWorkDirectory: context.pluginWorkDirectoryURL) } /// Collects the paths of the Swift files to be linted. - private func swiftFiles(target: XcodeTarget) -> [Path] { + private func swiftFiles(target: XcodeTarget) -> [URL] { target .inputFiles - .filter { $0.type == .source && $0.path.extension == "swift" } - .map(\.path) + .filter { $0.type == .source && $0.url.pathExtension == "swift" } + .map(\.url) } /// Creates an environment dictionary containing a value for the `BUILD_WORKSPACE_DIRECTORY` key. @@ -122,17 +122,17 @@ extension SwiftLintBuildToolPlugin: XcodeBuildToolPlugin { context: XcodePluginContext, target: XcodeTarget ) throws -> [String: String] { - let projectDirectory: Path = context.xcodeProject.directory - let swiftFiles: [Path] = swiftFiles(target: target) - let swiftFilesNotInProjectDirectory: [Path] = swiftFiles.filter { !$0.isDescendant(of: projectDirectory) } + let projectDirectory = context.xcodeProject.directoryURL + let swiftFiles = swiftFiles(target: target) + let swiftFilesNotInProjectDirectory = swiftFiles.filter { !$0.isDescendant(of: projectDirectory) } guard swiftFilesNotInProjectDirectory.isEmpty else { throw SwiftLintBuildToolPluginError.swiftFilesNotInProjectDirectory(projectDirectory) } - let directories: [Path] = try swiftFiles.map { try $0.resolveWorkingDirectory(in: projectDirectory) } - let workingDirectory: Path = directories.min { $0.depth < $1.depth } ?? projectDirectory - let swiftFilesNotInWorkingDirectory: [Path] = swiftFiles.filter { !$0.isDescendant(of: workingDirectory) } + let directories = try swiftFiles.map { try $0.resolvedWorkingDirectory(in: projectDirectory) } + let workingDirectory = directories.min { $0.depth < $1.depth } ?? projectDirectory + let swiftFilesNotInWorkingDirectory = swiftFiles.filter { !$0.isDescendant(of: workingDirectory) } guard swiftFilesNotInWorkingDirectory.isEmpty else { throw SwiftLintBuildToolPluginError.swiftFilesNotInWorkingDirectory(workingDirectory) diff --git a/Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPluginError.swift b/Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPluginError.swift index 16f839502f..969e37ae95 100644 --- a/Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPluginError.swift +++ b/Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPluginError.swift @@ -1,9 +1,10 @@ +import Foundation import PackagePlugin enum SwiftLintBuildToolPluginError: Error, CustomStringConvertible { - case pathNotInDirectory(path: Path, directory: Path) - case swiftFilesNotInProjectDirectory(Path) - case swiftFilesNotInWorkingDirectory(Path) + case pathNotInDirectory(path: URL, directory: URL) + case swiftFilesNotInProjectDirectory(URL) + case swiftFilesNotInWorkingDirectory(URL) var description: String { switch self { diff --git a/Plugins/SwiftLintBuildToolPlugin/URL+Helpers.swift b/Plugins/SwiftLintBuildToolPlugin/URL+Helpers.swift new file mode 100644 index 0000000000..dd38d3c16c --- /dev/null +++ b/Plugins/SwiftLintBuildToolPlugin/URL+Helpers.swift @@ -0,0 +1,37 @@ +import Foundation +import PackagePlugin + +extension URL { + var filepath: String { + withUnsafeFileSystemRepresentation { String(cString: $0!) } + } + + var depth: Int { + pathComponents.count + } + + func isDescendant(of other: URL) -> Bool { + path().hasPrefix(other.path()) + } + + func resolvedWorkingDirectory(in directory: URL) throws -> URL { + guard isDescendant(of: directory) else { + throw SwiftLintBuildToolPluginError.pathNotInDirectory(path: self, directory: directory) + } + + let path: URL? = sequence(first: self) { path in + let path = path.deletingLastPathComponent() + guard path.isDescendant(of: directory) else { + return nil + } + return path + } + .reversed() + .first { + let file = $0.appending(path: ".swiftlint.yml", directoryHint: .notDirectory).filepath + return FileManager.default.fileExists(atPath: file) + } + + return path ?? directory + } +} diff --git a/Plugins/SwiftLintCommandPlugin/CommandContext.swift b/Plugins/SwiftLintCommandPlugin/CommandContext.swift index 3f3f6daef0..8087f6bd90 100644 --- a/Plugins/SwiftLintCommandPlugin/CommandContext.swift +++ b/Plugins/SwiftLintCommandPlugin/CommandContext.swift @@ -1,32 +1,33 @@ +import Foundation import PackagePlugin protocol CommandContext { - var tool: String { get throws } + var tool: URL { get throws } - var cacheDirectory: String { get } + var cacheDirectory: URL { get } - var workingDirectory: String { get } + var workingDirectory: URL { get } var unitName: String { get } var subUnitName: String { get } - func targets(named names: [String]) throws -> [(paths: [String], name: String)] + func targets(named names: [String]) throws -> [(paths: [URL], name: String)] } extension PluginContext: CommandContext { - var tool: String { + var tool: URL { get throws { - try tool(named: "swiftlint").path.string + try tool(named: "swiftlint").url } } - var cacheDirectory: String { - pluginWorkDirectory.string + var cacheDirectory: URL { + pluginWorkDirectoryURL } - var workingDirectory: String { - package.directory.string + var workingDirectory: URL { + package.directoryURL } var unitName: String { @@ -37,17 +38,17 @@ extension PluginContext: CommandContext { "module" } - func targets(named names: [String]) throws -> [(paths: [String], name: String)] { + func targets(named names: [String]) throws -> [(paths: [URL], name: String)] { let targets = names.isEmpty ? package.targets : try package.targets(named: names) - return targets.compactMap { target in + return targets.compactMap { target -> (paths: [URL], name: String)? in guard let target = target.sourceModule else { Diagnostics.warning("Target '\(target.name)' is not a source module; skipping it") return nil } // Packages have a 1-to-1 mapping between targets and directories. - return (paths: [target.directory.string], name: target.name) + return (paths: [target.directoryURL], name: target.name) } } } @@ -57,18 +58,18 @@ extension PluginContext: CommandContext { import XcodeProjectPlugin extension XcodePluginContext: CommandContext { - var tool: String { + var tool: URL { get throws { - try tool(named: "swiftlint").path.string + try tool(named: "swiftlint").url } } - var cacheDirectory: String { - pluginWorkDirectory.string + var cacheDirectory: URL { + pluginWorkDirectoryURL } - var workingDirectory: String { - xcodeProject.directory.string + var workingDirectory: URL { + xcodeProject.directoryURL } var unitName: String { @@ -79,13 +80,13 @@ extension XcodePluginContext: CommandContext { "target" } - func targets(named names: [String]) -> [(paths: [String], name: String)] { + func targets(named names: [String]) -> [(paths: [URL], name: String)] { if names.isEmpty { - return [(paths: [xcodeProject.directory.string], name: xcodeProject.displayName)] + return [(paths: [xcodeProject.directoryURL], name: xcodeProject.displayName)] } return xcodeProject.targets .filter { names.contains($0.displayName) } - .map { (paths: $0.inputFiles.map(\.path.string).filter { $0.hasSuffix(".swift") }, name: $0.displayName) } + .map { (paths: $0.inputFiles.map(\.url).filter { $0.pathExtension == "swift" }, name: $0.displayName) } } } diff --git a/Plugins/SwiftLintCommandPlugin/SwiftLintCommandPlugin.swift b/Plugins/SwiftLintCommandPlugin/SwiftLintCommandPlugin.swift index fb208c4d07..d61f85e298 100644 --- a/Plugins/SwiftLintCommandPlugin/SwiftLintCommandPlugin.swift +++ b/Plugins/SwiftLintCommandPlugin/SwiftLintCommandPlugin.swift @@ -1,6 +1,12 @@ import Foundation import PackagePlugin +private extension URL { + var filepath: String { + withUnsafeFileSystemRepresentation { String(cString: $0!) } + } +} + private let commandsNotExpectingPaths: Set = [ "docs", "generate-docs", @@ -61,19 +67,19 @@ extension SwiftLintCommandPlugin { } } - private func lintFiles(in paths: [String] = ["."], + private func lintFiles(in paths: [URL] = [URL.currentDirectory()], for targetName: String? = nil, with context: some CommandContext, arguments: [String]) throws { let process = Process() - process.currentDirectoryURL = URL(fileURLWithPath: context.workingDirectory) - process.executableURL = URL(fileURLWithPath: try context.tool) + process.currentDirectoryURL = context.workingDirectory + process.executableURL = try context.tool process.arguments = arguments if commandsWithoutCachPathOption.isDisjoint(with: arguments) { - process.arguments! += ["--cache-path", context.cacheDirectory] + process.arguments! += ["--cache-path", context.cacheDirectory.filepath] } if commandsNotExpectingPaths.isDisjoint(with: arguments) { - process.arguments! += paths + process.arguments! += paths.map(\.filepath) } try process.run()