blob: 5719456f14d560a2959578d2a2c2d8dbba930672 [file] [log] [blame]
Dan Willemsenb82471a2018-05-17 16:37:09 -07001// Copyright 2018 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package terminal
16
17import (
18 "fmt"
19 "strings"
20 "time"
21
22 "android/soong/ui/status"
23)
24
25type statusOutput struct {
26 writer Writer
27 format string
28
29 start time.Time
30}
31
32// NewStatusOutput returns a StatusOutput that represents the
33// current build status similarly to Ninja's built-in terminal
34// output.
35//
36// statusFormat takes nearly all the same options as NINJA_STATUS.
37// %c is currently unsupported.
38func NewStatusOutput(w Writer, statusFormat string) status.StatusOutput {
39 return &statusOutput{
40 writer: w,
41 format: statusFormat,
42
43 start: time.Now(),
44 }
45}
46
47func (s *statusOutput) Message(level status.MsgLevel, message string) {
48 if level > status.StatusLvl {
49 s.writer.Print(fmt.Sprintf("%s%s", level.Prefix(), message))
50 } else if level == status.StatusLvl {
51 s.writer.StatusLine(message)
52 }
53}
54
55func (s *statusOutput) StartAction(action *status.Action, counts status.Counts) {
56 if !s.writer.isSmartTerminal() {
57 return
58 }
59
60 str := action.Description
61 if str == "" {
62 str = action.Command
63 }
64
65 s.writer.StatusLine(s.progress(counts) + str)
66}
67
68func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
69 str := result.Description
70 if str == "" {
71 str = result.Command
72 }
73
74 progress := s.progress(counts) + str
75
76 if result.Error != nil {
77 hasCommand := ""
78 if result.Command != "" {
79 hasCommand = "\n"
80 }
81
82 s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s%s%s",
83 strings.Join(result.Outputs, " "), result.Command, hasCommand, result.Output))
84 } else if result.Output != "" {
85 s.writer.StatusAndMessage(progress, result.Output)
86 } else {
87 s.writer.StatusLine(progress)
88 }
89}
90
91func (s *statusOutput) Flush() {}
92
93func (s *statusOutput) progress(counts status.Counts) string {
94 if s.format == "" {
95 return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
96 }
97
98 buf := &strings.Builder{}
99 for i := 0; i < len(s.format); i++ {
100 c := s.format[i]
101 if c != '%' {
102 buf.WriteByte(c)
103 continue
104 }
105
106 i = i + 1
107 if i == len(s.format) {
108 buf.WriteByte(c)
109 break
110 }
111
112 c = s.format[i]
113 switch c {
114 case '%':
115 buf.WriteByte(c)
116 case 's':
117 fmt.Fprintf(buf, "%d", counts.StartedActions)
118 case 't':
119 fmt.Fprintf(buf, "%d", counts.TotalActions)
120 case 'r':
121 fmt.Fprintf(buf, "%d", counts.RunningActions)
122 case 'u':
123 fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
124 case 'f':
125 fmt.Fprintf(buf, "%d", counts.FinishedActions)
126 case 'o':
127 fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
128 case 'c':
129 // TODO: implement?
130 buf.WriteRune('?')
131 case 'p':
132 fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
133 case 'e':
134 fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
135 default:
136 buf.WriteString("unknown placeholder '")
137 buf.WriteByte(c)
138 buf.WriteString("'")
139 }
140 }
141 return buf.String()
142}