| // 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 |
| } |