blob: 4ceb5efc580053895f9984f8a380957fb20b8742 [file] [log] [blame]
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package status
import (
"bufio"
"fmt"
"io"
"os"
"syscall"
"github.com/golang/protobuf/proto"
"android/soong/ui/logger"
"android/soong/ui/status/ninja_frontend"
)
// NinjaReader reads the protobuf frontend format from ninja and translates it
// into calls on the ToolStatus API.
func NinjaReader(ctx logger.Logger, status ToolStatus, fifo string) {
os.Remove(fifo)
err := syscall.Mkfifo(fifo, 0666)
if err != nil {
ctx.Fatalf("Failed to mkfifo(%q): %v", fifo, err)
}
go ninjaReader(status, fifo)
}
func ninjaReader(status ToolStatus, fifo string) {
f, err := os.Open(fifo)
if err != nil {
status.Error(fmt.Sprintf("Failed to open fifo: %v", err))
}
defer f.Close()
r := bufio.NewReader(f)
running := map[uint32]*Action{}
for {
size, err := readVarInt(r)
if err != nil {
if err != io.EOF {
status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
}
return
}
buf := make([]byte, size)
_, err = io.ReadFull(r, buf)
if err != nil {
if err == io.EOF {
status.Print(fmt.Sprintf("Missing message of size %d from ninja\n", size))
} else {
status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
}
return
}
msg := &ninja_frontend.Status{}
err = proto.Unmarshal(buf, msg)
if err != nil {
status.Print(fmt.Sprintf("Error reading message from ninja: %v", err))
continue
}
// Ignore msg.BuildStarted
if msg.TotalEdges != nil {
status.SetTotalActions(int(msg.TotalEdges.GetTotalEdges()))
}
if msg.EdgeStarted != nil {
action := &Action{
Description: msg.EdgeStarted.GetDesc(),
Outputs: msg.EdgeStarted.Outputs,
Command: msg.EdgeStarted.GetCommand(),
}
status.StartAction(action)
running[msg.EdgeStarted.GetId()] = action
}
if msg.EdgeFinished != nil {
if started, ok := running[msg.EdgeFinished.GetId()]; ok {
delete(running, msg.EdgeFinished.GetId())
var err error
exitCode := int(msg.EdgeFinished.GetStatus())
if exitCode != 0 {
err = fmt.Errorf("exited with code: %d", exitCode)
}
status.FinishAction(ActionResult{
Action: started,
Output: msg.EdgeFinished.GetOutput(),
Error: err,
})
}
}
if msg.Message != nil {
message := "ninja: " + msg.Message.GetMessage()
switch msg.Message.GetLevel() {
case ninja_frontend.Status_Message_INFO:
status.Status(message)
case ninja_frontend.Status_Message_WARNING:
status.Print("warning: " + message)
case ninja_frontend.Status_Message_ERROR:
status.Error(message)
default:
status.Print(message)
}
}
if msg.BuildFinished != nil {
status.Finish()
}
}
}
func readVarInt(r *bufio.Reader) (int, error) {
ret := 0
shift := uint(0)
for {
b, err := r.ReadByte()
if err != nil {
return 0, err
}
ret += int(b&0x7f) << (shift * 7)
if b&0x80 == 0 {
break
}
shift += 1
if shift > 4 {
return 0, fmt.Errorf("Expected varint32 length-delimited message")
}
}
return ret, nil
}