Skip to content
Draft
Show file tree
Hide file tree
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
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -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"),
Expand Down
34 changes: 0 additions & 34 deletions Plugins/SwiftLintBuildToolPlugin/Path+Helpers.swift

This file was deleted.

46 changes: 23 additions & 23 deletions Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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),
]
Expand All @@ -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.
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
37 changes: 37 additions & 0 deletions Plugins/SwiftLintBuildToolPlugin/URL+Helpers.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
45 changes: 23 additions & 22 deletions Plugins/SwiftLintCommandPlugin/CommandContext.swift
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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)
}
}
}
Expand All @@ -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 {
Expand All @@ -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) }
}
}

Expand Down
16 changes: 11 additions & 5 deletions Plugins/SwiftLintCommandPlugin/SwiftLintCommandPlugin.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import Foundation
import PackagePlugin

private extension URL {
var filepath: String {
withUnsafeFileSystemRepresentation { String(cString: $0!) }
}
}

private let commandsNotExpectingPaths: Set<String> = [
"docs",
"generate-docs",
Expand Down Expand Up @@ -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()
Expand Down
Loading