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 status 16 17import ( 18 "compress/gzip" 19 "errors" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "strings" 25 "sync" 26 "time" 27 28 "google.golang.org/protobuf/proto" 29 30 "android/soong/ui/logger" 31 soong_build_error_proto "android/soong/ui/status/build_error_proto" 32 soong_build_progress_proto "android/soong/ui/status/build_progress_proto" 33) 34 35type verboseLog struct { 36 w *gzip.Writer 37 lock *sync.Mutex 38 data chan []string 39 stop chan bool 40} 41 42func NewVerboseLog(log logger.Logger, filename string) StatusOutput { 43 if !strings.HasSuffix(filename, ".gz") { 44 filename += ".gz" 45 } 46 47 f, err := logger.CreateFileWithRotation(filename, 5) 48 if err != nil { 49 log.Println("Failed to create verbose log file:", err) 50 return nil 51 } 52 53 w := gzip.NewWriter(f) 54 55 l := &verboseLog{ 56 w: w, 57 lock: &sync.Mutex{}, 58 data: make(chan []string), 59 stop: make(chan bool), 60 } 61 l.startWriter() 62 return l 63} 64 65func (v *verboseLog) startWriter() { 66 go func() { 67 tick := time.Tick(time.Second) 68 for { 69 select { 70 case <-v.stop: 71 close(v.data) 72 v.w.Close() 73 return 74 case <-tick: 75 v.w.Flush() 76 case dataList := <-v.data: 77 for _, data := range dataList { 78 fmt.Fprint(v.w, data) 79 } 80 } 81 } 82 }() 83} 84 85func (v *verboseLog) stopWriter() { 86 v.stop <- true 87} 88 89func (v *verboseLog) queueWrite(s ...string) { 90 v.data <- s 91} 92 93func (v *verboseLog) StartAction(action *Action, counts Counts) {} 94 95func (v *verboseLog) FinishAction(result ActionResult, counts Counts) { 96 cmd := result.Command 97 if cmd == "" { 98 cmd = result.Description 99 } 100 101 v.queueWrite(fmt.Sprintf("[%d/%d] ", counts.FinishedActions, counts.TotalActions), cmd, "\n") 102 103 if result.Error != nil { 104 v.queueWrite("FAILED: ", strings.Join(result.Outputs, " "), "\n") 105 } 106 107 if result.Output != "" { 108 v.queueWrite(result.Output, "\n") 109 } 110} 111 112func (v *verboseLog) Flush() { 113 v.stopWriter() 114} 115 116func (v *verboseLog) Message(level MsgLevel, message string) { 117 v.queueWrite(level.Prefix(), message, "\n") 118} 119 120func (v *verboseLog) Write(p []byte) (int, error) { 121 v.queueWrite(string(p)) 122 return len(p), nil 123} 124 125type errorLog struct { 126 w io.WriteCloser 127 empty bool 128} 129 130func NewErrorLog(log logger.Logger, filename string) StatusOutput { 131 f, err := logger.CreateFileWithRotation(filename, 5) 132 if err != nil { 133 log.Println("Failed to create error log file:", err) 134 return nil 135 } 136 137 return &errorLog{ 138 w: f, 139 empty: true, 140 } 141} 142 143func (e *errorLog) StartAction(action *Action, counts Counts) {} 144 145func (e *errorLog) FinishAction(result ActionResult, counts Counts) { 146 if result.Error == nil { 147 return 148 } 149 150 if !e.empty { 151 fmt.Fprintf(e.w, "\n\n") 152 } 153 e.empty = false 154 155 fmt.Fprintf(e.w, "FAILED: %s\n", result.Description) 156 157 if len(result.Outputs) > 0 { 158 fmt.Fprintf(e.w, "Outputs: %s\n", strings.Join(result.Outputs, " ")) 159 } 160 161 fmt.Fprintf(e.w, "Error: %s\n", result.Error) 162 if result.Command != "" { 163 fmt.Fprintf(e.w, "Command: %s\n", result.Command) 164 } 165 fmt.Fprintf(e.w, "Output:\n%s\n", result.Output) 166} 167 168func (e *errorLog) Flush() { 169 e.w.Close() 170} 171 172func (e *errorLog) Message(level MsgLevel, message string) { 173 if level < ErrorLvl { 174 return 175 } 176 177 if !e.empty { 178 fmt.Fprintf(e.w, "\n\n") 179 } 180 e.empty = false 181 182 fmt.Fprintf(e.w, "error: %s\n", message) 183} 184 185func (e *errorLog) Write(p []byte) (int, error) { 186 fmt.Fprint(e.w, string(p)) 187 return len(p), nil 188} 189 190type errorProtoLog struct { 191 errorProto soong_build_error_proto.BuildError 192 filename string 193 log logger.Logger 194} 195 196func NewProtoErrorLog(log logger.Logger, filename string) StatusOutput { 197 os.Remove(filename) 198 return &errorProtoLog{ 199 errorProto: soong_build_error_proto.BuildError{}, 200 filename: filename, 201 log: log, 202 } 203} 204 205func (e *errorProtoLog) StartAction(action *Action, counts Counts) {} 206 207func (e *errorProtoLog) FinishAction(result ActionResult, counts Counts) { 208 if result.Error == nil { 209 return 210 } 211 212 e.errorProto.ActionErrors = append(e.errorProto.ActionErrors, &soong_build_error_proto.BuildActionError{ 213 Description: proto.String(result.Description), 214 Command: proto.String(result.Command), 215 Output: proto.String(result.Output), 216 Artifacts: result.Outputs, 217 Error: proto.String(result.Error.Error()), 218 }) 219 220 err := writeToFile(&e.errorProto, e.filename) 221 if err != nil { 222 e.log.Printf("Failed to write file %s: %v\n", e.filename, err) 223 } 224} 225 226func (e *errorProtoLog) Flush() { 227 //Not required. 228} 229 230func (e *errorProtoLog) Message(level MsgLevel, message string) { 231 if level > ErrorLvl { 232 e.errorProto.ErrorMessages = append(e.errorProto.ErrorMessages, message) 233 } 234} 235 236func (e *errorProtoLog) Write(p []byte) (int, error) { 237 return 0, errors.New("not supported") 238} 239 240type buildProgressLog struct { 241 filename string 242 log logger.Logger 243 failedActions uint64 244} 245 246func NewBuildProgressLog(log logger.Logger, filename string) StatusOutput { 247 return &buildProgressLog{ 248 filename: filename, 249 log: log, 250 failedActions: 0, 251 } 252} 253 254func (b *buildProgressLog) StartAction(action *Action, counts Counts) { 255 b.updateCounters(counts) 256} 257 258func (b *buildProgressLog) FinishAction(result ActionResult, counts Counts) { 259 if result.Error != nil { 260 b.failedActions++ 261 } 262 b.updateCounters(counts) 263} 264 265func (b *buildProgressLog) Flush() { 266 //Not required. 267} 268 269func (b *buildProgressLog) Message(level MsgLevel, message string) { 270 // Not required. 271} 272 273func (b *buildProgressLog) Write(p []byte) (int, error) { 274 return 0, errors.New("not supported") 275} 276 277func (b *buildProgressLog) updateCounters(counts Counts) { 278 err := writeToFile( 279 &soong_build_progress_proto.BuildProgress{ 280 CurrentActions: proto.Uint64(uint64(counts.RunningActions)), 281 FinishedActions: proto.Uint64(uint64(counts.FinishedActions)), 282 TotalActions: proto.Uint64(uint64(counts.TotalActions)), 283 FailedActions: proto.Uint64(b.failedActions), 284 }, 285 b.filename, 286 ) 287 if err != nil { 288 b.log.Printf("Failed to write file %s: %v\n", b.filename, err) 289 } 290} 291 292func writeToFile(pb proto.Message, outputPath string) (err error) { 293 data, err := proto.Marshal(pb) 294 if err != nil { 295 return err 296 } 297 298 tempPath := outputPath + ".tmp" 299 err = ioutil.WriteFile(tempPath, []byte(data), 0644) 300 if err != nil { 301 return err 302 } 303 304 err = os.Rename(tempPath, outputPath) 305 if err != nil { 306 return err 307 } 308 309 return nil 310} 311