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
diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go
index 3aa5a87..97d4cfa 100644
--- a/cmd/multiproduct_kati/main.go
+++ b/cmd/multiproduct_kati/main.go
@@ -56,6 +56,79 @@
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 @@
StdioInterface: build.StdioImpl{},
}}
- failed := false
+ status := NewStatus(buildCtx)
config := build.NewConfig(buildCtx)
if *outDir == "" {
@@ -94,7 +167,7 @@
if !*keep {
defer func() {
- if !failed {
+ if status.Finished() == 0 {
os.RemoveAll(*outDir)
}
}()
@@ -114,8 +187,9 @@
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 @@
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 @@
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 @@
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 f85bb6c..52a337d 100644
--- a/ui/build/context.go
+++ b/ui/build/context.go
@@ -102,3 +102,7 @@
}
return false
}
+
+func (c ContextImpl) TermWidth() (int, bool) {
+ return termWidth(c.Stdout())
+}