Skip to content

Commit bb1d611

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 842f22a commit bb1d611

File tree

2 files changed

+32
-15
lines changed

2 files changed

+32
-15
lines changed

Sources/swift-format/Frontend/Frontend.swift

Lines changed: 29 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,44 @@ 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+
if parallel {
133+
let allFilePaths = Array(FileIterator(paths: paths))
134+
DispatchQueue.concurrentPerform(iterations: allFilePaths.count) { index in
135+
let path = allFilePaths[index]
136+
openAndProcess(path)
136137
}
137-
138-
guard let configuration = configuration(
139-
atPath: lintFormatOptions.configurationPath, orInferredFromSwiftFileAtPath: path)
140-
else {
141-
// Already diagnosed in the called method.
142-
continue
138+
} else {
139+
for path in FileIterator(paths: paths) {
140+
openAndProcess(path)
143141
}
142+
}
143+
}
144144

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

164+
151165
/// Returns the configuration that applies to the given `.swift` source file, when an explicit
152166
/// configuration path is also perhaps provided.
153167
///

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 = false
5151

52+
@Flag(help: "Whether or not to run formatting or linting in parallel consuming all possible resources")
53+
var parallel: Bool = false
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)