blob: 7b25d50aa901755f2b20ff47e4443b6078009119 [file] [log] [blame]
Dan Willemsenb82471a2018-05-17 16:37:09 -07001// 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"
Spandan Das05063612021-06-25 01:39:04 +000022 "regexp"
23 "strings"
Dan Willemsenb82471a2018-05-17 16:37:09 -070024 "syscall"
Colin Crossb98d3bc2019-03-21 16:02:58 -070025 "time"
Dan Willemsenb82471a2018-05-17 16:37:09 -070026
Dan Willemsen4591b642021-05-24 14:24:12 -070027 "google.golang.org/protobuf/proto"
Dan Willemsenb82471a2018-05-17 16:37:09 -070028
29 "android/soong/ui/logger"
30 "android/soong/ui/status/ninja_frontend"
31)
32
Colin Crossb98d3bc2019-03-21 16:02:58 -070033// NewNinjaReader reads the protobuf frontend format from ninja and translates it
Dan Willemsenb82471a2018-05-17 16:37:09 -070034// into calls on the ToolStatus API.
Colin Crossb98d3bc2019-03-21 16:02:58 -070035func NewNinjaReader(ctx logger.Logger, status ToolStatus, fifo string) *NinjaReader {
Dan Willemsenb82471a2018-05-17 16:37:09 -070036 os.Remove(fifo)
37
ustafb67fd12022-08-19 19:26:00 -040038 if err := syscall.Mkfifo(fifo, 0666); err != nil {
Dan Willemsenb82471a2018-05-17 16:37:09 -070039 ctx.Fatalf("Failed to mkfifo(%q): %v", fifo, err)
40 }
41
Colin Crossb98d3bc2019-03-21 16:02:58 -070042 n := &NinjaReader{
Colin Crossb0b369c2023-08-30 17:32:17 -070043 status: status,
44 fifo: fifo,
45 forceClose: make(chan bool),
46 done: make(chan bool),
47 cancelOpen: make(chan bool),
Colin Crossb98d3bc2019-03-21 16:02:58 -070048 }
49
50 go n.run()
51
52 return n
Dan Willemsenb82471a2018-05-17 16:37:09 -070053}
54
Colin Crossb98d3bc2019-03-21 16:02:58 -070055type NinjaReader struct {
Colin Crossb0b369c2023-08-30 17:32:17 -070056 status ToolStatus
57 fifo string
58 forceClose chan bool
59 done chan bool
60 cancelOpen chan bool
Colin Crossb98d3bc2019-03-21 16:02:58 -070061}
62
63const NINJA_READER_CLOSE_TIMEOUT = 5 * time.Second
64
65// Close waits for NinjaReader to finish reading from the fifo, or 5 seconds.
66func (n *NinjaReader) Close() {
67 // Signal the goroutine to stop if it is blocking opening the fifo.
Colin Crossb0b369c2023-08-30 17:32:17 -070068 close(n.cancelOpen)
Colin Crossb98d3bc2019-03-21 16:02:58 -070069
Colin Crossb0b369c2023-08-30 17:32:17 -070070 // Ninja should already have exited or been killed, wait 5 seconds for the FIFO to be closed and any
71 // remaining messages to be processed through the NinjaReader.run goroutine.
Colin Crossb98d3bc2019-03-21 16:02:58 -070072 timeoutCh := time.After(NINJA_READER_CLOSE_TIMEOUT)
Colin Crossb98d3bc2019-03-21 16:02:58 -070073 select {
74 case <-n.done:
Colin Crossb0b369c2023-08-30 17:32:17 -070075 return
Colin Crossb98d3bc2019-03-21 16:02:58 -070076 case <-timeoutCh:
Colin Crossb0b369c2023-08-30 17:32:17 -070077 // Channel is not closed yet
Dan Willemsenb82471a2018-05-17 16:37:09 -070078 }
Colin Crossb98d3bc2019-03-21 16:02:58 -070079
Colin Crossb0b369c2023-08-30 17:32:17 -070080 n.status.Error(fmt.Sprintf("ninja fifo didn't finish after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
81
82 // Force close the reader even if the FIFO didn't close.
83 close(n.forceClose)
84
85 // Wait again for the reader thread to acknowledge the close before giving up and assuming it isn't going
86 // to send anything else.
87 timeoutCh = time.After(NINJA_READER_CLOSE_TIMEOUT)
88 select {
89 case <-n.done:
90 return
91 case <-timeoutCh:
92 // Channel is not closed yet
93 }
94
95 n.status.Verbose(fmt.Sprintf("ninja fifo didn't finish even after force closing after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
Colin Crossb98d3bc2019-03-21 16:02:58 -070096}
97
98func (n *NinjaReader) run() {
99 defer close(n.done)
100
101 // Opening the fifo can block forever if ninja never opens the write end, do it in a goroutine so this
102 // method can exit on cancel.
103 fileCh := make(chan *os.File)
104 go func() {
105 f, err := os.Open(n.fifo)
106 if err != nil {
107 n.status.Error(fmt.Sprintf("Failed to open fifo: %v", err))
108 close(fileCh)
109 return
110 }
111 fileCh <- f
112 }()
113
114 var f *os.File
115
116 select {
117 case f = <-fileCh:
118 // Nothing
Colin Crossb0b369c2023-08-30 17:32:17 -0700119 case <-n.cancelOpen:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700120 return
121 }
122
Dan Willemsenb82471a2018-05-17 16:37:09 -0700123 defer f.Close()
124
125 r := bufio.NewReader(f)
126
127 running := map[uint32]*Action{}
128
Colin Crossb0b369c2023-08-30 17:32:17 -0700129 msgChan := make(chan *ninja_frontend.Status)
130
131 // Read from the ninja fifo and decode the protobuf in a goroutine so the main NinjaReader.run goroutine
132 // can listen
133 go func() {
134 defer close(msgChan)
135 for {
136 size, err := readVarInt(r)
137 if err != nil {
138 if err != io.EOF {
139 n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
140 }
141 return
142 }
143
144 buf := make([]byte, size)
145 _, err = io.ReadFull(r, buf)
146 if err != nil {
147 if err == io.EOF {
148 n.status.Print(fmt.Sprintf("Missing message of size %d from ninja\n", size))
149 } else {
150 n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
151 }
152 return
153 }
154
155 msg := &ninja_frontend.Status{}
156 err = proto.Unmarshal(buf, msg)
157 if err != nil {
158 n.status.Print(fmt.Sprintf("Error reading message from ninja: %v", err))
159 continue
160 }
161
162 msgChan <- msg
163 }
164 }()
165
Dan Willemsenb82471a2018-05-17 16:37:09 -0700166 for {
Colin Crossb0b369c2023-08-30 17:32:17 -0700167 var msg *ninja_frontend.Status
168 var msgOk bool
169 select {
170 case <-n.forceClose:
171 // Close() has been called, but the reader goroutine didn't get EOF after 5 seconds
172 break
173 case msg, msgOk = <-msgChan:
174 // msg is ready or closed
Dan Willemsenb82471a2018-05-17 16:37:09 -0700175 }
176
Colin Crossb0b369c2023-08-30 17:32:17 -0700177 if !msgOk {
178 // msgChan is closed
179 break
Dan Willemsenb82471a2018-05-17 16:37:09 -0700180 }
Dan Willemsenb82471a2018-05-17 16:37:09 -0700181 // Ignore msg.BuildStarted
182 if msg.TotalEdges != nil {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700183 n.status.SetTotalActions(int(msg.TotalEdges.GetTotalEdges()))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700184 }
185 if msg.EdgeStarted != nil {
186 action := &Action{
187 Description: msg.EdgeStarted.GetDesc(),
188 Outputs: msg.EdgeStarted.Outputs,
Colin Cross7b624532019-06-21 15:08:30 -0700189 Inputs: msg.EdgeStarted.Inputs,
Dan Willemsenb82471a2018-05-17 16:37:09 -0700190 Command: msg.EdgeStarted.GetCommand(),
191 }
Colin Crossb98d3bc2019-03-21 16:02:58 -0700192 n.status.StartAction(action)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700193 running[msg.EdgeStarted.GetId()] = action
194 }
195 if msg.EdgeFinished != nil {
196 if started, ok := running[msg.EdgeFinished.GetId()]; ok {
197 delete(running, msg.EdgeFinished.GetId())
198
199 var err error
200 exitCode := int(msg.EdgeFinished.GetStatus())
201 if exitCode != 0 {
202 err = fmt.Errorf("exited with code: %d", exitCode)
203 }
204
Spandan Das05063612021-06-25 01:39:04 +0000205 outputWithErrorHint := errorHintGenerator.GetOutputWithErrorHint(msg.EdgeFinished.GetOutput(), exitCode)
Colin Crossb98d3bc2019-03-21 16:02:58 -0700206 n.status.FinishAction(ActionResult{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700207 Action: started,
Spandan Das05063612021-06-25 01:39:04 +0000208 Output: outputWithErrorHint,
Dan Willemsenb82471a2018-05-17 16:37:09 -0700209 Error: err,
Colin Crossd888b6b2020-10-15 13:46:32 -0700210 Stats: ActionResultStats{
211 UserTime: msg.EdgeFinished.GetUserTime(),
212 SystemTime: msg.EdgeFinished.GetSystemTime(),
213 MaxRssKB: msg.EdgeFinished.GetMaxRssKb(),
214 MinorPageFaults: msg.EdgeFinished.GetMinorPageFaults(),
215 MajorPageFaults: msg.EdgeFinished.GetMajorPageFaults(),
216 IOInputKB: msg.EdgeFinished.GetIoInputKb(),
217 IOOutputKB: msg.EdgeFinished.GetIoOutputKb(),
218 VoluntaryContextSwitches: msg.EdgeFinished.GetVoluntaryContextSwitches(),
219 InvoluntaryContextSwitches: msg.EdgeFinished.GetInvoluntaryContextSwitches(),
Dan Alberte82234e2023-06-01 23:09:38 +0000220 Tags: msg.EdgeFinished.GetTags(),
Colin Crossd888b6b2020-10-15 13:46:32 -0700221 },
Dan Willemsenb82471a2018-05-17 16:37:09 -0700222 })
223 }
224 }
225 if msg.Message != nil {
226 message := "ninja: " + msg.Message.GetMessage()
227 switch msg.Message.GetLevel() {
228 case ninja_frontend.Status_Message_INFO:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700229 n.status.Status(message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700230 case ninja_frontend.Status_Message_WARNING:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700231 n.status.Print("warning: " + message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700232 case ninja_frontend.Status_Message_ERROR:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700233 n.status.Error(message)
Dan Willemsen08218222020-05-18 14:02:02 -0700234 case ninja_frontend.Status_Message_DEBUG:
235 n.status.Verbose(message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700236 default:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700237 n.status.Print(message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700238 }
239 }
240 if msg.BuildFinished != nil {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700241 n.status.Finish()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700242 }
243 }
244}
245
246func readVarInt(r *bufio.Reader) (int, error) {
247 ret := 0
248 shift := uint(0)
249
250 for {
251 b, err := r.ReadByte()
252 if err != nil {
253 return 0, err
254 }
255
256 ret += int(b&0x7f) << (shift * 7)
257 if b&0x80 == 0 {
258 break
259 }
260 shift += 1
261 if shift > 4 {
262 return 0, fmt.Errorf("Expected varint32 length-delimited message")
263 }
264 }
265
266 return ret, nil
267}
Spandan Das05063612021-06-25 01:39:04 +0000268
269// key is pattern in stdout/stderr
270// value is error hint
271var allErrorHints = map[string]string{
272 "Read-only file system": `\nWrite to a read-only file system detected. Possible fixes include
2731. Generate file directly to out/ which is ReadWrite, #recommend solution
2742. BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST := <my/path/1> <my/path/2> #discouraged, subset of source tree will be RW
2753. BUILD_BROKEN_SRC_DIR_IS_WRITABLE := true #highly discouraged, entire source tree will be RW
276`,
277}
278var errorHintGenerator = *newErrorHintGenerator(allErrorHints)
279
280type ErrorHintGenerator struct {
281 allErrorHints map[string]string
282 allErrorHintPatternsCompiled *regexp.Regexp
283}
284
285func newErrorHintGenerator(allErrorHints map[string]string) *ErrorHintGenerator {
286 var allErrorHintPatterns []string
287 for errorHintPattern, _ := range allErrorHints {
288 allErrorHintPatterns = append(allErrorHintPatterns, errorHintPattern)
289 }
290 allErrorHintPatternsRegex := strings.Join(allErrorHintPatterns[:], "|")
291 re := regexp.MustCompile(allErrorHintPatternsRegex)
292 return &ErrorHintGenerator{
293 allErrorHints: allErrorHints,
294 allErrorHintPatternsCompiled: re,
295 }
296}
297
298func (errorHintGenerator *ErrorHintGenerator) GetOutputWithErrorHint(rawOutput string, buildExitCode int) string {
299 if buildExitCode == 0 {
300 return rawOutput
301 }
302 errorHint := errorHintGenerator.getErrorHint(rawOutput)
303 if errorHint == nil {
304 return rawOutput
305 }
306 return rawOutput + *errorHint
307}
308
309// Returns the error hint corresponding to the FIRST match in raw output
310func (errorHintGenerator *ErrorHintGenerator) getErrorHint(rawOutput string) *string {
311 firstMatch := errorHintGenerator.allErrorHintPatternsCompiled.FindString(rawOutput)
312 if _, found := errorHintGenerator.allErrorHints[firstMatch]; found {
313 errorHint := errorHintGenerator.allErrorHints[firstMatch]
314 return &errorHint
315 }
316 return nil
317}