Skip to content

Commit 539d779

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 539d779

File tree

2 files changed

+30
-9
lines changed

2 files changed

+30
-9
lines changed

Sources/swift-format/Frontend/Frontend.swift

Lines changed: 27 additions & 9 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,28 +124,46 @@ 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) {
132+
let concurrentQueue = DispatchQueue(
133+
label: "swift-format-queue", qos: .userInitiated, attributes: .concurrent)
134+
let group = DispatchGroup()
135+
let lock = NSLock()
136+
let openAndProcess: (String) -> Void = { path in
133137
guard let sourceFile = FileHandle(forReadingAtPath: path) else {
134-
diagnosticEngine.diagnose(Diagnostic.Message(.error, "Unable to open \(path)"))
135-
continue
138+
lock.lock()
139+
defer { lock.unlock() }
140+
self.diagnosticEngine.diagnose(Diagnostic.Message(.error, "Unable to open \(path)"))
141+
return
136142
}
137143

138-
guard let configuration = configuration(
139-
atPath: lintFormatOptions.configurationPath, orInferredFromSwiftFileAtPath: path)
144+
guard let configuration = self.configuration(
145+
atPath: self.lintFormatOptions.configurationPath, orInferredFromSwiftFileAtPath: path)
140146
else {
141147
// Already diagnosed in the called method.
142-
continue
148+
return
143149
}
144150

145151
let fileToProcess = FileToProcess(
146152
fileHandle: sourceFile, path: path, configuration: configuration)
147-
processFile(fileToProcess)
153+
self.processFile(fileToProcess)
148154
}
155+
156+
for path in FileIterator(paths: paths) {
157+
if parallel {
158+
concurrentQueue.async(group: group) {
159+
openAndProcess(path)
160+
}
161+
} else {
162+
openAndProcess(path)
163+
}
164+
}
165+
166+
group.wait()
149167
}
150168

151169
/// Returns the configuration that applies to the given `.swift` source file, when an explicit

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)