Skip to content

Commit 5786e26

Browse files
committed
Multithread processing source files
Previously every source file was formatted / linted one by one. On our codebase this took a full project format from ~17 minutes to ~5 minutes.
1 parent 634a112 commit 5786e26

File tree

2 files changed

+35
-15
lines changed

2 files changed

+35
-15
lines changed

Sources/swift-format/Frontend/Frontend.swift

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class Frontend {
9292
if paths.isEmpty {
9393
processStandardInput()
9494
} else {
95-
processPaths(paths)
95+
processPaths(paths, parallel: lintFormatOptions.parallel)
9696
}
9797
}
9898

@@ -124,30 +124,47 @@ class Frontend {
124124
}
125125

126126
/// Processes source content from a list of files and/or directories provided as paths.
127-
private func processPaths(_ paths: [String]) {
127+
private func processPaths(_ paths: [String], parallel: Bool) {
128128
precondition(
129129
!paths.isEmpty,
130130
"processPaths(_:) should only be called when paths is non-empty.")
131131

132-
for path in FileIterator(paths: paths) {
133-
guard let sourceFile = FileHandle(forReadingAtPath: path) else {
134-
diagnosticEngine.diagnose(Diagnostic.Message(.error, "Unable to open \(path)"))
135-
continue
132+
let lock = NSLock()
133+
if parallel {
134+
let allFilePaths = Array(FileIterator(paths: paths))
135+
DispatchQueue.concurrentPerform(iterations: allFilePaths.count) { index in
136+
let path = allFilePaths[index]
137+
openAndProcess(path, lock: lock)
136138
}
137-
138-
guard let configuration = configuration(
139-
atPath: lintFormatOptions.configurationPath, orInferredFromSwiftFileAtPath: path)
140-
else {
141-
// Already diagnosed in the called method.
142-
continue
139+
} else {
140+
for path in FileIterator(paths: paths) {
141+
openAndProcess(path, lock: nil)
143142
}
143+
}
144+
}
144145

145-
let fileToProcess = FileToProcess(
146-
fileHandle: sourceFile, path: path, configuration: configuration)
147-
processFile(fileToProcess)
146+
/// Read and process the given path, optionally synchronizing diagnostic output with the given lock.
147+
private func openAndProcess(_ path: String, lock: NSLock?) -> Void {
148+
guard let sourceFile = FileHandle(forReadingAtPath: path) else {
149+
lock?.lock()
150+
defer { lock?.unlock() }
151+
diagnosticEngine.diagnose(Diagnostic.Message(.error, "Unable to open \(path)"))
152+
return
148153
}
154+
155+
guard let configuration = configuration(
156+
atPath: lintFormatOptions.configurationPath, orInferredFromSwiftFileAtPath: path)
157+
else {
158+
// Already diagnosed in the called method.
159+
return
160+
}
161+
162+
let fileToProcess = FileToProcess(
163+
fileHandle: sourceFile, path: path, configuration: configuration)
164+
processFile(fileToProcess)
149165
}
150166

167+
151168
/// Returns the configuration that applies to the given `.swift` source file, when an explicit
152169
/// configuration path is also perhaps provided.
153170
///

Sources/swift-format/Subcommands/LintFormatOptions.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ struct LintFormatOptions: ParsableArguments {
4949
""")
5050
var ignoreUnparsableFiles: Bool
5151

52+
@Flag(help: "Whether or not to run formatting or linting in parallel consuming all possible resources")
53+
var parallel: Bool
54+
5255
/// The list of paths to Swift source files that should be formatted or linted.
5356
@Argument(help: "Zero or more input filenames.")
5457
var paths: [String]

0 commit comments

Comments
 (0)