xref: /aosp_15_r20/build/soong/ui/status/ninja.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1*333d2b36SAndroid Build Coastguard Worker// Copyright 2018 Google Inc. All rights reserved.
2*333d2b36SAndroid Build Coastguard Worker//
3*333d2b36SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*333d2b36SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*333d2b36SAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*333d2b36SAndroid Build Coastguard Worker//
7*333d2b36SAndroid Build Coastguard Worker//     http://www.apache.org/licenses/LICENSE-2.0
8*333d2b36SAndroid Build Coastguard Worker//
9*333d2b36SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*333d2b36SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*333d2b36SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*333d2b36SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*333d2b36SAndroid Build Coastguard Worker// limitations under the License.
14*333d2b36SAndroid Build Coastguard Worker
15*333d2b36SAndroid Build Coastguard Workerpackage status
16*333d2b36SAndroid Build Coastguard Worker
17*333d2b36SAndroid Build Coastguard Workerimport (
18*333d2b36SAndroid Build Coastguard Worker	"bufio"
19*333d2b36SAndroid Build Coastguard Worker	"fmt"
20*333d2b36SAndroid Build Coastguard Worker	"io"
21*333d2b36SAndroid Build Coastguard Worker	"os"
22*333d2b36SAndroid Build Coastguard Worker	"regexp"
23*333d2b36SAndroid Build Coastguard Worker	"runtime"
24*333d2b36SAndroid Build Coastguard Worker	"strings"
25*333d2b36SAndroid Build Coastguard Worker	"syscall"
26*333d2b36SAndroid Build Coastguard Worker	"time"
27*333d2b36SAndroid Build Coastguard Worker
28*333d2b36SAndroid Build Coastguard Worker	"google.golang.org/protobuf/proto"
29*333d2b36SAndroid Build Coastguard Worker
30*333d2b36SAndroid Build Coastguard Worker	"android/soong/ui/logger"
31*333d2b36SAndroid Build Coastguard Worker	"android/soong/ui/status/ninja_frontend"
32*333d2b36SAndroid Build Coastguard Worker)
33*333d2b36SAndroid Build Coastguard Worker
34*333d2b36SAndroid Build Coastguard Worker// NewNinjaReader reads the protobuf frontend format from ninja and translates it
35*333d2b36SAndroid Build Coastguard Worker// into calls on the ToolStatus API.
36*333d2b36SAndroid Build Coastguard Workerfunc NewNinjaReader(ctx logger.Logger, status ToolStatus, fifo string) *NinjaReader {
37*333d2b36SAndroid Build Coastguard Worker	os.Remove(fifo)
38*333d2b36SAndroid Build Coastguard Worker
39*333d2b36SAndroid Build Coastguard Worker	if err := syscall.Mkfifo(fifo, 0666); err != nil {
40*333d2b36SAndroid Build Coastguard Worker		ctx.Fatalf("Failed to mkfifo(%q): %v", fifo, err)
41*333d2b36SAndroid Build Coastguard Worker	}
42*333d2b36SAndroid Build Coastguard Worker
43*333d2b36SAndroid Build Coastguard Worker	n := &NinjaReader{
44*333d2b36SAndroid Build Coastguard Worker		status:     status,
45*333d2b36SAndroid Build Coastguard Worker		fifo:       fifo,
46*333d2b36SAndroid Build Coastguard Worker		forceClose: make(chan bool),
47*333d2b36SAndroid Build Coastguard Worker		done:       make(chan bool),
48*333d2b36SAndroid Build Coastguard Worker		cancelOpen: make(chan bool),
49*333d2b36SAndroid Build Coastguard Worker	}
50*333d2b36SAndroid Build Coastguard Worker
51*333d2b36SAndroid Build Coastguard Worker	go n.run()
52*333d2b36SAndroid Build Coastguard Worker
53*333d2b36SAndroid Build Coastguard Worker	return n
54*333d2b36SAndroid Build Coastguard Worker}
55*333d2b36SAndroid Build Coastguard Worker
56*333d2b36SAndroid Build Coastguard Workertype NinjaReader struct {
57*333d2b36SAndroid Build Coastguard Worker	status     ToolStatus
58*333d2b36SAndroid Build Coastguard Worker	fifo       string
59*333d2b36SAndroid Build Coastguard Worker	forceClose chan bool
60*333d2b36SAndroid Build Coastguard Worker	done       chan bool
61*333d2b36SAndroid Build Coastguard Worker	cancelOpen chan bool
62*333d2b36SAndroid Build Coastguard Worker}
63*333d2b36SAndroid Build Coastguard Worker
64*333d2b36SAndroid Build Coastguard Workerconst NINJA_READER_CLOSE_TIMEOUT = 5 * time.Second
65*333d2b36SAndroid Build Coastguard Worker
66*333d2b36SAndroid Build Coastguard Worker// Close waits for NinjaReader to finish reading from the fifo, or 5 seconds.
67*333d2b36SAndroid Build Coastguard Workerfunc (n *NinjaReader) Close() {
68*333d2b36SAndroid Build Coastguard Worker	// Signal the goroutine to stop if it is blocking opening the fifo.
69*333d2b36SAndroid Build Coastguard Worker	close(n.cancelOpen)
70*333d2b36SAndroid Build Coastguard Worker
71*333d2b36SAndroid Build Coastguard Worker	// Ninja should already have exited or been killed, wait 5 seconds for the FIFO to be closed and any
72*333d2b36SAndroid Build Coastguard Worker	// remaining messages to be processed through the NinjaReader.run goroutine.
73*333d2b36SAndroid Build Coastguard Worker	timeoutCh := time.After(NINJA_READER_CLOSE_TIMEOUT)
74*333d2b36SAndroid Build Coastguard Worker	select {
75*333d2b36SAndroid Build Coastguard Worker	case <-n.done:
76*333d2b36SAndroid Build Coastguard Worker		return
77*333d2b36SAndroid Build Coastguard Worker	case <-timeoutCh:
78*333d2b36SAndroid Build Coastguard Worker		// Channel is not closed yet
79*333d2b36SAndroid Build Coastguard Worker	}
80*333d2b36SAndroid Build Coastguard Worker
81*333d2b36SAndroid Build Coastguard Worker	n.status.Error(fmt.Sprintf("ninja fifo didn't finish after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
82*333d2b36SAndroid Build Coastguard Worker
83*333d2b36SAndroid Build Coastguard Worker	// Force close the reader even if the FIFO didn't close.
84*333d2b36SAndroid Build Coastguard Worker	close(n.forceClose)
85*333d2b36SAndroid Build Coastguard Worker
86*333d2b36SAndroid Build Coastguard Worker	// Wait again for the reader thread to acknowledge the close before giving up and assuming it isn't going
87*333d2b36SAndroid Build Coastguard Worker	// to send anything else.
88*333d2b36SAndroid Build Coastguard Worker	timeoutCh = time.After(NINJA_READER_CLOSE_TIMEOUT)
89*333d2b36SAndroid Build Coastguard Worker	select {
90*333d2b36SAndroid Build Coastguard Worker	case <-n.done:
91*333d2b36SAndroid Build Coastguard Worker		return
92*333d2b36SAndroid Build Coastguard Worker	case <-timeoutCh:
93*333d2b36SAndroid Build Coastguard Worker		// Channel is not closed yet
94*333d2b36SAndroid Build Coastguard Worker	}
95*333d2b36SAndroid Build Coastguard Worker
96*333d2b36SAndroid Build Coastguard Worker	n.status.Verbose(fmt.Sprintf("ninja fifo didn't finish even after force closing after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
97*333d2b36SAndroid Build Coastguard Worker}
98*333d2b36SAndroid Build Coastguard Worker
99*333d2b36SAndroid Build Coastguard Workerfunc (n *NinjaReader) run() {
100*333d2b36SAndroid Build Coastguard Worker	defer close(n.done)
101*333d2b36SAndroid Build Coastguard Worker
102*333d2b36SAndroid Build Coastguard Worker	// Opening the fifo can block forever if ninja never opens the write end, do it in a goroutine so this
103*333d2b36SAndroid Build Coastguard Worker	// method can exit on cancel.
104*333d2b36SAndroid Build Coastguard Worker	fileCh := make(chan *os.File)
105*333d2b36SAndroid Build Coastguard Worker	go func() {
106*333d2b36SAndroid Build Coastguard Worker		f, err := os.Open(n.fifo)
107*333d2b36SAndroid Build Coastguard Worker		if err != nil {
108*333d2b36SAndroid Build Coastguard Worker			n.status.Error(fmt.Sprintf("Failed to open fifo: %v", err))
109*333d2b36SAndroid Build Coastguard Worker			close(fileCh)
110*333d2b36SAndroid Build Coastguard Worker			return
111*333d2b36SAndroid Build Coastguard Worker		}
112*333d2b36SAndroid Build Coastguard Worker		fileCh <- f
113*333d2b36SAndroid Build Coastguard Worker	}()
114*333d2b36SAndroid Build Coastguard Worker
115*333d2b36SAndroid Build Coastguard Worker	var f *os.File
116*333d2b36SAndroid Build Coastguard Worker
117*333d2b36SAndroid Build Coastguard Worker	select {
118*333d2b36SAndroid Build Coastguard Worker	case f = <-fileCh:
119*333d2b36SAndroid Build Coastguard Worker		// Nothing
120*333d2b36SAndroid Build Coastguard Worker	case <-n.cancelOpen:
121*333d2b36SAndroid Build Coastguard Worker		return
122*333d2b36SAndroid Build Coastguard Worker	}
123*333d2b36SAndroid Build Coastguard Worker
124*333d2b36SAndroid Build Coastguard Worker	defer f.Close()
125*333d2b36SAndroid Build Coastguard Worker
126*333d2b36SAndroid Build Coastguard Worker	r := bufio.NewReader(f)
127*333d2b36SAndroid Build Coastguard Worker
128*333d2b36SAndroid Build Coastguard Worker	running := map[uint32]*Action{}
129*333d2b36SAndroid Build Coastguard Worker
130*333d2b36SAndroid Build Coastguard Worker	msgChan := make(chan *ninja_frontend.Status)
131*333d2b36SAndroid Build Coastguard Worker
132*333d2b36SAndroid Build Coastguard Worker	// Read from the ninja fifo and decode the protobuf in a goroutine so the main NinjaReader.run goroutine
133*333d2b36SAndroid Build Coastguard Worker	// can listen
134*333d2b36SAndroid Build Coastguard Worker	go func() {
135*333d2b36SAndroid Build Coastguard Worker		defer close(msgChan)
136*333d2b36SAndroid Build Coastguard Worker		for {
137*333d2b36SAndroid Build Coastguard Worker			size, err := readVarInt(r)
138*333d2b36SAndroid Build Coastguard Worker			if err != nil {
139*333d2b36SAndroid Build Coastguard Worker				if err != io.EOF {
140*333d2b36SAndroid Build Coastguard Worker					n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
141*333d2b36SAndroid Build Coastguard Worker				}
142*333d2b36SAndroid Build Coastguard Worker				return
143*333d2b36SAndroid Build Coastguard Worker			}
144*333d2b36SAndroid Build Coastguard Worker
145*333d2b36SAndroid Build Coastguard Worker			buf := make([]byte, size)
146*333d2b36SAndroid Build Coastguard Worker			_, err = io.ReadFull(r, buf)
147*333d2b36SAndroid Build Coastguard Worker			if err != nil {
148*333d2b36SAndroid Build Coastguard Worker				if err == io.EOF {
149*333d2b36SAndroid Build Coastguard Worker					n.status.Print(fmt.Sprintf("Missing message of size %d from ninja\n", size))
150*333d2b36SAndroid Build Coastguard Worker				} else {
151*333d2b36SAndroid Build Coastguard Worker					n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
152*333d2b36SAndroid Build Coastguard Worker				}
153*333d2b36SAndroid Build Coastguard Worker				return
154*333d2b36SAndroid Build Coastguard Worker			}
155*333d2b36SAndroid Build Coastguard Worker
156*333d2b36SAndroid Build Coastguard Worker			msg := &ninja_frontend.Status{}
157*333d2b36SAndroid Build Coastguard Worker			err = proto.Unmarshal(buf, msg)
158*333d2b36SAndroid Build Coastguard Worker			if err != nil {
159*333d2b36SAndroid Build Coastguard Worker				n.status.Print(fmt.Sprintf("Error reading message from ninja: %v", err))
160*333d2b36SAndroid Build Coastguard Worker				continue
161*333d2b36SAndroid Build Coastguard Worker			}
162*333d2b36SAndroid Build Coastguard Worker
163*333d2b36SAndroid Build Coastguard Worker			msgChan <- msg
164*333d2b36SAndroid Build Coastguard Worker		}
165*333d2b36SAndroid Build Coastguard Worker	}()
166*333d2b36SAndroid Build Coastguard Worker
167*333d2b36SAndroid Build Coastguard Worker	for {
168*333d2b36SAndroid Build Coastguard Worker		var msg *ninja_frontend.Status
169*333d2b36SAndroid Build Coastguard Worker		var msgOk bool
170*333d2b36SAndroid Build Coastguard Worker		select {
171*333d2b36SAndroid Build Coastguard Worker		case <-n.forceClose:
172*333d2b36SAndroid Build Coastguard Worker			// Close() has been called, but the reader goroutine didn't get EOF after 5 seconds
173*333d2b36SAndroid Build Coastguard Worker			break
174*333d2b36SAndroid Build Coastguard Worker		case msg, msgOk = <-msgChan:
175*333d2b36SAndroid Build Coastguard Worker			// msg is ready or closed
176*333d2b36SAndroid Build Coastguard Worker		}
177*333d2b36SAndroid Build Coastguard Worker
178*333d2b36SAndroid Build Coastguard Worker		if !msgOk {
179*333d2b36SAndroid Build Coastguard Worker			// msgChan is closed
180*333d2b36SAndroid Build Coastguard Worker			break
181*333d2b36SAndroid Build Coastguard Worker		}
182*333d2b36SAndroid Build Coastguard Worker
183*333d2b36SAndroid Build Coastguard Worker		if msg.BuildStarted != nil {
184*333d2b36SAndroid Build Coastguard Worker			parallelism := uint32(runtime.NumCPU())
185*333d2b36SAndroid Build Coastguard Worker			if msg.BuildStarted.GetParallelism() > 0 {
186*333d2b36SAndroid Build Coastguard Worker				parallelism = msg.BuildStarted.GetParallelism()
187*333d2b36SAndroid Build Coastguard Worker			}
188*333d2b36SAndroid Build Coastguard Worker			// It is estimated from total time / parallelism assumming the build is packing enough.
189*333d2b36SAndroid Build Coastguard Worker			estimatedDurationFromTotal := time.Duration(msg.BuildStarted.GetEstimatedTotalTime()/parallelism) * time.Millisecond
190*333d2b36SAndroid Build Coastguard Worker			// It is estimated from critical path time which is useful for small size build.
191*333d2b36SAndroid Build Coastguard Worker			estimatedDurationFromCriticalPath := time.Duration(msg.BuildStarted.GetCriticalPathTime()) * time.Millisecond
192*333d2b36SAndroid Build Coastguard Worker			// Select the longer one.
193*333d2b36SAndroid Build Coastguard Worker			estimatedDuration := max(estimatedDurationFromTotal, estimatedDurationFromCriticalPath)
194*333d2b36SAndroid Build Coastguard Worker
195*333d2b36SAndroid Build Coastguard Worker			if estimatedDuration > 0 {
196*333d2b36SAndroid Build Coastguard Worker				n.status.SetEstimatedTime(time.Now().Add(estimatedDuration))
197*333d2b36SAndroid Build Coastguard Worker				n.status.Verbose(fmt.Sprintf("parallelism: %d, estimated from total time: %s, critical path time: %s",
198*333d2b36SAndroid Build Coastguard Worker					parallelism,
199*333d2b36SAndroid Build Coastguard Worker					estimatedDurationFromTotal,
200*333d2b36SAndroid Build Coastguard Worker					estimatedDurationFromCriticalPath))
201*333d2b36SAndroid Build Coastguard Worker
202*333d2b36SAndroid Build Coastguard Worker			}
203*333d2b36SAndroid Build Coastguard Worker		}
204*333d2b36SAndroid Build Coastguard Worker		if msg.TotalEdges != nil {
205*333d2b36SAndroid Build Coastguard Worker			n.status.SetTotalActions(int(msg.TotalEdges.GetTotalEdges()))
206*333d2b36SAndroid Build Coastguard Worker		}
207*333d2b36SAndroid Build Coastguard Worker		if msg.EdgeStarted != nil {
208*333d2b36SAndroid Build Coastguard Worker			action := &Action{
209*333d2b36SAndroid Build Coastguard Worker				Description:   msg.EdgeStarted.GetDesc(),
210*333d2b36SAndroid Build Coastguard Worker				Outputs:       msg.EdgeStarted.Outputs,
211*333d2b36SAndroid Build Coastguard Worker				Inputs:        msg.EdgeStarted.Inputs,
212*333d2b36SAndroid Build Coastguard Worker				Command:       msg.EdgeStarted.GetCommand(),
213*333d2b36SAndroid Build Coastguard Worker				ChangedInputs: msg.EdgeStarted.ChangedInputs,
214*333d2b36SAndroid Build Coastguard Worker			}
215*333d2b36SAndroid Build Coastguard Worker			n.status.StartAction(action)
216*333d2b36SAndroid Build Coastguard Worker			running[msg.EdgeStarted.GetId()] = action
217*333d2b36SAndroid Build Coastguard Worker		}
218*333d2b36SAndroid Build Coastguard Worker		if msg.EdgeFinished != nil {
219*333d2b36SAndroid Build Coastguard Worker			if started, ok := running[msg.EdgeFinished.GetId()]; ok {
220*333d2b36SAndroid Build Coastguard Worker				delete(running, msg.EdgeFinished.GetId())
221*333d2b36SAndroid Build Coastguard Worker
222*333d2b36SAndroid Build Coastguard Worker				var err error
223*333d2b36SAndroid Build Coastguard Worker				exitCode := int(msg.EdgeFinished.GetStatus())
224*333d2b36SAndroid Build Coastguard Worker				if exitCode != 0 {
225*333d2b36SAndroid Build Coastguard Worker					err = fmt.Errorf("exited with code: %d", exitCode)
226*333d2b36SAndroid Build Coastguard Worker				}
227*333d2b36SAndroid Build Coastguard Worker
228*333d2b36SAndroid Build Coastguard Worker				outputWithErrorHint := errorHintGenerator.GetOutputWithErrorHint(msg.EdgeFinished.GetOutput(), exitCode)
229*333d2b36SAndroid Build Coastguard Worker				n.status.FinishAction(ActionResult{
230*333d2b36SAndroid Build Coastguard Worker					Action: started,
231*333d2b36SAndroid Build Coastguard Worker					Output: outputWithErrorHint,
232*333d2b36SAndroid Build Coastguard Worker					Error:  err,
233*333d2b36SAndroid Build Coastguard Worker					Stats: ActionResultStats{
234*333d2b36SAndroid Build Coastguard Worker						UserTime:                   msg.EdgeFinished.GetUserTime(),
235*333d2b36SAndroid Build Coastguard Worker						SystemTime:                 msg.EdgeFinished.GetSystemTime(),
236*333d2b36SAndroid Build Coastguard Worker						MaxRssKB:                   msg.EdgeFinished.GetMaxRssKb(),
237*333d2b36SAndroid Build Coastguard Worker						MinorPageFaults:            msg.EdgeFinished.GetMinorPageFaults(),
238*333d2b36SAndroid Build Coastguard Worker						MajorPageFaults:            msg.EdgeFinished.GetMajorPageFaults(),
239*333d2b36SAndroid Build Coastguard Worker						IOInputKB:                  msg.EdgeFinished.GetIoInputKb(),
240*333d2b36SAndroid Build Coastguard Worker						IOOutputKB:                 msg.EdgeFinished.GetIoOutputKb(),
241*333d2b36SAndroid Build Coastguard Worker						VoluntaryContextSwitches:   msg.EdgeFinished.GetVoluntaryContextSwitches(),
242*333d2b36SAndroid Build Coastguard Worker						InvoluntaryContextSwitches: msg.EdgeFinished.GetInvoluntaryContextSwitches(),
243*333d2b36SAndroid Build Coastguard Worker						Tags:                       msg.EdgeFinished.GetTags(),
244*333d2b36SAndroid Build Coastguard Worker					},
245*333d2b36SAndroid Build Coastguard Worker				})
246*333d2b36SAndroid Build Coastguard Worker			}
247*333d2b36SAndroid Build Coastguard Worker		}
248*333d2b36SAndroid Build Coastguard Worker		if msg.Message != nil {
249*333d2b36SAndroid Build Coastguard Worker			message := "ninja: " + msg.Message.GetMessage()
250*333d2b36SAndroid Build Coastguard Worker			switch msg.Message.GetLevel() {
251*333d2b36SAndroid Build Coastguard Worker			case ninja_frontend.Status_Message_INFO:
252*333d2b36SAndroid Build Coastguard Worker				n.status.Status(message)
253*333d2b36SAndroid Build Coastguard Worker			case ninja_frontend.Status_Message_WARNING:
254*333d2b36SAndroid Build Coastguard Worker				n.status.Print("warning: " + message)
255*333d2b36SAndroid Build Coastguard Worker			case ninja_frontend.Status_Message_ERROR:
256*333d2b36SAndroid Build Coastguard Worker				n.status.Error(message)
257*333d2b36SAndroid Build Coastguard Worker			case ninja_frontend.Status_Message_DEBUG:
258*333d2b36SAndroid Build Coastguard Worker				n.status.Verbose(message)
259*333d2b36SAndroid Build Coastguard Worker			default:
260*333d2b36SAndroid Build Coastguard Worker				n.status.Print(message)
261*333d2b36SAndroid Build Coastguard Worker			}
262*333d2b36SAndroid Build Coastguard Worker		}
263*333d2b36SAndroid Build Coastguard Worker		if msg.BuildFinished != nil {
264*333d2b36SAndroid Build Coastguard Worker			n.status.Finish()
265*333d2b36SAndroid Build Coastguard Worker		}
266*333d2b36SAndroid Build Coastguard Worker	}
267*333d2b36SAndroid Build Coastguard Worker}
268*333d2b36SAndroid Build Coastguard Worker
269*333d2b36SAndroid Build Coastguard Workerfunc readVarInt(r *bufio.Reader) (int, error) {
270*333d2b36SAndroid Build Coastguard Worker	ret := 0
271*333d2b36SAndroid Build Coastguard Worker	shift := uint(0)
272*333d2b36SAndroid Build Coastguard Worker
273*333d2b36SAndroid Build Coastguard Worker	for {
274*333d2b36SAndroid Build Coastguard Worker		b, err := r.ReadByte()
275*333d2b36SAndroid Build Coastguard Worker		if err != nil {
276*333d2b36SAndroid Build Coastguard Worker			return 0, err
277*333d2b36SAndroid Build Coastguard Worker		}
278*333d2b36SAndroid Build Coastguard Worker
279*333d2b36SAndroid Build Coastguard Worker		ret += int(b&0x7f) << (shift * 7)
280*333d2b36SAndroid Build Coastguard Worker		if b&0x80 == 0 {
281*333d2b36SAndroid Build Coastguard Worker			break
282*333d2b36SAndroid Build Coastguard Worker		}
283*333d2b36SAndroid Build Coastguard Worker		shift += 1
284*333d2b36SAndroid Build Coastguard Worker		if shift > 4 {
285*333d2b36SAndroid Build Coastguard Worker			return 0, fmt.Errorf("Expected varint32 length-delimited message")
286*333d2b36SAndroid Build Coastguard Worker		}
287*333d2b36SAndroid Build Coastguard Worker	}
288*333d2b36SAndroid Build Coastguard Worker
289*333d2b36SAndroid Build Coastguard Worker	return ret, nil
290*333d2b36SAndroid Build Coastguard Worker}
291*333d2b36SAndroid Build Coastguard Worker
292*333d2b36SAndroid Build Coastguard Worker// key is pattern in stdout/stderr
293*333d2b36SAndroid Build Coastguard Worker// value is error hint
294*333d2b36SAndroid Build Coastguard Workervar allErrorHints = map[string]string{
295*333d2b36SAndroid Build Coastguard Worker	"Read-only file system": `\nWrite to a read-only file system detected. Possible fixes include
296*333d2b36SAndroid Build Coastguard Worker1. Generate file directly to out/ which is ReadWrite, #recommend solution
297*333d2b36SAndroid Build Coastguard Worker2. BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST := <my/path/1> <my/path/2> #discouraged, subset of source tree will be RW
298*333d2b36SAndroid Build Coastguard Worker3. BUILD_BROKEN_SRC_DIR_IS_WRITABLE := true #highly discouraged, entire source tree will be RW
299*333d2b36SAndroid Build Coastguard Worker`,
300*333d2b36SAndroid Build Coastguard Worker}
301*333d2b36SAndroid Build Coastguard Workervar errorHintGenerator = *newErrorHintGenerator(allErrorHints)
302*333d2b36SAndroid Build Coastguard Worker
303*333d2b36SAndroid Build Coastguard Workertype ErrorHintGenerator struct {
304*333d2b36SAndroid Build Coastguard Worker	allErrorHints                map[string]string
305*333d2b36SAndroid Build Coastguard Worker	allErrorHintPatternsCompiled *regexp.Regexp
306*333d2b36SAndroid Build Coastguard Worker}
307*333d2b36SAndroid Build Coastguard Worker
308*333d2b36SAndroid Build Coastguard Workerfunc newErrorHintGenerator(allErrorHints map[string]string) *ErrorHintGenerator {
309*333d2b36SAndroid Build Coastguard Worker	var allErrorHintPatterns []string
310*333d2b36SAndroid Build Coastguard Worker	for errorHintPattern, _ := range allErrorHints {
311*333d2b36SAndroid Build Coastguard Worker		allErrorHintPatterns = append(allErrorHintPatterns, errorHintPattern)
312*333d2b36SAndroid Build Coastguard Worker	}
313*333d2b36SAndroid Build Coastguard Worker	allErrorHintPatternsRegex := strings.Join(allErrorHintPatterns[:], "|")
314*333d2b36SAndroid Build Coastguard Worker	re := regexp.MustCompile(allErrorHintPatternsRegex)
315*333d2b36SAndroid Build Coastguard Worker	return &ErrorHintGenerator{
316*333d2b36SAndroid Build Coastguard Worker		allErrorHints:                allErrorHints,
317*333d2b36SAndroid Build Coastguard Worker		allErrorHintPatternsCompiled: re,
318*333d2b36SAndroid Build Coastguard Worker	}
319*333d2b36SAndroid Build Coastguard Worker}
320*333d2b36SAndroid Build Coastguard Worker
321*333d2b36SAndroid Build Coastguard Workerfunc (errorHintGenerator *ErrorHintGenerator) GetOutputWithErrorHint(rawOutput string, buildExitCode int) string {
322*333d2b36SAndroid Build Coastguard Worker	if buildExitCode == 0 {
323*333d2b36SAndroid Build Coastguard Worker		return rawOutput
324*333d2b36SAndroid Build Coastguard Worker	}
325*333d2b36SAndroid Build Coastguard Worker	errorHint := errorHintGenerator.getErrorHint(rawOutput)
326*333d2b36SAndroid Build Coastguard Worker	if errorHint == nil {
327*333d2b36SAndroid Build Coastguard Worker		return rawOutput
328*333d2b36SAndroid Build Coastguard Worker	}
329*333d2b36SAndroid Build Coastguard Worker	return rawOutput + *errorHint
330*333d2b36SAndroid Build Coastguard Worker}
331*333d2b36SAndroid Build Coastguard Worker
332*333d2b36SAndroid Build Coastguard Worker// Returns the error hint corresponding to the FIRST match in raw output
333*333d2b36SAndroid Build Coastguard Workerfunc (errorHintGenerator *ErrorHintGenerator) getErrorHint(rawOutput string) *string {
334*333d2b36SAndroid Build Coastguard Worker	firstMatch := errorHintGenerator.allErrorHintPatternsCompiled.FindString(rawOutput)
335*333d2b36SAndroid Build Coastguard Worker	if _, found := errorHintGenerator.allErrorHints[firstMatch]; found {
336*333d2b36SAndroid Build Coastguard Worker		errorHint := errorHintGenerator.allErrorHints[firstMatch]
337*333d2b36SAndroid Build Coastguard Worker		return &errorHint
338*333d2b36SAndroid Build Coastguard Worker	}
339*333d2b36SAndroid Build Coastguard Worker	return nil
340*333d2b36SAndroid Build Coastguard Worker}
341