blob: 539102390f693d351e9303f6719f9a812e9463cb [file] [log] [blame]
Colin Crossce525352019-06-08 18:58:00 -07001// Copyright 2019 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 formatter struct {
26 format string
27 quiet bool
Jeongik Cha0ba68e42023-11-30 09:03:38 +090028 smart bool
Colin Crossce525352019-06-08 18:58:00 -070029 start time.Time
30}
31
32// newFormatter returns a formatter for formatting output to
33// the terminal in a format similar to Ninja.
34// format takes nearly all the same options as NINJA_STATUS.
35// %c is currently unsupported.
Jeongik Cha0ba68e42023-11-30 09:03:38 +090036func newFormatter(format string, quiet bool, smart bool) formatter {
Colin Crossce525352019-06-08 18:58:00 -070037 return formatter{
38 format: format,
39 quiet: quiet,
Jeongik Cha0ba68e42023-11-30 09:03:38 +090040 smart: smart,
Colin Crossce525352019-06-08 18:58:00 -070041 start: time.Now(),
42 }
43}
44
45func (s formatter) message(level status.MsgLevel, message string) string {
46 if level >= status.ErrorLvl {
47 return fmt.Sprintf("FAILED: %s", message)
48 } else if level > status.StatusLvl {
49 return fmt.Sprintf("%s%s", level.Prefix(), message)
50 } else if level == status.StatusLvl {
51 return message
52 }
53 return ""
54}
55
Jeongik Cha3622b342023-11-27 11:00:52 +090056func remainingTimeString(t time.Time) string {
57 now := time.Now()
58 if t.After(now) {
59 return t.Sub(now).Round(time.Duration(time.Second)).String()
60 }
61 return time.Duration(0).Round(time.Duration(time.Second)).String()
62}
Colin Crossce525352019-06-08 18:58:00 -070063func (s formatter) progress(counts status.Counts) string {
64 if s.format == "" {
Jeongik Cha3622b342023-11-27 11:00:52 +090065 output := fmt.Sprintf("[%3d%% %d/%d", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
Jeongik Cha0ba68e42023-11-30 09:03:38 +090066 // Not to break parsing logic in the build bot
67 // TODO(b/313981966): make buildbot more flexible for output format
68 if s.smart && !counts.EstimatedTime.IsZero() {
Jeongik Cha3622b342023-11-27 11:00:52 +090069 output += fmt.Sprintf(" %s remaining", remainingTimeString(counts.EstimatedTime))
70 }
71 output += "] "
72 return output
Colin Crossce525352019-06-08 18:58:00 -070073 }
74
75 buf := &strings.Builder{}
76 for i := 0; i < len(s.format); i++ {
77 c := s.format[i]
78 if c != '%' {
79 buf.WriteByte(c)
80 continue
81 }
82
83 i = i + 1
84 if i == len(s.format) {
85 buf.WriteByte(c)
86 break
87 }
88
89 c = s.format[i]
90 switch c {
91 case '%':
92 buf.WriteByte(c)
93 case 's':
94 fmt.Fprintf(buf, "%d", counts.StartedActions)
95 case 't':
96 fmt.Fprintf(buf, "%d", counts.TotalActions)
97 case 'r':
98 fmt.Fprintf(buf, "%d", counts.RunningActions)
99 case 'u':
100 fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
101 case 'f':
102 fmt.Fprintf(buf, "%d", counts.FinishedActions)
103 case 'o':
104 fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
105 case 'c':
106 // TODO: implement?
107 buf.WriteRune('?')
108 case 'p':
109 fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
110 case 'e':
111 fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
Jeongik Cha3622b342023-11-27 11:00:52 +0900112 case 'l':
113 if counts.EstimatedTime.IsZero() {
114 // No esitimated data
115 buf.WriteRune('?')
116 } else {
117 fmt.Fprintf(buf, "%s", remainingTimeString(counts.EstimatedTime))
118 }
Colin Crossce525352019-06-08 18:58:00 -0700119 default:
120 buf.WriteString("unknown placeholder '")
121 buf.WriteByte(c)
122 buf.WriteString("'")
123 }
124 }
125 return buf.String()
126}
127
128func (s formatter) result(result status.ActionResult) string {
129 var ret string
130 if result.Error != nil {
131 targets := strings.Join(result.Outputs, " ")
132 if s.quiet || result.Command == "" {
133 ret = fmt.Sprintf("FAILED: %s\n%s", targets, result.Output)
134 } else {
135 ret = fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output)
136 }
137 } else if result.Output != "" {
138 ret = result.Output
139 }
140
141 if len(ret) > 0 && ret[len(ret)-1] != '\n' {
142 ret += "\n"
143 }
144
145 return ret
146}