blob: c8eb382fca01d5ef959feba935fc72eee724ca75 [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) {
Dan Willemsenf78a7342018-07-12 21:26:10 -070048 if level >= status.ErrorLvl {
49 s.writer.Print(fmt.Sprintf("FAILED: %s", message))
50 } else if level > status.StatusLvl {
Dan Willemsenb82471a2018-05-17 16:37:09 -070051 s.writer.Print(fmt.Sprintf("%s%s", level.Prefix(), message))
52 } else if level == status.StatusLvl {
53 s.writer.StatusLine(message)
54 }
55}
56
57func (s *statusOutput) StartAction(action *status.Action, counts status.Counts) {
58 if !s.writer.isSmartTerminal() {
59 return
60 }
61
62 str := action.Description
63 if str == "" {
64 str = action.Command
65 }
66
67 s.writer.StatusLine(s.progress(counts) + str)
68}
69
70func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
71 str := result.Description
72 if str == "" {
73 str = result.Command
74 }
75
76 progress := s.progress(counts) + str
77
78 if result.Error != nil {
79 hasCommand := ""
80 if result.Command != "" {
81 hasCommand = "\n"
82 }
83
84 s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s%s%s",
85 strings.Join(result.Outputs, " "), result.Command, hasCommand, result.Output))
86 } else if result.Output != "" {
87 s.writer.StatusAndMessage(progress, result.Output)
88 } else {
89 s.writer.StatusLine(progress)
90 }
91}
92
93func (s *statusOutput) Flush() {}
94
95func (s *statusOutput) progress(counts status.Counts) string {
96 if s.format == "" {
97 return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
98 }
99
100 buf := &strings.Builder{}
101 for i := 0; i < len(s.format); i++ {
102 c := s.format[i]
103 if c != '%' {
104 buf.WriteByte(c)
105 continue
106 }
107
108 i = i + 1
109 if i == len(s.format) {
110 buf.WriteByte(c)
111 break
112 }
113
114 c = s.format[i]
115 switch c {
116 case '%':
117 buf.WriteByte(c)
118 case 's':
119 fmt.Fprintf(buf, "%d", counts.StartedActions)
120 case 't':
121 fmt.Fprintf(buf, "%d", counts.TotalActions)
122 case 'r':
123 fmt.Fprintf(buf, "%d", counts.RunningActions)
124 case 'u':
125 fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
126 case 'f':
127 fmt.Fprintf(buf, "%d", counts.FinishedActions)
128 case 'o':
129 fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
130 case 'c':
131 // TODO: implement?
132 buf.WriteRune('?')
133 case 'p':
134 fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
135 case 'e':
136 fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
137 default:
138 buf.WriteString("unknown placeholder '")
139 buf.WriteByte(c)
140 buf.WriteString("'")
141 }
142 }
143 return buf.String()
144}