Skip to content

Commit 00141ed

Browse files
Jerry XieJerry Xie
authored andcommitted
perf: use batch brew install with native parallelism for CLI and GUI packages
Replaces the custom parallel worker pool with a simpler batch install approach that leverages Homebrew's native parallel download capability (introduced in Homebrew/brew#20975). Changes: - CLI packages: brew install pkg1 pkg2 pkg3... (single batch command) - GUI packages (casks): brew install --cask app1 app2 app3... (single batch command) - Removes 92 lines of complex worker pool and individual cask install code - Dry-run now shows batch commands instead of individual lines This aligns with modern Homebrew's HOMEBREW_DOWNLOAD_CONCURRENCY=auto (default since Nov 2025) which handles parallel downloads automatically. Note: Casks that require password prompts may still prompt, but Homebrew handles them gracefully in batch mode.
1 parent 1578184 commit 00141ed

File tree

1 file changed

+47
-92
lines changed

1 file changed

+47
-92
lines changed

internal/brew/brew.go

Lines changed: 47 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import (
1616
"github.com/openbootdotdev/openboot/internal/ui"
1717
)
1818

19-
const maxWorkers = 1
20-
2119
type OutdatedPackage struct {
2220
Name string
2321
Current string
@@ -208,11 +206,11 @@ func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) (installedForm
208206

209207
if dryRun {
210208
ui.Info("Would install packages:")
211-
for _, p := range cliPkgs {
212-
fmt.Printf(" brew install %s\n", p)
209+
if len(cliPkgs) > 0 {
210+
fmt.Printf(" brew install %s\n", strings.Join(cliPkgs, " "))
213211
}
214-
for _, p := range caskPkgs {
215-
fmt.Printf(" brew install --cask %s\n", p)
212+
if len(caskPkgs) > 0 {
213+
fmt.Printf(" brew install --cask %s\n", strings.Join(caskPkgs, " "))
216214
}
217215
return nil, nil, nil
218216
}
@@ -261,40 +259,62 @@ func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) (installedForm
261259
var allFailed []failedJob
262260

263261
if len(newCli) > 0 {
264-
failed := runParallelInstallWithProgress(newCli, progress)
265-
failedSet := make(map[string]bool, len(failed))
266-
for _, f := range failed {
267-
failedSet[f.name] = true
268-
}
269-
for _, p := range newCli {
270-
if !failedSet[p] {
271-
installedFormulae = append(installedFormulae, p)
262+
progress.SetCurrent(fmt.Sprintf("Installing %d CLI packages...", len(newCli)))
263+
ui.Info(fmt.Sprintf("Installing %d CLI packages with native brew parallelism...", len(newCli)))
264+
265+
args := append([]string{"install"}, newCli...)
266+
cmd := brewInstallCmd(args...)
267+
output, err := cmd.CombinedOutput()
268+
outputStr := string(output)
269+
270+
for _, pkg := range newCli {
271+
progress.IncrementWithStatus(true)
272+
if err == nil || !strings.Contains(outputStr, pkg) {
273+
installedFormulae = append(installedFormulae, pkg)
274+
} else {
275+
errMsg := parseBrewError(outputStr)
276+
if errMsg == "" {
277+
errMsg = "install failed"
278+
}
279+
allFailed = append(allFailed, failedJob{
280+
installJob: installJob{name: pkg, isCask: false},
281+
errMsg: errMsg,
282+
})
272283
}
273284
}
274-
allFailed = append(allFailed, failed...)
275285
}
276286

277287
if len(newCask) > 0 {
288+
progress.SetCurrent(fmt.Sprintf("Installing %d GUI apps...", len(newCask)))
289+
ui.Info(fmt.Sprintf("Installing %d GUI apps with native brew parallelism...", len(newCask)))
290+
291+
args := append([]string{"install", "--cask"}, newCask...)
292+
cmd := brewInstallCmd(args...)
293+
output, err := cmd.CombinedOutput()
294+
outputStr := string(output)
295+
278296
for _, pkg := range newCask {
279-
progress.SetCurrent(pkg)
280-
progress.PrintLine(" Installing %s...", pkg)
281-
start := time.Now()
282-
errMsg := installCaskWithProgress(pkg, progress)
283-
elapsed := time.Since(start)
284-
progress.IncrementWithStatus(errMsg == "")
285-
duration := ui.FormatDuration(elapsed)
286-
if errMsg == "" {
287-
progress.PrintLine(" %s %s", ui.Green("✔ "+pkg), ui.Cyan("("+duration+")"))
288-
installedCasks = append(installedCasks, pkg)
297+
progress.IncrementWithStatus(true)
298+
// Resolve the cask name to handle any aliases
299+
resolvedName := ResolveFormulaName(pkg)
300+
if err == nil || !strings.Contains(outputStr, pkg) {
301+
installedCasks = append(installedCasks, resolvedName)
302+
progress.PrintLine(" %s %s", ui.Green("✔ "+pkg), ui.Cyan("(installed)"))
289303
} else {
290-
progress.PrintLine(" %s %s", ui.Red("✗ "+pkg+" ("+errMsg+")"), ui.Cyan("("+duration+")"))
304+
errMsg := parseBrewError(outputStr)
305+
if errMsg == "" {
306+
errMsg = "install failed"
307+
}
308+
progress.PrintLine(" %s %s", ui.Red("✗ "+pkg+" ("+errMsg+")"), ui.Cyan("(failed)"))
291309
allFailed = append(allFailed, failedJob{
292-
installJob: installJob{name: pkg, isCask: true},
310+
installJob: installJob{name: resolvedName, isCask: true},
293311
errMsg: errMsg,
294312
})
295313
}
296314
}
297315
}
316+
}
317+
}
298318

299319
progress.Finish()
300320

@@ -366,71 +386,6 @@ type failedJob struct {
366386
errMsg string
367387
}
368388

369-
func runParallelInstallWithProgress(pkgs []string, progress *ui.StickyProgress) []failedJob {
370-
if len(pkgs) == 0 {
371-
return nil
372-
}
373-
374-
jobs := make([]installJob, 0, len(pkgs))
375-
for _, pkg := range pkgs {
376-
jobs = append(jobs, installJob{name: pkg, isCask: false})
377-
}
378-
379-
jobChan := make(chan installJob, len(jobs))
380-
results := make(chan installResult, len(jobs))
381-
382-
var wg sync.WaitGroup
383-
workers := maxWorkers
384-
if len(jobs) < workers {
385-
workers = len(jobs)
386-
}
387-
388-
for i := 0; i < workers; i++ {
389-
wg.Add(1)
390-
go func() {
391-
defer wg.Done()
392-
for job := range jobChan {
393-
progress.SetCurrent(job.name)
394-
start := time.Now()
395-
errMsg := installFormulaWithError(job.name)
396-
elapsed := time.Since(start)
397-
progress.IncrementWithStatus(errMsg == "")
398-
duration := ui.FormatDuration(elapsed)
399-
if errMsg == "" {
400-
progress.PrintLine(" %s %s", ui.Green("✔ "+job.name), ui.Cyan("("+duration+")"))
401-
} else {
402-
progress.PrintLine(" %s %s", ui.Red("✗ "+job.name+" ("+errMsg+")"), ui.Cyan("("+duration+")"))
403-
}
404-
results <- installResult{name: job.name, failed: errMsg != "", isCask: job.isCask, errMsg: errMsg}
405-
}
406-
}()
407-
}
408-
409-
go func() {
410-
for _, job := range jobs {
411-
jobChan <- job
412-
}
413-
close(jobChan)
414-
}()
415-
416-
go func() {
417-
wg.Wait()
418-
close(results)
419-
}()
420-
421-
var failed []failedJob
422-
for result := range results {
423-
if result.failed {
424-
failed = append(failed, failedJob{
425-
installJob: installJob{name: result.name, isCask: result.isCask},
426-
errMsg: result.errMsg,
427-
})
428-
}
429-
}
430-
431-
return failed
432-
}
433-
434389
func installCaskWithProgress(pkg string, progress *ui.StickyProgress) string {
435390
progress.PauseForInteractive()
436391

0 commit comments

Comments
 (0)