1// 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 quiet bool 31} 32 33// NewStatusOutput returns a StatusOutput that represents the 34// current build status similarly to Ninja's built-in terminal 35// output. 36// 37// statusFormat takes nearly all the same options as NINJA_STATUS. 38// %c is currently unsupported. 39func NewStatusOutput(w Writer, statusFormat string, quietBuild bool) status.StatusOutput { 40 return &statusOutput{ 41 writer: w, 42 format: statusFormat, 43 44 start: time.Now(), 45 quiet: quietBuild, 46 } 47} 48 49func (s *statusOutput) Message(level status.MsgLevel, message string) { 50 if level >= status.ErrorLvl { 51 s.writer.Print(fmt.Sprintf("FAILED: %s", message)) 52 } else if level > status.StatusLvl { 53 s.writer.Print(fmt.Sprintf("%s%s", level.Prefix(), message)) 54 } else if level == status.StatusLvl { 55 s.writer.StatusLine(message) 56 } 57} 58 59func (s *statusOutput) StartAction(action *status.Action, counts status.Counts) { 60 if !s.writer.isSmartTerminal() { 61 return 62 } 63 64 str := action.Description 65 if str == "" { 66 str = action.Command 67 } 68 69 s.writer.StatusLine(s.progress(counts) + str) 70} 71 72func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Counts) { 73 str := result.Description 74 if str == "" { 75 str = result.Command 76 } 77 78 progress := s.progress(counts) + str 79 80 if result.Error != nil { 81 targets := strings.Join(result.Outputs, " ") 82 if s.quiet || result.Command == "" { 83 s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s", targets, result.Output)) 84 } else { 85 s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output)) 86 } 87 } else if result.Output != "" { 88 s.writer.StatusAndMessage(progress, result.Output) 89 } else { 90 s.writer.StatusLine(progress) 91 } 92} 93 94func (s *statusOutput) Flush() {} 95 96func (s *statusOutput) progress(counts status.Counts) string { 97 if s.format == "" { 98 return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions) 99 } 100 101 buf := &strings.Builder{} 102 for i := 0; i < len(s.format); i++ { 103 c := s.format[i] 104 if c != '%' { 105 buf.WriteByte(c) 106 continue 107 } 108 109 i = i + 1 110 if i == len(s.format) { 111 buf.WriteByte(c) 112 break 113 } 114 115 c = s.format[i] 116 switch c { 117 case '%': 118 buf.WriteByte(c) 119 case 's': 120 fmt.Fprintf(buf, "%d", counts.StartedActions) 121 case 't': 122 fmt.Fprintf(buf, "%d", counts.TotalActions) 123 case 'r': 124 fmt.Fprintf(buf, "%d", counts.RunningActions) 125 case 'u': 126 fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions) 127 case 'f': 128 fmt.Fprintf(buf, "%d", counts.FinishedActions) 129 case 'o': 130 fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds()) 131 case 'c': 132 // TODO: implement? 133 buf.WriteRune('?') 134 case 'p': 135 fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions) 136 case 'e': 137 fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds()) 138 default: 139 buf.WriteString("unknown placeholder '") 140 buf.WriteByte(c) 141 buf.WriteString("'") 142 } 143 } 144 return buf.String() 145} 146