blob: 9cf2f6a812014dc10754f420318081997b238a2d [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"
22 "syscall"
Colin Crossb98d3bc2019-03-21 16:02:58 -070023 "time"
Dan Willemsenb82471a2018-05-17 16:37:09 -070024
25 "github.com/golang/protobuf/proto"
26
27 "android/soong/ui/logger"
28 "android/soong/ui/status/ninja_frontend"
29)
30
Colin Crossb98d3bc2019-03-21 16:02:58 -070031// NewNinjaReader reads the protobuf frontend format from ninja and translates it
Dan Willemsenb82471a2018-05-17 16:37:09 -070032// into calls on the ToolStatus API.
Colin Crossb98d3bc2019-03-21 16:02:58 -070033func NewNinjaReader(ctx logger.Logger, status ToolStatus, fifo string) *NinjaReader {
Dan Willemsenb82471a2018-05-17 16:37:09 -070034 os.Remove(fifo)
35
36 err := syscall.Mkfifo(fifo, 0666)
37 if err != nil {
38 ctx.Fatalf("Failed to mkfifo(%q): %v", fifo, err)
39 }
40
Colin Crossb98d3bc2019-03-21 16:02:58 -070041 n := &NinjaReader{
42 status: status,
43 fifo: fifo,
44 done: make(chan bool),
45 cancel: make(chan bool),
46 }
47
48 go n.run()
49
50 return n
Dan Willemsenb82471a2018-05-17 16:37:09 -070051}
52
Colin Crossb98d3bc2019-03-21 16:02:58 -070053type NinjaReader struct {
54 status ToolStatus
55 fifo string
56 done chan bool
57 cancel chan bool
58}
59
60const NINJA_READER_CLOSE_TIMEOUT = 5 * time.Second
61
62// Close waits for NinjaReader to finish reading from the fifo, or 5 seconds.
63func (n *NinjaReader) Close() {
64 // Signal the goroutine to stop if it is blocking opening the fifo.
65 close(n.cancel)
66
67 timeoutCh := time.After(NINJA_READER_CLOSE_TIMEOUT)
68
69 select {
70 case <-n.done:
71 // Nothing
72 case <-timeoutCh:
73 n.status.Error(fmt.Sprintf("ninja fifo didn't finish after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
Dan Willemsenb82471a2018-05-17 16:37:09 -070074 }
Colin Crossb98d3bc2019-03-21 16:02:58 -070075
76 return
77}
78
79func (n *NinjaReader) run() {
80 defer close(n.done)
81
82 // Opening the fifo can block forever if ninja never opens the write end, do it in a goroutine so this
83 // method can exit on cancel.
84 fileCh := make(chan *os.File)
85 go func() {
86 f, err := os.Open(n.fifo)
87 if err != nil {
88 n.status.Error(fmt.Sprintf("Failed to open fifo: %v", err))
89 close(fileCh)
90 return
91 }
92 fileCh <- f
93 }()
94
95 var f *os.File
96
97 select {
98 case f = <-fileCh:
99 // Nothing
100 case <-n.cancel:
101 return
102 }
103
Dan Willemsenb82471a2018-05-17 16:37:09 -0700104 defer f.Close()
105
106 r := bufio.NewReader(f)
107
108 running := map[uint32]*Action{}
109
110 for {
111 size, err := readVarInt(r)
112 if err != nil {
113 if err != io.EOF {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700114 n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700115 }
116 return
117 }
118
119 buf := make([]byte, size)
120 _, err = io.ReadFull(r, buf)
121 if err != nil {
122 if err == io.EOF {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700123 n.status.Print(fmt.Sprintf("Missing message of size %d from ninja\n", size))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700124 } else {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700125 n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700126 }
127 return
128 }
129
130 msg := &ninja_frontend.Status{}
131 err = proto.Unmarshal(buf, msg)
132 if err != nil {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700133 n.status.Print(fmt.Sprintf("Error reading message from ninja: %v", err))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700134 continue
135 }
136
137 // Ignore msg.BuildStarted
138 if msg.TotalEdges != nil {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700139 n.status.SetTotalActions(int(msg.TotalEdges.GetTotalEdges()))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700140 }
141 if msg.EdgeStarted != nil {
142 action := &Action{
143 Description: msg.EdgeStarted.GetDesc(),
144 Outputs: msg.EdgeStarted.Outputs,
Colin Cross7b624532019-06-21 15:08:30 -0700145 Inputs: msg.EdgeStarted.Inputs,
Dan Willemsenb82471a2018-05-17 16:37:09 -0700146 Command: msg.EdgeStarted.GetCommand(),
147 }
Colin Crossb98d3bc2019-03-21 16:02:58 -0700148 n.status.StartAction(action)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700149 running[msg.EdgeStarted.GetId()] = action
150 }
151 if msg.EdgeFinished != nil {
152 if started, ok := running[msg.EdgeFinished.GetId()]; ok {
153 delete(running, msg.EdgeFinished.GetId())
154
155 var err error
156 exitCode := int(msg.EdgeFinished.GetStatus())
157 if exitCode != 0 {
158 err = fmt.Errorf("exited with code: %d", exitCode)
159 }
160
Colin Crossb98d3bc2019-03-21 16:02:58 -0700161 n.status.FinishAction(ActionResult{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700162 Action: started,
163 Output: msg.EdgeFinished.GetOutput(),
164 Error: err,
165 })
166 }
167 }
168 if msg.Message != nil {
169 message := "ninja: " + msg.Message.GetMessage()
170 switch msg.Message.GetLevel() {
171 case ninja_frontend.Status_Message_INFO:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700172 n.status.Status(message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700173 case ninja_frontend.Status_Message_WARNING:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700174 n.status.Print("warning: " + message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700175 case ninja_frontend.Status_Message_ERROR:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700176 n.status.Error(message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700177 default:
Colin Crossb98d3bc2019-03-21 16:02:58 -0700178 n.status.Print(message)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700179 }
180 }
181 if msg.BuildFinished != nil {
Colin Crossb98d3bc2019-03-21 16:02:58 -0700182 n.status.Finish()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700183 }
184 }
185}
186
187func readVarInt(r *bufio.Reader) (int, error) {
188 ret := 0
189 shift := uint(0)
190
191 for {
192 b, err := r.ReadByte()
193 if err != nil {
194 return 0, err
195 }
196
197 ret += int(b&0x7f) << (shift * 7)
198 if b&0x80 == 0 {
199 break
200 }
201 shift += 1
202 if shift > 4 {
203 return 0, fmt.Errorf("Expected varint32 length-delimited message")
204 }
205 }
206
207 return ret, nil
208}