-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathSwiftLintBuildToolPlugin.swift
More file actions
145 lines (131 loc) · 6.24 KB
/
SwiftLintBuildToolPlugin.swift
File metadata and controls
145 lines (131 loc) · 6.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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.pluginWorkDirectoryURL)
}
/// Collects the paths of the Swift files to be linted.
private func swiftFiles(target: SourceModuleTarget) -> [URL] {
target
.sourceFiles(withSuffix: "swift")
.map(\.url)
}
/// 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 = try target.directoryURL.resolvedWorkingDirectory(in: context.package.directoryURL)
return ["BUILD_WORKSPACE_DIRECTORY": "\(workingDirectory.filepath)"]
}
private func makeCommand(
executable: PluginContext.Tool,
swiftFiles: [URL],
environment: [String: String],
pluginWorkDirectory path: URL
) 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.appending(path: "Cache", directoryHint: .isDirectory)
try FileManager.default.createDirectory(atPath: cachePath.filepath, withIntermediateDirectories: true)
cacheArguments = ["--cache-path", "\(cachePath)"]
}
let outputPath = path.appending(path: "Output", directoryHint: .isDirectory)
try FileManager.default.createDirectory(atPath: outputPath.filepath, withIntermediateDirectories: true)
return [
.prebuildCommand(
displayName: "SwiftLint",
executable: executable.url,
arguments: arguments + cacheArguments + swiftFiles.map(\.filepath),
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.pluginWorkDirectoryURL)
}
/// Collects the paths of the Swift files to be linted.
private func swiftFiles(target: XcodeTarget) -> [URL] {
target
.inputFiles
.filter { $0.type == .source && $0.url.pathExtension == "swift" }
.map(\.url)
}
/// 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 = 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 = 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)
}
return ["BUILD_WORKSPACE_DIRECTORY": "\(workingDirectory)"]
}
}
#endif