blob: fb760ac8f05c63d482bc23d5228f0e579dd59c59 [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{
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
Dan Willemsenb82471a2018-05-17 16:37:09 -070052}
53
Colin Crossb98d3bc2019-03-21 16:02:58 -070054type 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()))
Dan Willemsenb82471a2018-05-17 16:37:09 -070075 }
Colin Crossb98d3bc2019-03-21 16:02:58 -070076
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
Dan Willemsenb82471a2018-05-17 16:37:09 -0700105 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 {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700115 n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700116 }
117 return
118 }
119
120 buf := make([]byte, size)
121 _, err = io.ReadFull(r, buf)
122 if err != nil {
123 if err == io.EOF {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700124 n.status.Print(fmt.Sprintf("Missing message of size %d from ninja\n", size))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700125 } else {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700126 n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700127 }
128 return
129 }
130
131 msg := &ninja_frontend.Status{}
132 err = proto.Unmarshal(buf, msg)
133 if err != nil {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700134 n.status.Print(fmt.Sprintf("Error reading message from ninja: %v", err))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700135 continue
136 }
137
138 // Ignore msg.BuildStarted
139 if msg.TotalEdges != nil {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700140 n.status.SetTotalActions(int(msg.TotalEdges.GetTotalEdges()))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700141 }
142 if msg.EdgeStarted != nil {
143 action := &Action{
144 Description: msg.EdgeStarted.GetDesc(),
145 Outputs: msg.EdgeStarted.Outputs,
Colin Cross7b624532019-06-21 15:08:30 -0700146 Inputs: msg.EdgeStarted.Inputs,
Dan Willemsenb82471a2018-05-17 16:37:09 -0700147 Command: msg.EdgeStarted.GetCommand(),
148 }
Colin Crossb98d3bc2019-03-21 16:02:58 -0700149 n.status.StartAction(action)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700150 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
Spandan Das05063612021-06-25 01:39:04 +0000162 outputWithErrorHint := errorHintGenerator.GetOutputWithErrorHint(msg.EdgeFinished.GetOutput(), exitCode)
Colin Crossb98d3bc2019-03-21 16:02:58 -0700163 n.status.FinishAction(ActionResult{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700164 Action: started,
Spandan Das05063612021-06-25 01:39:04 +0000165 Output: outputWithErrorHint,
Dan Willemsenb82471a2018-05-17 16:37:09 -0700166 Error: err,
Colin Crossd888b6b2020-10-15 13:46:32 -0700167 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(),
Dan Alberte82234e2023-06-01 23:09:38 +0000177 Tags: msg.EdgeFinished.GetTags(),
Colin Crossd888b6b2020-10-15 13:46:32 -0700178 },
Dan Willemsenb82471a2018-05-17 16:37:09 -0700179 })
180 }
181 }
182 if msg.Message != nil {
183 message := "ninja: " + msg.Message.GetMessage()
184 switch msg.Message.GetLevel() {
185 case ninja_frontend.Status_Message_INFO:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700186 n.status.Status(message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700187 case ninja_frontend.Status_Message_WARNING:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700188 n.status.Print("warning: " + message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700189 case ninja_frontend.Status_Message_ERROR:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700190 n.status.Error(message)
Dan Willemsen08218222020-05-18 14:02:02 -0700191 case ninja_frontend.Status_Message_DEBUG:
192 n.status.Verbose(message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700193 default:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700194 n.status.Print(message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700195 }
196 }
197 if msg.BuildFinished != nil {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700198 n.status.Finish()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700199 }
200 }
201}
202
203func readVarInt(r *bufio.Reader) (int, error) {
204 ret := 0
205 shift := uint(0)
206
207 for {
208 b, err := r.ReadByte()
209 if err != nil {
210 return 0, err
211 }
212
213 ret += int(b&0x7f) << (shift * 7)
214 if b&0x80 == 0 {
215 break
216 }
217 shift += 1
218 if shift > 4 {
219 return 0, fmt.Errorf("Expected varint32 length-delimited message")
220 }
221 }
222
223 return ret, nil
224}
Spandan Das05063612021-06-25 01:39:04 +0000225
226// key is pattern in stdout/stderr
227// value is error hint
228var allErrorHints = map[string]string{
229 "Read-only file system": `\nWrite to a read-only file system detected. Possible fixes include
2301. Generate file directly to out/ which is ReadWrite, #recommend solution
2312. BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST := <my/path/1> <my/path/2> #discouraged, subset of source tree will be RW
2323. BUILD_BROKEN_SRC_DIR_IS_WRITABLE := true #highly discouraged, entire source tree will be RW
233`,
234}
235var errorHintGenerator = *newErrorHintGenerator(allErrorHints)
236
237type ErrorHintGenerator struct {
238 allErrorHints map[string]string
239 allErrorHintPatternsCompiled *regexp.Regexp
240}
241
242func newErrorHintGenerator(allErrorHints map[string]string) *ErrorHintGenerator {
243 var allErrorHintPatterns []string
244 for errorHintPattern, _ := range allErrorHints {
245 allErrorHintPatterns = append(allErrorHintPatterns, errorHintPattern)
246 }
247 allErrorHintPatternsRegex := strings.Join(allErrorHintPatterns[:], "|")
248 re := regexp.MustCompile(allErrorHintPatternsRegex)
249 return &ErrorHintGenerator{
250 allErrorHints: allErrorHints,
251 allErrorHintPatternsCompiled: re,
252 }
253}
254
255func (errorHintGenerator *ErrorHintGenerator) GetOutputWithErrorHint(rawOutput string, buildExitCode int) string {
256 if buildExitCode == 0 {
257 return rawOutput
258 }
259 errorHint := errorHintGenerator.getErrorHint(rawOutput)
260 if errorHint == nil {
261 return rawOutput
262 }
263 return rawOutput + *errorHint
264}
265
266// Returns the error hint corresponding to the FIRST match in raw output
267func (errorHintGenerator *ErrorHintGenerator) getErrorHint(rawOutput string) *string {
268 firstMatch := errorHintGenerator.allErrorHintPatternsCompiled.FindString(rawOutput)
269 if _, found := errorHintGenerator.allErrorHints[firstMatch]; found {
270 errorHint := errorHintGenerator.allErrorHints[firstMatch]
271 return &errorHint
272 }
273 return nil
274}