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