From a4e43a77d85c32b69576fed7944a39b184a6fad4 Mon Sep 17 00:00:00 2001 From: Dan Willemsen Date: Sat, 6 May 2017 16:58:26 -0700 Subject: [PATCH] Improve multiproduct_kati output It now uses the same output style as ninja, overwriting status lines in smart terminals. Test: multiproduct_kati Test: multiproduct_kati | cat Change-Id: I8db5198ffdc5ebc5503241ac492379753d92978e --- cmd/multiproduct_kati/main.go | 100 ++++++++++++++++++++++++++++------ ui/build/context.go | 4 ++ 2 files changed, 87 insertions(+), 17 deletions(-) diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go index 3aa5a8767..97d4cfafc 100644 --- a/cmd/multiproduct_kati/main.go +++ b/cmd/multiproduct_kati/main.go @@ -56,6 +56,79 @@ type Product struct { config build.Config } +type Status struct { + cur int + total int + failed int + + ctx build.Context + haveBlankLine bool + smartTerminal bool + + lock sync.Mutex +} + +func NewStatus(ctx build.Context) *Status { + return &Status{ + ctx: ctx, + haveBlankLine: true, + smartTerminal: ctx.IsTerminal(), + } +} + +func (s *Status) SetTotal(total int) { + s.total = total +} + +func (s *Status) Fail(product string, err error) { + s.Finish(product) + + s.lock.Lock() + defer s.lock.Unlock() + + if s.smartTerminal && !s.haveBlankLine { + fmt.Fprintln(s.ctx.Stdout()) + s.haveBlankLine = true + } + + s.failed++ + fmt.Fprintln(s.ctx.Stderr(), "FAILED:", product) + s.ctx.Verboseln("FAILED:", product) + s.ctx.Println(err) +} + +func (s *Status) Finish(product string) { + s.lock.Lock() + defer s.lock.Unlock() + + s.cur++ + line := fmt.Sprintf("[%d/%d] %s", s.cur, s.total, product) + + if s.smartTerminal { + if max, ok := s.ctx.TermWidth(); ok { + if len(line) > max { + line = line[:max] + } + } + + fmt.Fprint(s.ctx.Stdout(), "\r", line, "\x1b[K") + s.haveBlankLine = false + } else { + s.ctx.Println(line) + } +} + +func (s *Status) Finished() int { + s.lock.Lock() + defer s.lock.Unlock() + + if !s.haveBlankLine { + fmt.Fprintln(s.ctx.Stdout()) + s.haveBlankLine = true + } + return s.failed +} + func main() { log := logger.New(os.Stderr) defer log.Cleanup() @@ -80,7 +153,7 @@ func main() { StdioInterface: build.StdioImpl{}, }} - failed := false + status := NewStatus(buildCtx) config := build.NewConfig(buildCtx) if *outDir == "" { @@ -94,7 +167,7 @@ func main() { if !*keep { defer func() { - if !failed { + if status.Finished() == 0 { os.RemoveAll(*outDir) } }() @@ -114,8 +187,9 @@ func main() { products := strings.Fields(vars["all_named_products"]) log.Verbose("Got product list:", products) + status.SetTotal(len(products)) + var wg sync.WaitGroup - errs := make(chan error, len(products)) productConfigs := make(chan Product, len(products)) // Run the product config for every product in parallel @@ -124,7 +198,7 @@ func main() { go func(product string) { defer wg.Done() defer logger.Recover(func(err error) { - errs <- fmt.Errorf("Error building %s: %v", product, err) + status.Fail(product, err) }) productOutDir := filepath.Join(config.OutDir(), product) @@ -171,7 +245,7 @@ func main() { for product := range productConfigs { func() { defer logger.Recover(func(err error) { - errs <- fmt.Errorf("Error building %s: %v", product.config.TargetProduct(), err) + status.Fail(product.config.TargetProduct(), err) }) buildWhat := 0 @@ -185,22 +259,14 @@ func main() { if !*keep { os.RemoveAll(product.config.OutDir()) } - log.Println("Finished running for", product.config.TargetProduct()) + status.Finish(product.config.TargetProduct()) }() } }() } - go func() { - wg2.Wait() - close(errs) - }() + wg2.Wait() - for err := range errs { - failed = true - log.Print(err) - } - - if failed { - log.Fatalln("Failed") + if count := status.Finished(); count > 0 { + log.Fatalln(count, "products failed") } } diff --git a/ui/build/context.go b/ui/build/context.go index f85bb6c0c..52a337d48 100644 --- a/ui/build/context.go +++ b/ui/build/context.go @@ -102,3 +102,7 @@ func (c ContextImpl) IsTerminal() bool { } return false } + +func (c ContextImpl) TermWidth() (int, bool) { + return termWidth(c.Stdout()) +}