Run clang-format in parallel (#40419)
* Run clang-format in parallel * Apply PR feedback
Blue committed
May 5, 2026 at 11:24 UTC
198d7f8364fd0cf0b02cbe273fa0e91ac8820cf4
1 file changed
+114
-40
tools/FormatSource.ps1.in
+114
-40
@@ -150,29 +150,16 @@ if (-not (Test-Path -Type Leaf -Path $ClangFormat)) {
150
exit 1
151
}
152
153
-function Format-Directory {
153
+function Collect-FilesToFormat {
154
[CmdletBinding()]
155
param (
156
[Parameter(Mandatory = $True)]
157
[string]$Path,
158
- [Parameter(Mandatory = $True)]
159
- [string]$ClangFormat,
160
- [string]$RepoRoot = $null,
161
- [AllowEmptyString()][string]$FilePatterns = $null,
162
- [string[]]$ModifiedFiles = $null,
163
- [boolean]$Verify = $false
158
+ [string[]]$ModifiedFiles = $null
159
)
160
process {
166
- if (-not (Test-Path -Path $Path)) {
167
- Write-Host -ForegroundColor Red "Item not found: $Path"
168
- return $False
169
- }
170
- if ($null -eq $FilePatterns) {
171
- $FilePatterns = ""
172
- }
161
$Path = Resolve-Path $Path
174
- $Success = $True
175
- $FilesToFormat = @()
162
+ $files = [System.Collections.Generic.List[string]]::new()
163
if ((Get-Item -Path $Path) -is [System.IO.DirectoryInfo]) {
164
Get-ChildItem -Path $Path -File `
165
| Where-Object { $_.Name -match $FilePatterns } `
@@ -180,46 +167,133 @@ function Format-Directory {
167
$FilePath = "$Path\$($_.Name)"
168
if (($null -eq $ModifiedFiles) -or ($ModifiedFiles -contains $FilePath)) {
169
if (!($FilePath -match "Intermediate")) {
183
- $FilesToFormat += $FilePath
170
+ [void]$files.Add($FilePath)
171
}
172
}
173
}
174
Get-ChildItem -Path $Path -Directory `
175
| Where-Object { $_.Name -notmatch $IgnoreFolders } `
176
| ForEach-Object {
190
- $SubResult = (Format-Directory -Path "$Path\$($_.Name)" `
191
- -ClangFormat $ClangFormat `
192
- -RepoRoot $RepoRoot `
193
- -FilePatterns $FilePatterns `
194
- -ModifiedFiles $ModifiedFiles `
195
- -Verify $Verify)
196
- $Success = $SubResult -and $Success
177
+ $subFiles = @(Collect-FilesToFormat -Path "$Path\$($_.Name)" `
178
+ -ModifiedFiles $ModifiedFiles)
179
+ foreach ($f in $subFiles) {
180
+ [void]$files.Add($f)
181
+ }
182
}
183
}
184
else {
200
- $FilesToFormat += $Path
185
+ [void]$files.Add("$Path")
186
}
202
- $FilesToFormat | ForEach-Object {
203
- if ($Verify) {
204
- Write-Host "[clang-format] Checking formatting: $_"
205
- & $ClangFormat --style=file -Werror --dry-run $_
187
+ return @($files)
188
+ }
189
+}
190
+
191
+$allFiles = @(Collect-FilesToFormat -Path $Path -ModifiedFiles $ModifiedFiles)
192
+
193
+if ($allFiles.Count -eq 0) {
194
+ Write-Host -ForegroundColor Green "No files to format."
195
+ exit 0
196
+}
197
+
198
+$threadCount = [Environment]::ProcessorCount
199
+$totalFiles = $allFiles.Count
200
+Write-Host "[clang-format] Checking $totalFiles files using $threadCount threads..."
201
+
202
+# Create runspace pool for parallel execution (one thread per CPU core)
203
+$pool = [RunspaceFactory]::CreateRunspacePool(1, $threadCount)
204
+$pool.Open()
205
+
206
+$formatScript = {
207
+ param($clangFormatExe, $filePath, $verifyMode)
208
+ $pinfo = [System.Diagnostics.ProcessStartInfo]::new()
209
+ $pinfo.FileName = $clangFormatExe
210
+ if ($verifyMode) {
211
+ $pinfo.Arguments = "--style=file -Werror --dry-run `"$filePath`""
212
+ }
213
+ else {
214
+ $pinfo.Arguments = "--style=file -Werror -i `"$filePath`""
215
+ }
216
+ $pinfo.RedirectStandardError = $true # Not redirecting stdout to avoid a pipe deadlock.
217
+ $pinfo.UseShellExecute = $false
218
+
219
+ $proc = [System.Diagnostics.Process]::new()
220
+ $proc.StartInfo = $pinfo
221
+ [void]$proc.Start()
222
+ $stderr = $proc.StandardError.ReadToEnd()
223
+ $proc.WaitForExit()
224
+
225
+ [PSCustomObject]@{
226
+ File = $filePath
227
+ ExitCode = $proc.ExitCode
228
+ Output = $stderr.TrimEnd()
229
+ }
230
+}
231
+
232
+# Submit all jobs to the runspace pool
233
+$jobs = [System.Collections.ArrayList]::new()
234
+foreach ($file in $allFiles) {
235
+ $ps = [PowerShell]::Create()
236
+ $ps.RunspacePool = $pool
237
+ [void]$ps.AddScript($formatScript).AddArgument($ClangFormat).AddArgument($file).AddArgument($Verify)
238
+ $handle = $ps.BeginInvoke()
239
+ [void]$jobs.Add([PSCustomObject]@{
240
+ PowerShell = $ps
241
+ Handle = $handle
242
+ })
243
+}
244
+
245
+# Determine console width for progress display
246
+try { $lineWidth = [Math]::Max(([Console]::BufferWidth - 1), 40) } catch { $lineWidth = 120 }
247
+
248
+$Success = $true
249
+
250
+try {
251
+ while ($jobs.Count -gt 0) {
252
+ $finishedJobs = @($jobs | Where-Object { $_.Handle.IsCompleted })
253
+ foreach ($job in $finishedJobs) {
254
+ try {
255
+ $result = $job.PowerShell.EndInvoke($job.Handle)[0]
256
}
207
- else {
208
- Write-Host "[clang-format] Formatting $_"
209
- & $ClangFormat --style=file -Werror -i $_
257
+ catch {
258
+ $result = [PSCustomObject]@{ File = "unknown"; ExitCode = 1; Output = $_.Exception.Message }
259
}
211
- $Success = (0 -eq $LASTEXITCODE) -and $Success
260
+ $job.PowerShell.Dispose()
261
+ [void]$jobs.Remove($job)
262
+
263
+ if ($result.ExitCode -ne 0) {
264
+ $Success = $false
265
+ # Clear progress line and print error permanently
266
+ [Console]::Write("`r" + (" " * $lineWidth) + "`r")
267
+ Write-Host -ForegroundColor Red "[clang-format] $($result.File)"
268
+ if ($result.Output) {
269
+ Write-Host $result.Output
270
+ }
271
+ }
272
+
273
+ # Update progress line in-place with latest file
274
+ $completed = $totalFiles - $jobs.Count
275
+ $status = "[clang-format] ($completed/$totalFiles) $($result.File)"
276
+ if ($status.Length -gt $lineWidth) {
277
+ $status = $status.Substring(0, $lineWidth)
278
+ }
279
+ [Console]::Write("`r" + $status.PadRight($lineWidth))
280
}
213
- return $Success
281
+ Start-Sleep -Milliseconds 50
282
}
283
}
284
+finally {
285
+ # Clean up any remaining jobs and the pool
286
+ foreach ($job in $jobs) {
287
+ $job.PowerShell.Stop()
288
+ $job.PowerShell.Dispose()
289
+ }
290
217
-$Success = (Format-Directory -Path $Path `
218
- -ClangFormat $ClangFormat `
219
- -FilePatterns $FilePatterns `
220
- -ModifiedFiles $ModifiedFiles `
221
- -RepoRoot $RepoRoot `
222
- -Verify $Verify)
291
+ # Clear final progress line
292
+ [Console]::Write("`r" + (" " * $lineWidth) + "`r")
293
+
294
+ $pool.Close()
295
+ $pool.Dispose()
296
+}
297
298
if ($Success) {
299
Write-Host "Done."