Skip to content

Commit 28a4700

Browse files
Jerry XieJerry Xie
authored andcommitted
perf: pipe brew output directly instead of capturing and parsing
Simplifies the install process by piping brew's native output directly to stdout/stderr instead of capturing it and building custom progress. Changes: - CLI packages: brew install output shown directly - GUI apps: brew install --cask output shown directly with TTY for passwords - Removed complex output parsing and custom progress display - Progress bar still tracks completion count but doesn't intercept brew output - 91 lines removed, much simpler and more maintainable Users now see Homebrew's native progress indicators which are more accurate and familiar.
1 parent 1578184 commit 28a4700

File tree

1 file changed

+64
-91
lines changed

1 file changed

+64
-91
lines changed

internal/brew/brew.go

Lines changed: 64 additions & 91 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,36 +259,53 @@ 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+
ui.Info(fmt.Sprintf("Installing %d CLI packages...", len(newCli)))
263+
264+
args := append([]string{"install"}, newCli...)
265+
cmd := brewInstallCmd(args...)
266+
cmd.Stdout = os.Stdout
267+
cmd.Stderr = os.Stderr
268+
err := cmd.Run()
269+
270+
// Track as completed - we rely on brew's exit code for errors
271+
for _, pkg := range newCli {
272+
progress.IncrementWithStatus(err == nil)
273+
if err == nil {
274+
installedFormulae = append(installedFormulae, pkg)
275+
} else {
276+
allFailed = append(allFailed, failedJob{
277+
installJob: installJob{name: pkg, isCask: false},
278+
errMsg: "install failed",
279+
})
272280
}
273281
}
274-
allFailed = append(allFailed, failed...)
275282
}
276283

277284
if len(newCask) > 0 {
285+
ui.Info(fmt.Sprintf("Installing %d GUI apps...", len(newCask)))
286+
287+
args := append([]string{"install", "--cask"}, newCask...)
288+
cmd := brewInstallCmd(args...)
289+
cmd.Stdout = os.Stdout
290+
cmd.Stderr = os.Stderr
291+
// Open TTY for password prompts
292+
tty, opened := system.OpenTTY()
293+
if opened {
294+
cmd.Stdin = tty
295+
}
296+
err := cmd.Run()
297+
if opened {
298+
tty.Close()
299+
}
300+
278301
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+")"))
302+
progress.IncrementWithStatus(err == nil)
303+
if err == nil {
288304
installedCasks = append(installedCasks, pkg)
289305
} else {
290-
progress.PrintLine(" %s %s", ui.Red("✗ "+pkg+" ("+errMsg+")"), ui.Cyan("("+duration+")"))
291306
allFailed = append(allFailed, failedJob{
292307
installJob: installJob{name: pkg, isCask: true},
293-
errMsg: errMsg,
308+
errMsg: "install failed",
294309
})
295310
}
296311
}
@@ -366,71 +381,6 @@ type failedJob struct {
366381
errMsg string
367382
}
368383

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-
434384
func installCaskWithProgress(pkg string, progress *ui.StickyProgress) string {
435385
progress.PauseForInteractive()
436386

@@ -857,3 +807,26 @@ func PreInstallChecks(packageCount int) error {
857807

858808
return nil
859809
}
810+
811+
// ResolveFormulaName resolves a formula alias to its canonical name.
812+
// This handles cases like "postgresql" → "postgresql@18" or "kubectl" → "kubernetes-cli".
813+
// Returns the original name if resolution fails.
814+
func ResolveFormulaName(name string) string {
815+
cmd := exec.Command("brew", "info", "--json", name)
816+
output, err := cmd.Output()
817+
if err != nil {
818+
return name
819+
}
820+
821+
var result []struct {
822+
Name string `json:"name"`
823+
}
824+
if err := json.Unmarshal(output, &result); err != nil {
825+
return name
826+
}
827+
828+
if len(result) > 0 && result[0].Name != "" {
829+
return result[0].Name
830+
}
831+
return name
832+
}

0 commit comments

Comments
 (0)