• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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	"bufio"
19	"fmt"
20	"io"
21	"os"
22	"regexp"
23	"strings"
24	"syscall"
25	"time"
26
27	"google.golang.org/protobuf/proto"
28
29	"android/soong/ui/logger"
30	"android/soong/ui/status/ninja_frontend"
31)
32
33// NewNinjaReader reads the protobuf frontend format from ninja and translates it
34// into calls on the ToolStatus API.
35func NewNinjaReader(ctx logger.Logger, status ToolStatus, fifo string) *NinjaReader {
36	os.Remove(fifo)
37
38	if err := syscall.Mkfifo(fifo, 0666); err != nil {
39		ctx.Fatalf("Failed to mkfifo(%q): %v", fifo, err)
40	}
41
42	n := &NinjaReader{
43		status: status,
44		fifo:   fifo,
45		done:   make(chan bool),
46		cancel: make(chan bool),
47	}
48
49	go n.run()
50
51	return n
52}
53
54type NinjaReader struct {
55	status ToolStatus
56	fifo   string
57	done   chan bool
58	cancel chan bool
59}
60
61const NINJA_READER_CLOSE_TIMEOUT = 5 * time.Second
62
63// Close waits for NinjaReader to finish reading from the fifo, or 5 seconds.
64func (n *NinjaReader) Close() {
65	// Signal the goroutine to stop if it is blocking opening the fifo.
66	close(n.cancel)
67
68	timeoutCh := time.After(NINJA_READER_CLOSE_TIMEOUT)
69
70	select {
71	case <-n.done:
72		// Nothing
73	case <-timeoutCh:
74		n.status.Error(fmt.Sprintf("ninja fifo didn't finish after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
75	}
76
77	return
78}
79
80func (n *NinjaReader) run() {
81	defer close(n.done)
82
83	// Opening the fifo can block forever if ninja never opens the write end, do it in a goroutine so this
84	// method can exit on cancel.
85	fileCh := make(chan *os.File)
86	go func() {
87		f, err := os.Open(n.fifo)
88		if err != nil {
89			n.status.Error(fmt.Sprintf("Failed to open fifo: %v", err))
90			close(fileCh)
91			return
92		}
93		fileCh <- f
94	}()
95
96	var f *os.File
97
98	select {
99	case f = <-fileCh:
100		// Nothing
101	case <-n.cancel:
102		return
103	}
104
105	defer f.Close()
106
107	r := bufio.NewReader(f)
108
109	running := map[uint32]*Action{}
110
111	for {
112		size, err := readVarInt(r)
113		if err != nil {
114			if err != io.EOF {
115				n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
116			}
117			return
118		}
119
120		buf := make([]byte, size)
121		_, err = io.ReadFull(r, buf)
122		if err != nil {
123			if err == io.EOF {
124				n.status.Print(fmt.Sprintf("Missing message of size %d from ninja\n", size))
125			} else {
126				n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
127			}
128			return
129		}
130
131		msg := &ninja_frontend.Status{}
132		err = proto.Unmarshal(buf, msg)
133		if err != nil {
134			n.status.Print(fmt.Sprintf("Error reading message from ninja: %v", err))
135			continue
136		}
137
138		// Ignore msg.BuildStarted
139		if msg.TotalEdges != nil {
140			n.status.SetTotalActions(int(msg.TotalEdges.GetTotalEdges()))
141		}
142		if msg.EdgeStarted != nil {
143			action := &Action{
144				Description: msg.EdgeStarted.GetDesc(),
145				Outputs:     msg.EdgeStarted.Outputs,
146				Inputs:      msg.EdgeStarted.Inputs,
147				Command:     msg.EdgeStarted.GetCommand(),
148			}
149			n.status.StartAction(action)
150			running[msg.EdgeStarted.GetId()] = action
151		}
152		if msg.EdgeFinished != nil {
153			if started, ok := running[msg.EdgeFinished.GetId()]; ok {
154				delete(running, msg.EdgeFinished.GetId())
155
156				var err error
157				exitCode := int(msg.EdgeFinished.GetStatus())
158				if exitCode != 0 {
159					err = fmt.Errorf("exited with code: %d", exitCode)
160				}
161
162				outputWithErrorHint := errorHintGenerator.GetOutputWithErrorHint(msg.EdgeFinished.GetOutput(), exitCode)
163				n.status.FinishAction(ActionResult{
164					Action: started,
165					Output: outputWithErrorHint,
166					Error:  err,
167					Stats: ActionResultStats{
168						UserTime:                   msg.EdgeFinished.GetUserTime(),
169						SystemTime:                 msg.EdgeFinished.GetSystemTime(),
170						MaxRssKB:                   msg.EdgeFinished.GetMaxRssKb(),
171						MinorPageFaults:            msg.EdgeFinished.GetMinorPageFaults(),
172						MajorPageFaults:            msg.EdgeFinished.GetMajorPageFaults(),
173						IOInputKB:                  msg.EdgeFinished.GetIoInputKb(),
174						IOOutputKB:                 msg.EdgeFinished.GetIoOutputKb(),
175						VoluntaryContextSwitches:   msg.EdgeFinished.GetVoluntaryContextSwitches(),
176						InvoluntaryContextSwitches: msg.EdgeFinished.GetInvoluntaryContextSwitches(),
177					},
178				})
179			}
180		}
181		if msg.Message != nil {
182			message := "ninja: " + msg.Message.GetMessage()
183			switch msg.Message.GetLevel() {
184			case ninja_frontend.Status_Message_INFO:
185				n.status.Status(message)
186			case ninja_frontend.Status_Message_WARNING:
187				n.status.Print("warning: " + message)
188			case ninja_frontend.Status_Message_ERROR:
189				n.status.Error(message)
190			case ninja_frontend.Status_Message_DEBUG:
191				n.status.Verbose(message)
192			default:
193				n.status.Print(message)
194			}
195		}
196		if msg.BuildFinished != nil {
197			n.status.Finish()
198		}
199	}
200}
201
202func readVarInt(r *bufio.Reader) (int, error) {
203	ret := 0
204	shift := uint(0)
205
206	for {
207		b, err := r.ReadByte()
208		if err != nil {
209			return 0, err
210		}
211
212		ret += int(b&0x7f) << (shift * 7)
213		if b&0x80 == 0 {
214			break
215		}
216		shift += 1
217		if shift > 4 {
218			return 0, fmt.Errorf("Expected varint32 length-delimited message")
219		}
220	}
221
222	return ret, nil
223}
224
225// key is pattern in stdout/stderr
226// value is error hint
227var allErrorHints = map[string]string{
228	"Read-only file system": `\nWrite to a read-only file system detected. Possible fixes include
2291. Generate file directly to out/ which is ReadWrite, #recommend solution
2302. BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST := <my/path/1> <my/path/2> #discouraged, subset of source tree will be RW
2313. BUILD_BROKEN_SRC_DIR_IS_WRITABLE := true #highly discouraged, entire source tree will be RW
232`,
233}
234var errorHintGenerator = *newErrorHintGenerator(allErrorHints)
235
236type ErrorHintGenerator struct {
237	allErrorHints                map[string]string
238	allErrorHintPatternsCompiled *regexp.Regexp
239}
240
241func newErrorHintGenerator(allErrorHints map[string]string) *ErrorHintGenerator {
242	var allErrorHintPatterns []string
243	for errorHintPattern, _ := range allErrorHints {
244		allErrorHintPatterns = append(allErrorHintPatterns, errorHintPattern)
245	}
246	allErrorHintPatternsRegex := strings.Join(allErrorHintPatterns[:], "|")
247	re := regexp.MustCompile(allErrorHintPatternsRegex)
248	return &ErrorHintGenerator{
249		allErrorHints:                allErrorHints,
250		allErrorHintPatternsCompiled: re,
251	}
252}
253
254func (errorHintGenerator *ErrorHintGenerator) GetOutputWithErrorHint(rawOutput string, buildExitCode int) string {
255	if buildExitCode == 0 {
256		return rawOutput
257	}
258	errorHint := errorHintGenerator.getErrorHint(rawOutput)
259	if errorHint == nil {
260		return rawOutput
261	}
262	return rawOutput + *errorHint
263}
264
265// Returns the error hint corresponding to the FIRST match in raw output
266func (errorHintGenerator *ErrorHintGenerator) getErrorHint(rawOutput string) *string {
267	firstMatch := errorHintGenerator.allErrorHintPatternsCompiled.FindString(rawOutput)
268	if _, found := errorHintGenerator.allErrorHints[firstMatch]; found {
269		errorHint := errorHintGenerator.allErrorHints[firstMatch]
270		return &errorHint
271	}
272	return nil
273}
274