mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
146 lines
6.2 KiB
Swift
146 lines
6.2 KiB
Swift
import Foundation
|
|
import PackagePlugin
|
|
|
|
@main
|
|
struct SwiftLintBuildToolPlugin: BuildToolPlugin {
|
|
func createBuildCommands(
|
|
context: PluginContext,
|
|
target: Target
|
|
) throws -> [Command] {
|
|
try makeCommand(executable: context.tool(named: "swiftlint"),
|
|
swiftFiles: (target as? SourceModuleTarget).flatMap(swiftFiles) ?? [],
|
|
environment: environment(context: context, target: target),
|
|
pluginWorkDirectory: context.pluginWorkDirectory)
|
|
}
|
|
|
|
/// Collects the paths of the Swift files to be linted.
|
|
private func swiftFiles(target: SourceModuleTarget) -> [Path] {
|
|
target
|
|
.sourceFiles(withSuffix: "swift")
|
|
.map(\.path)
|
|
}
|
|
|
|
/// Creates an environment dictionary containing a value for the `BUILD_WORKSPACE_DIRECTORY` key.
|
|
///
|
|
/// This method locates the topmost `.swiftlint.yml` config file within the package directory for this target
|
|
/// and sets the config file's containing directory as the `BUILD_WORKSPACE_DIRECTORY` value. The package
|
|
/// directory is used if a config file is not found.
|
|
///
|
|
/// The `BUILD_WORKSPACE_DIRECTORY` environment variable controls SwiftLint's working directory.
|
|
///
|
|
/// Reference: [https://github.com/realm/SwiftLint/blob/0.50.3/Source/swiftlint/Commands/SwiftLint.swift#L7](
|
|
/// https://github.com/realm/SwiftLint/blob/0.50.3/Source/swiftlint/Commands/SwiftLint.swift#L7
|
|
/// )
|
|
private func environment(
|
|
context: PluginContext,
|
|
target: Target
|
|
) throws -> [String: String] {
|
|
let workingDirectory: Path = try target.directory.resolveWorkingDirectory(in: context.package.directory)
|
|
return ["BUILD_WORKSPACE_DIRECTORY": "\(workingDirectory)"]
|
|
}
|
|
|
|
private func makeCommand(
|
|
executable: PluginContext.Tool,
|
|
swiftFiles: [Path],
|
|
environment: [String: String],
|
|
pluginWorkDirectory path: Path
|
|
) throws -> [Command] {
|
|
// Don't lint anything if there are no Swift source files in this target
|
|
guard !swiftFiles.isEmpty else {
|
|
return []
|
|
}
|
|
// Outputs the environment to the build log for reference.
|
|
#if DEBUG
|
|
print("Environment:", environment)
|
|
#endif
|
|
let arguments: [String] = [
|
|
"lint",
|
|
"--quiet",
|
|
// We always pass all of the Swift source files in the target to the tool,
|
|
// so we need to ensure that any exclusion rules in the configuration are
|
|
// respected.
|
|
"--force-exclude",
|
|
]
|
|
// Determine whether we need to enable cache or not (for Xcode Cloud we don't)
|
|
let cacheArguments: [String]
|
|
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)
|
|
cacheArguments = ["--cache-path", "\(cachePath)"]
|
|
}
|
|
let outputPath: Path = path.appending("Output")
|
|
try FileManager.default.createDirectory(atPath: outputPath.string, withIntermediateDirectories: true)
|
|
return [
|
|
.prebuildCommand(
|
|
displayName: "SwiftLint",
|
|
executable: executable.path,
|
|
arguments: arguments + cacheArguments + swiftFiles.map(\.string),
|
|
environment: environment,
|
|
outputFilesDirectory: outputPath),
|
|
]
|
|
}
|
|
}
|
|
|
|
#if canImport(XcodeProjectPlugin)
|
|
|
|
import XcodeProjectPlugin
|
|
|
|
// swiftlint:disable:next no_grouping_extension
|
|
extension SwiftLintBuildToolPlugin: XcodeBuildToolPlugin {
|
|
func createBuildCommands(
|
|
context: XcodePluginContext,
|
|
target: XcodeTarget
|
|
) throws -> [Command] {
|
|
try makeCommand(executable: context.tool(named: "swiftlint"),
|
|
swiftFiles: swiftFiles(target: target),
|
|
environment: environment(context: context, target: target),
|
|
pluginWorkDirectory: context.pluginWorkDirectory)
|
|
}
|
|
|
|
/// Collects the paths of the Swift files to be linted.
|
|
private func swiftFiles(target: XcodeTarget) -> [Path] {
|
|
target
|
|
.inputFiles
|
|
.filter { $0.type == .source && $0.path.extension == "swift" }
|
|
.map(\.path)
|
|
}
|
|
|
|
/// Creates an environment dictionary containing a value for the `BUILD_WORKSPACE_DIRECTORY` key.
|
|
///
|
|
/// This method locates the topmost `.swiftlint.yml` config file within the project directory for this target's
|
|
/// Swift source files and sets the config file's containing directory as the `BUILD_WORKSPACE_DIRECTORY` value.
|
|
/// The project directory is used if a config file is not found.
|
|
///
|
|
/// The `BUILD_WORKSPACE_DIRECTORY` environment variable controls SwiftLint's working directory.
|
|
///
|
|
/// Reference: [https://github.com/realm/SwiftLint/blob/0.50.3/Source/swiftlint/Commands/SwiftLint.swift#L7](
|
|
/// https://github.com/realm/SwiftLint/blob/0.50.3/Source/swiftlint/Commands/SwiftLint.swift#L7
|
|
/// )
|
|
private func environment(
|
|
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) }
|
|
|
|
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) }
|
|
|
|
guard swiftFilesNotInWorkingDirectory.isEmpty else {
|
|
throw SwiftLintBuildToolPluginError.swiftFilesNotInWorkingDirectory(workingDirectory)
|
|
}
|
|
|
|
return ["BUILD_WORKSPACE_DIRECTORY": "\(workingDirectory)"]
|
|
}
|
|
}
|
|
|
|
#endif
|