aboutsummaryrefslogtreecommitdiffstats
path: root/ui/status
diff options
context:
space:
mode:
authorDan Willemsen <dwillemsen@google.com>2018-05-17 16:37:09 -0700
committerDan Willemsen <dwillemsen@google.com>2018-07-12 14:15:31 -0700
commitb82471ad6dfec4790b2d86e49525dadc3fc06416 (patch)
treebca7e73da63c7506bbc3c967468034a351aaad94 /ui/status
parent5e48b1d183a8333528f529c7e677c4ab644f8caf (diff)
downloadbuild_soong-b82471ad6dfec4790b2d86e49525dadc3fc06416.tar.gz
build_soong-b82471ad6dfec4790b2d86e49525dadc3fc06416.tar.bz2
build_soong-b82471ad6dfec4790b2d86e49525dadc3fc06416.zip
Add a unified status reporting UI
This adds a new status package that merges the running of "actions" (ninja calls them edges) of multiple tools into one view of the current state, and gives that to a number of different outputs. For inputs: Kati's output parser has been rewritten (and moved) to map onto the StartAction/FinishAction API. A byproduct of this is that the build servers should be able to extract errors from Kati better, since they look like the errors that Ninja used to write. Ninja is no longer directly connected to the terminal, but its output is read via the protobuf frontend API, so it's just another tool whose output becomes merged together. multiproduct_kati loses its custom status routines, and uses the common one instead. For outputs: The primary output is the ui/terminal.Status type, which along with ui/terminal.Writer now controls everything about the terminal output. Today, this doesn't really change any behaviors, but having all terminal output going through here allows a more complicated (multi-line / full window) status display in the future. The tracer acts as an output of the status package, tracing all the action start / finish events. This replaces reading the .ninja_log file, so it now properly handles multiple output files from a single action. A new rotated log file (out/error.log, or out/dist/logs/error.log) just contains a description of all of the errors that happened during the current build. Another new compressed and rotated log file (out/verbose.log.gz, or out/dist/logs/verbose.log.gz) contains the full verbose (showcommands) log of every execution run by the build. Since this is now written on every build, the showcommands argument is now ignored -- if you want to get the commands run, look at the log file after the build. Test: m Test: <built-in tests> Test: NINJA_ARGS="-t list" m Test: check the build.trace.gz Test: check the new log files Change-Id: If1d8994890d43ef68f65aa10ddd8e6e06dc7013a
Diffstat (limited to 'ui/status')
-rw-r--r--ui/status/Android.bp42
-rw-r--r--ui/status/kati.go138
-rw-r--r--ui/status/kati_test.go175
-rw-r--r--ui/status/log.go136
-rw-r--r--ui/status/ninja.go153
-rw-r--r--ui/status/ninja_frontend/README3
-rw-r--r--ui/status/ninja_frontend/frontend.pb.go510
-rw-r--r--ui/status/ninja_frontend/frontend.proto84
-rwxr-xr-xui/status/ninja_frontend/regen.sh3
-rw-r--r--ui/status/status.go340
-rw-r--r--ui/status/status_test.go166
11 files changed, 1750 insertions, 0 deletions
diff --git a/ui/status/Android.bp b/ui/status/Android.bp
new file mode 100644
index 00000000..76caaef1
--- /dev/null
+++ b/ui/status/Android.bp
@@ -0,0 +1,42 @@
+// 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.
+
+bootstrap_go_package {
+ name: "soong-ui-status",
+ pkgPath: "android/soong/ui/status",
+ deps: [
+ "golang-protobuf-proto",
+ "soong-ui-logger",
+ "soong-ui-status-ninja_frontend",
+ ],
+ srcs: [
+ "kati.go",
+ "log.go",
+ "ninja.go",
+ "status.go",
+ ],
+ testSrcs: [
+ "kati_test.go",
+ "status_test.go",
+ ],
+}
+
+bootstrap_go_package {
+ name: "soong-ui-status-ninja_frontend",
+ pkgPath: "android/soong/ui/status/ninja_frontend",
+ deps: ["golang-protobuf-proto"],
+ srcs: [
+ "ninja_frontend/frontend.pb.go",
+ ],
+}
diff --git a/ui/status/kati.go b/ui/status/kati.go
new file mode 100644
index 00000000..552a9e97
--- /dev/null
+++ b/ui/status/kati.go
@@ -0,0 +1,138 @@
+// 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"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+var katiError = regexp.MustCompile(`^(\033\[1m)?[^ ]+:[0-9]+: (\033\[31m)?error:`)
+var katiIncludeRe = regexp.MustCompile(`^(\[(\d+)/(\d+)] )?((including [^ ]+|initializing build system|finishing build rules|writing build rules) ...)$`)
+var katiLogRe = regexp.MustCompile(`^\*kati\*: `)
+var katiNinjaMissing = regexp.MustCompile("^[^ ]+ is missing, regenerating...$")
+
+type katiOutputParser struct {
+ st ToolStatus
+
+ count int
+ total int
+ extra int
+
+ action *Action
+ buf strings.Builder
+ hasError bool
+}
+
+func (k *katiOutputParser) flushAction() {
+ if k.action == nil {
+ return
+ }
+
+ var err error
+ if k.hasError {
+ err = fmt.Errorf("makefile error")
+ }
+
+ k.st.FinishAction(ActionResult{
+ Action: k.action,
+ Output: k.buf.String(),
+ Error: err,
+ })
+
+ k.buf.Reset()
+ k.hasError = false
+}
+
+func (k *katiOutputParser) parseLine(line string) {
+ // Only put kati debug/stat lines in our verbose log
+ if katiLogRe.MatchString(line) {
+ k.st.Verbose(line)
+ return
+ }
+
+ if matches := katiIncludeRe.FindStringSubmatch(line); len(matches) > 0 {
+ k.flushAction()
+ k.count += 1
+
+ matches := katiIncludeRe.FindStringSubmatch(line)
+ if matches[2] != "" {
+ idx, err := strconv.Atoi(matches[2])
+
+ if err == nil && idx+k.extra != k.count {
+ k.extra = k.count - idx
+ k.st.SetTotalActions(k.total + k.extra)
+ }
+ } else {
+ k.extra += 1
+ k.st.SetTotalActions(k.total + k.extra)
+ }
+
+ if matches[3] != "" {
+ tot, err := strconv.Atoi(matches[3])
+
+ if err == nil && tot != k.total {
+ k.total = tot
+ k.st.SetTotalActions(k.total + k.extra)
+ }
+ }
+
+ k.action = &Action{
+ Description: matches[4],
+ }
+ k.st.StartAction(k.action)
+ } else if k.action != nil {
+ if katiError.MatchString(line) {
+ k.hasError = true
+ }
+ k.buf.WriteString(line)
+ k.buf.WriteString("\n")
+ } else {
+ // Before we've started executing actions from Kati
+ if line == "No need to regenerate ninja file" || katiNinjaMissing.MatchString(line) {
+ k.st.Status(line)
+ } else {
+ k.st.Print(line)
+ }
+ }
+}
+
+// KatiReader reads the output from Kati, and turns it into Actions and
+// messages that are passed into the ToolStatus API.
+func KatiReader(st ToolStatus, pipe io.ReadCloser) {
+ parser := &katiOutputParser{
+ st: st,
+ }
+
+ scanner := bufio.NewScanner(pipe)
+ for scanner.Scan() {
+ parser.parseLine(scanner.Text())
+ }
+
+ parser.flushAction()
+
+ if err := scanner.Err(); err != nil {
+ var buf strings.Builder
+ io.Copy(&buf, pipe)
+ st.Print(fmt.Sprintf("Error from kati parser: %s", err))
+ st.Print(buf.String())
+ }
+
+ st.Finish()
+}
diff --git a/ui/status/kati_test.go b/ui/status/kati_test.go
new file mode 100644
index 00000000..f2cb8132
--- /dev/null
+++ b/ui/status/kati_test.go
@@ -0,0 +1,175 @@
+// 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 (
+ "testing"
+)
+
+type lastOutput struct {
+ counterOutput
+
+ action *Action
+ result ActionResult
+
+ msgLevel MsgLevel
+ msg string
+}
+
+func (l *lastOutput) StartAction(a *Action, c Counts) {
+ l.action = a
+ l.counterOutput.StartAction(a, c)
+}
+func (l *lastOutput) FinishAction(r ActionResult, c Counts) {
+ l.result = r
+ l.counterOutput.FinishAction(r, c)
+}
+func (l *lastOutput) Message(level MsgLevel, msg string) {
+ l.msgLevel = level
+ l.msg = msg
+}
+func (l *lastOutput) Flush() {}
+
+func TestKatiNormalCase(t *testing.T) {
+ status := &Status{}
+ output := &lastOutput{}
+ status.AddOutput(output)
+
+ parser := &katiOutputParser{
+ st: status.StartTool(),
+ }
+
+ msg := "*kati*: verbose msg"
+ parser.parseLine(msg)
+ output.Expect(t, Counts{})
+
+ if output.msgLevel != VerboseLvl {
+ t.Errorf("Expected verbose message, but got %d", output.msgLevel)
+ }
+ if output.msg != msg {
+ t.Errorf("unexpected message contents:\nwant: %q\n got: %q\n", msg, output.msg)
+ }
+
+ parser.parseLine("out/build-aosp_arm.ninja is missing, regenerating...")
+ output.Expect(t, Counts{})
+
+ parser.parseLine("[1/1] initializing build system ...")
+ output.Expect(t, Counts{
+ TotalActions: 1,
+ RunningActions: 1,
+ StartedActions: 1,
+ FinishedActions: 0,
+ })
+
+ parser.parseLine("[2/5] including out/soong/Android-aosp_arm.mk ...")
+ output.Expect(t, Counts{
+ TotalActions: 5,
+ RunningActions: 1,
+ StartedActions: 2,
+ FinishedActions: 1,
+ })
+
+ parser.parseLine("[3/5] including a ...")
+ msg = "a random message"
+ parser.parseLine(msg)
+
+ // Start the next line to flush the previous result
+ parser.parseLine("[4/5] finishing build rules ...")
+
+ msg += "\n"
+ if output.result.Output != msg {
+ t.Errorf("output for action did not match:\nwant: %q\n got: %q\n", msg, output.result.Output)
+ }
+
+ parser.parseLine("[5/5] writing build rules ...")
+ parser.parseLine("*kati*: verbose msg")
+ parser.flushAction()
+
+ if output.result.Output != "" {
+ t.Errorf("expected no output for last action, but got %q", output.result.Output)
+ }
+
+ output.Expect(t, Counts{
+ TotalActions: 5,
+ RunningActions: 0,
+ StartedActions: 5,
+ FinishedActions: 5,
+ })
+}
+
+func TestKatiExtraIncludes(t *testing.T) {
+ status := &Status{}
+ output := &lastOutput{}
+ status.AddOutput(output)
+
+ parser := &katiOutputParser{
+ st: status.StartTool(),
+ }
+
+ parser.parseLine("[1/1] initializing build system ...")
+ parser.parseLine("[2/5] including out/soong/Android-aosp_arm.mk ...")
+ output.Expect(t, Counts{
+ TotalActions: 5,
+ RunningActions: 1,
+ StartedActions: 2,
+ FinishedActions: 1,
+ })
+
+ parser.parseLine("including a ...")
+
+ output.Expect(t, Counts{
+ TotalActions: 6,
+ RunningActions: 1,
+ StartedActions: 3,
+ FinishedActions: 2,
+ })
+
+ parser.parseLine("including b ...")
+
+ output.Expect(t, Counts{
+ TotalActions: 7,
+ RunningActions: 1,
+ StartedActions: 4,
+ FinishedActions: 3,
+ })
+
+ parser.parseLine("[3/5] finishing build rules ...")
+
+ output.Expect(t, Counts{
+ TotalActions: 7,
+ RunningActions: 1,
+ StartedActions: 5,
+ FinishedActions: 4,
+ })
+}
+
+func TestKatiFailOnError(t *testing.T) {
+ status := &Status{}
+ output := &lastOutput{}
+ status.AddOutput(output)
+
+ parser := &katiOutputParser{
+ st: status.StartTool(),
+ }
+
+ parser.parseLine("[1/1] initializing build system ...")
+ parser.parseLine("[2/5] inclduing out/soong/Android-aosp_arm.mk ...")
+ parser.parseLine("build/make/tools/Android.mk:19: error: testing")
+ parser.flushAction()
+
+ if output.result.Error == nil {
+ t.Errorf("Expected the last action to be marked as an error")
+ }
+}
diff --git a/ui/status/log.go b/ui/status/log.go
new file mode 100644
index 00000000..921aa440
--- /dev/null
+++ b/ui/status/log.go
@@ -0,0 +1,136 @@
+// 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 (
+ "android/soong/ui/logger"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "strings"
+)
+
+type verboseLog struct {
+ w io.WriteCloser
+}
+
+func NewVerboseLog(log logger.Logger, filename string) StatusOutput {
+ if !strings.HasSuffix(filename, ".gz") {
+ filename += ".gz"
+ }
+
+ f, err := logger.CreateFileWithRotation(filename, 5)
+ if err != nil {
+ log.Println("Failed to create verbose log file:", err)
+ return nil
+ }
+
+ w := gzip.NewWriter(f)
+
+ return &verboseLog{
+ w: w,
+ }
+}
+
+func (v *verboseLog) StartAction(action *Action, counts Counts) {}
+
+func (v *verboseLog) FinishAction(result ActionResult, counts Counts) {
+ cmd := result.Command
+ if cmd == "" {
+ cmd = result.Description
+ }
+
+ fmt.Fprintf(v.w, "[%d/%d] %s\n", counts.FinishedActions, counts.TotalActions, cmd)
+
+ if result.Error != nil {
+ fmt.Fprintf(v.w, "FAILED: %s\n", strings.Join(result.Outputs, " "))
+ }
+
+ if result.Output != "" {
+ fmt.Fprintln(v.w, result.Output)
+ }
+}
+
+func (v *verboseLog) Flush() {
+ v.w.Close()
+}
+
+func (v *verboseLog) Message(level MsgLevel, message string) {
+ fmt.Fprintf(v.w, "%s%s\n", level.Prefix(), message)
+}
+
+type errorLog struct {
+ w io.WriteCloser
+
+ empty bool
+}
+
+func NewErrorLog(log logger.Logger, filename string) StatusOutput {
+ f, err := logger.CreateFileWithRotation(filename, 5)
+ if err != nil {
+ log.Println("Failed to create error log file:", err)
+ return nil
+ }
+
+ return &errorLog{
+ w: f,
+ empty: true,
+ }
+}
+
+func (e *errorLog) StartAction(action *Action, counts Counts) {}
+
+func (e *errorLog) FinishAction(result ActionResult, counts Counts) {
+ if result.Error == nil {
+ return
+ }
+
+ cmd := result.Command
+ if cmd == "" {
+ cmd = result.Description
+ }
+
+ if !e.empty {
+ fmt.Fprintf(e.w, "\n\n")
+ }
+ e.empty = false
+
+ fmt.Fprintf(e.w, "FAILED: %s\n", result.Description)
+ if len(result.Outputs) > 0 {
+ fmt.Fprintf(e.w, "Outputs: %s\n", strings.Join(result.Outputs, " "))
+ }
+ fmt.Fprintf(e.w, "Error: %s\n", result.Error)
+ if result.Command != "" {
+ fmt.Fprintf(e.w, "Command: %s\n", result.Command)
+ }
+ fmt.Fprintf(e.w, "Output:\n%s\n", result.Output)
+}
+
+func (e *errorLog) Flush() {
+ e.w.Close()
+}
+
+func (e *errorLog) Message(level MsgLevel, message string) {
+ if level < ErrorLvl {
+ return
+ }
+
+ if !e.empty {
+ fmt.Fprintf(e.w, "\n\n")
+ }
+ e.empty = false
+
+ fmt.Fprintf(e.w, "error: %s\n", message)
+}
diff --git a/ui/status/ninja.go b/ui/status/ninja.go
new file mode 100644
index 00000000..4cce6815
--- /dev/null
+++ b/ui/status/ninja.go
@@ -0,0 +1,153 @@
+// 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(ctx, status, fifo)
+}
+
+func ninjaReader(ctx logger.Logger, status ToolStatus, fifo string) {
+ defer os.Remove(fifo)
+
+ f, err := os.Open(fifo)
+ if err != nil {
+ ctx.Fatal("Failed to open fifo:", err)
+ }
+ defer f.Close()
+
+ r := bufio.NewReader(f)
+
+ running := map[uint32]*Action{}
+
+ for {
+ size, err := readVarInt(r)
+ if err != nil {
+ if err != io.EOF {
+ ctx.Println("Got error reading from ninja:", err)
+ }
+ return
+ }
+
+ buf := make([]byte, size)
+ _, err = io.ReadFull(r, buf)
+ if err != nil {
+ if err == io.EOF {
+ ctx.Printf("Missing message of size %d from ninja\n", size)
+ } else {
+ ctx.Fatal("Got error reading from ninja:", err)
+ }
+ return
+ }
+
+ msg := &ninja_frontend.Status{}
+ err = proto.Unmarshal(buf, msg)
+ if err != nil {
+ ctx.Printf("Error reading message from ninja: %v\n", 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
+}
diff --git a/ui/status/ninja_frontend/README b/ui/status/ninja_frontend/README
new file mode 100644
index 00000000..8c4b4510
--- /dev/null
+++ b/ui/status/ninja_frontend/README
@@ -0,0 +1,3 @@
+This comes from https://android.googlesource.com/platform/external/ninja/+/master/src/frontend.proto
+
+The only difference is the specification of a go_package. To regenerate frontend.pb.go, run regen.sh.
diff --git a/ui/status/ninja_frontend/frontend.pb.go b/ui/status/ninja_frontend/frontend.pb.go
new file mode 100644
index 00000000..7c05eed4
--- /dev/null
+++ b/ui/status/ninja_frontend/frontend.pb.go
@@ -0,0 +1,510 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: frontend.proto
+
+package ninja_frontend
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type Status_Message_Level int32
+
+const (
+ Status_Message_INFO Status_Message_Level = 0
+ Status_Message_WARNING Status_Message_Level = 1
+ Status_Message_ERROR Status_Message_Level = 2
+)
+
+var Status_Message_Level_name = map[int32]string{
+ 0: "INFO",
+ 1: "WARNING",
+ 2: "ERROR",
+}
+var Status_Message_Level_value = map[string]int32{
+ "INFO": 0,
+ "WARNING": 1,
+ "ERROR": 2,
+}
+
+func (x Status_Message_Level) Enum() *Status_Message_Level {
+ p := new(Status_Message_Level)
+ *p = x
+ return p
+}
+func (x Status_Message_Level) String() string {
+ return proto.EnumName(Status_Message_Level_name, int32(x))
+}
+func (x *Status_Message_Level) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(Status_Message_Level_value, data, "Status_Message_Level")
+ if err != nil {
+ return err
+ }
+ *x = Status_Message_Level(value)
+ return nil
+}
+func (Status_Message_Level) EnumDescriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 5, 0}
+}
+
+type Status struct {
+ TotalEdges *Status_TotalEdges `protobuf:"bytes,1,opt,name=total_edges,json=totalEdges" json:"total_edges,omitempty"`
+ BuildStarted *Status_BuildStarted `protobuf:"bytes,2,opt,name=build_started,json=buildStarted" json:"build_started,omitempty"`
+ BuildFinished *Status_BuildFinished `protobuf:"bytes,3,opt,name=build_finished,json=buildFinished" json:"build_finished,omitempty"`
+ EdgeStarted *Status_EdgeStarted `protobuf:"bytes,4,opt,name=edge_started,json=edgeStarted" json:"edge_started,omitempty"`
+ EdgeFinished *Status_EdgeFinished `protobuf:"bytes,5,opt,name=edge_finished,json=edgeFinished" json:"edge_finished,omitempty"`
+ Message *Status_Message `protobuf:"bytes,6,opt,name=message" json:"message,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status) Reset() { *m = Status{} }
+func (m *Status) String() string { return proto.CompactTextString(m) }
+func (*Status) ProtoMessage() {}
+func (*Status) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0}
+}
+func (m *Status) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status.Unmarshal(m, b)
+}
+func (m *Status) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status.Marshal(b, m, deterministic)
+}
+func (dst *Status) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status.Merge(dst, src)
+}
+func (m *Status) XXX_Size() int {
+ return xxx_messageInfo_Status.Size(m)
+}
+func (m *Status) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status proto.InternalMessageInfo
+
+func (m *Status) GetTotalEdges() *Status_TotalEdges {
+ if m != nil {
+ return m.TotalEdges
+ }
+ return nil
+}
+
+func (m *Status) GetBuildStarted() *Status_BuildStarted {
+ if m != nil {
+ return m.BuildStarted
+ }
+ return nil
+}
+
+func (m *Status) GetBuildFinished() *Status_BuildFinished {
+ if m != nil {
+ return m.BuildFinished
+ }
+ return nil
+}
+
+func (m *Status) GetEdgeStarted() *Status_EdgeStarted {
+ if m != nil {
+ return m.EdgeStarted
+ }
+ return nil
+}
+
+func (m *Status) GetEdgeFinished() *Status_EdgeFinished {
+ if m != nil {
+ return m.EdgeFinished
+ }
+ return nil
+}
+
+func (m *Status) GetMessage() *Status_Message {
+ if m != nil {
+ return m.Message
+ }
+ return nil
+}
+
+type Status_TotalEdges struct {
+ // New value for total edges in the build.
+ TotalEdges *uint32 `protobuf:"varint,1,opt,name=total_edges,json=totalEdges" json:"total_edges,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_TotalEdges) Reset() { *m = Status_TotalEdges{} }
+func (m *Status_TotalEdges) String() string { return proto.CompactTextString(m) }
+func (*Status_TotalEdges) ProtoMessage() {}
+func (*Status_TotalEdges) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 0}
+}
+func (m *Status_TotalEdges) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_TotalEdges.Unmarshal(m, b)
+}
+func (m *Status_TotalEdges) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_TotalEdges.Marshal(b, m, deterministic)
+}
+func (dst *Status_TotalEdges) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_TotalEdges.Merge(dst, src)
+}
+func (m *Status_TotalEdges) XXX_Size() int {
+ return xxx_messageInfo_Status_TotalEdges.Size(m)
+}
+func (m *Status_TotalEdges) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_TotalEdges.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_TotalEdges proto.InternalMessageInfo
+
+func (m *Status_TotalEdges) GetTotalEdges() uint32 {
+ if m != nil && m.TotalEdges != nil {
+ return *m.TotalEdges
+ }
+ return 0
+}
+
+type Status_BuildStarted struct {
+ // Number of jobs Ninja will run in parallel.
+ Parallelism *uint32 `protobuf:"varint,1,opt,name=parallelism" json:"parallelism,omitempty"`
+ // Verbose value passed to ninja.
+ Verbose *bool `protobuf:"varint,2,opt,name=verbose" json:"verbose,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_BuildStarted) Reset() { *m = Status_BuildStarted{} }
+func (m *Status_BuildStarted) String() string { return proto.CompactTextString(m) }
+func (*Status_BuildStarted) ProtoMessage() {}
+func (*Status_BuildStarted) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 1}
+}
+func (m *Status_BuildStarted) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_BuildStarted.Unmarshal(m, b)
+}
+func (m *Status_BuildStarted) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_BuildStarted.Marshal(b, m, deterministic)
+}
+func (dst *Status_BuildStarted) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_BuildStarted.Merge(dst, src)
+}
+func (m *Status_BuildStarted) XXX_Size() int {
+ return xxx_messageInfo_Status_BuildStarted.Size(m)
+}
+func (m *Status_BuildStarted) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_BuildStarted.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_BuildStarted proto.InternalMessageInfo
+
+func (m *Status_BuildStarted) GetParallelism() uint32 {
+ if m != nil && m.Parallelism != nil {
+ return *m.Parallelism
+ }
+ return 0
+}
+
+func (m *Status_BuildStarted) GetVerbose() bool {
+ if m != nil && m.Verbose != nil {
+ return *m.Verbose
+ }
+ return false
+}
+
+type Status_BuildFinished struct {
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_BuildFinished) Reset() { *m = Status_BuildFinished{} }
+func (m *Status_BuildFinished) String() string { return proto.CompactTextString(m) }
+func (*Status_BuildFinished) ProtoMessage() {}
+func (*Status_BuildFinished) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 2}
+}
+func (m *Status_BuildFinished) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_BuildFinished.Unmarshal(m, b)
+}
+func (m *Status_BuildFinished) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_BuildFinished.Marshal(b, m, deterministic)
+}
+func (dst *Status_BuildFinished) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_BuildFinished.Merge(dst, src)
+}
+func (m *Status_BuildFinished) XXX_Size() int {
+ return xxx_messageInfo_Status_BuildFinished.Size(m)
+}
+func (m *Status_BuildFinished) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_BuildFinished.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_BuildFinished proto.InternalMessageInfo
+
+type Status_EdgeStarted struct {
+ // Edge identification number, unique to a Ninja run.
+ Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
+ // Edge start time in milliseconds since Ninja started.
+ StartTime *uint32 `protobuf:"varint,2,opt,name=start_time,json=startTime" json:"start_time,omitempty"`
+ // List of edge inputs.
+ Inputs []string `protobuf:"bytes,3,rep,name=inputs" json:"inputs,omitempty"`
+ // List of edge outputs.
+ Outputs []string `protobuf:"bytes,4,rep,name=outputs" json:"outputs,omitempty"`
+ // Description field from the edge.
+ Desc *string `protobuf:"bytes,5,opt,name=desc" json:"desc,omitempty"`
+ // Command field from the edge.
+ Command *string `protobuf:"bytes,6,opt,name=command" json:"command,omitempty"`
+ // Edge uses console.
+ Console *bool `protobuf:"varint,7,opt,name=console" json:"console,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_EdgeStarted) Reset() { *m = Status_EdgeStarted{} }
+func (m *Status_EdgeStarted) String() string { return proto.CompactTextString(m) }
+func (*Status_EdgeStarted) ProtoMessage() {}
+func (*Status_EdgeStarted) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 3}
+}
+func (m *Status_EdgeStarted) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_EdgeStarted.Unmarshal(m, b)
+}
+func (m *Status_EdgeStarted) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_EdgeStarted.Marshal(b, m, deterministic)
+}
+func (dst *Status_EdgeStarted) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_EdgeStarted.Merge(dst, src)
+}
+func (m *Status_EdgeStarted) XXX_Size() int {
+ return xxx_messageInfo_Status_EdgeStarted.Size(m)
+}
+func (m *Status_EdgeStarted) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_EdgeStarted.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_EdgeStarted proto.InternalMessageInfo
+
+func (m *Status_EdgeStarted) GetId() uint32 {
+ if m != nil && m.Id != nil {
+ return *m.Id
+ }
+ return 0
+}
+
+func (m *Status_EdgeStarted) GetStartTime() uint32 {
+ if m != nil && m.StartTime != nil {
+ return *m.StartTime
+ }
+ return 0
+}
+
+func (m *Status_EdgeStarted) GetInputs() []string {
+ if m != nil {
+ return m.Inputs
+ }
+ return nil
+}
+
+func (m *Status_EdgeStarted) GetOutputs() []string {
+ if m != nil {
+ return m.Outputs
+ }
+ return nil
+}
+
+func (m *Status_EdgeStarted) GetDesc() string {
+ if m != nil && m.Desc != nil {
+ return *m.Desc
+ }
+ return ""
+}
+
+func (m *Status_EdgeStarted) GetCommand() string {
+ if m != nil && m.Command != nil {
+ return *m.Command
+ }
+ return ""
+}
+
+func (m *Status_EdgeStarted) GetConsole() bool {
+ if m != nil && m.Console != nil {
+ return *m.Console
+ }
+ return false
+}
+
+type Status_EdgeFinished struct {
+ // Edge identification number, unique to a Ninja run.
+ Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
+ // Edge end time in milliseconds since Ninja started.
+ EndTime *uint32 `protobuf:"varint,2,opt,name=end_time,json=endTime" json:"end_time,omitempty"`
+ // Exit status (0 for success).
+ Status *int32 `protobuf:"zigzag32,3,opt,name=status" json:"status,omitempty"`
+ // Edge output, may contain ANSI codes.
+ Output *string `protobuf:"bytes,4,opt,name=output" json:"output,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_EdgeFinished) Reset() { *m = Status_EdgeFinished{} }
+func (m *Status_EdgeFinished) String() string { return proto.CompactTextString(m) }
+func (*Status_EdgeFinished) ProtoMessage() {}
+func (*Status_EdgeFinished) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 4}
+}
+func (m *Status_EdgeFinished) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_EdgeFinished.Unmarshal(m, b)
+}
+func (m *Status_EdgeFinished) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_EdgeFinished.Marshal(b, m, deterministic)
+}
+func (dst *Status_EdgeFinished) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_EdgeFinished.Merge(dst, src)
+}
+func (m *Status_EdgeFinished) XXX_Size() int {
+ return xxx_messageInfo_Status_EdgeFinished.Size(m)
+}
+func (m *Status_EdgeFinished) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_EdgeFinished.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_EdgeFinished proto.InternalMessageInfo
+
+func (m *Status_EdgeFinished) GetId() uint32 {
+ if m != nil && m.Id != nil {
+ return *m.Id
+ }
+ return 0
+}
+
+func (m *Status_EdgeFinished) GetEndTime() uint32 {
+ if m != nil && m.EndTime != nil {
+ return *m.EndTime
+ }
+ return 0
+}
+
+func (m *Status_EdgeFinished) GetStatus() int32 {
+ if m != nil && m.Status != nil {
+ return *m.Status
+ }
+ return 0
+}
+
+func (m *Status_EdgeFinished) GetOutput() string {
+ if m != nil && m.Output != nil {
+ return *m.Output
+ }
+ return ""
+}
+
+type Status_Message struct {
+ // Message priority level (INFO, WARNING, or ERROR).
+ Level *Status_Message_Level `protobuf:"varint,1,opt,name=level,enum=ninja.Status_Message_Level,def=0" json:"level,omitempty"`
+ // Info/warning/error message from Ninja.
+ Message *string `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_Message) Reset() { *m = Status_Message{} }
+func (m *Status_Message) String() string { return proto.CompactTextString(m) }
+func (*Status_Message) ProtoMessage() {}
+func (*Status_Message) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 5}
+}
+func (m *Status_Message) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_Message.Unmarshal(m, b)
+}
+func (m *Status_Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_Message.Marshal(b, m, deterministic)
+}
+func (dst *Status_Message) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_Message.Merge(dst, src)
+}
+func (m *Status_Message) XXX_Size() int {
+ return xxx_messageInfo_Status_Message.Size(m)
+}
+func (m *Status_Message) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_Message.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_Message proto.InternalMessageInfo
+
+const Default_Status_Message_Level Status_Message_Level = Status_Message_INFO
+
+func (m *Status_Message) GetLevel() Status_Message_Level {
+ if m != nil && m.Level != nil {
+ return *m.Level
+ }
+ return Default_Status_Message_Level
+}
+
+func (m *Status_Message) GetMessage() string {
+ if m != nil && m.Message != nil {
+ return *m.Message
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*Status)(nil), "ninja.Status")
+ proto.RegisterType((*Status_TotalEdges)(nil), "ninja.Status.TotalEdges")
+ proto.RegisterType((*Status_BuildStarted)(nil), "ninja.Status.BuildStarted")
+ proto.RegisterType((*Status_BuildFinished)(nil), "ninja.Status.BuildFinished")
+ proto.RegisterType((*Status_EdgeStarted)(nil), "ninja.Status.EdgeStarted")
+ proto.RegisterType((*Status_EdgeFinished)(nil), "ninja.Status.EdgeFinished")
+ proto.RegisterType((*Status_Message)(nil), "ninja.Status.Message")
+ proto.RegisterEnum("ninja.Status_Message_Level", Status_Message_Level_name, Status_Message_Level_value)
+}
+
+func init() { proto.RegisterFile("frontend.proto", fileDescriptor_frontend_5a49d9b15a642005) }
+
+var fileDescriptor_frontend_5a49d9b15a642005 = []byte{
+ // 496 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0xd1, 0x6e, 0xd3, 0x30,
+ 0x14, 0xa5, 0x69, 0xd3, 0x34, 0x37, 0x6d, 0x28, 0x96, 0x40, 0x59, 0x10, 0xa2, 0xda, 0xd3, 0x78,
+ 0x20, 0x48, 0xbc, 0x20, 0x10, 0x12, 0xa2, 0xd2, 0x06, 0x43, 0xd0, 0x49, 0xde, 0x24, 0x24, 0x5e,
+ 0xaa, 0x74, 0xf6, 0x86, 0x51, 0xe2, 0x54, 0xb1, 0xbb, 0x5f, 0xe0, 0x7f, 0x78, 0xe0, 0xfb, 0x90,
+ 0xaf, 0xed, 0x2c, 0x65, 0x7b, 0xcb, 0xf1, 0x3d, 0xe7, 0xde, 0x73, 0x8f, 0x1d, 0x48, 0xaf, 0xda,
+ 0x46, 0x6a, 0x2e, 0x59, 0xb1, 0x6d, 0x1b, 0xdd, 0x90, 0x50, 0x0a, 0xf9, 0xab, 0x3c, 0xfc, 0x13,
+ 0xc1, 0xf8, 0x5c, 0x97, 0x7a, 0xa7, 0xc8, 0x5b, 0x48, 0x74, 0xa3, 0xcb, 0x6a, 0xcd, 0xd9, 0x35,
+ 0x57, 0xd9, 0x60, 0x31, 0x38, 0x4a, 0x5e, 0x67, 0x05, 0xf2, 0x0a, 0xcb, 0x29, 0x2e, 0x0c, 0xe1,
+ 0xd8, 0xd4, 0x29, 0xe8, 0xee, 0x9b, 0x7c, 0x80, 0xd9, 0x66, 0x27, 0x2a, 0xb6, 0x56, 0xba, 0x6c,
+ 0x35, 0x67, 0x59, 0x80, 0xe2, 0x7c, 0x5f, 0xbc, 0x34, 0x94, 0x73, 0xcb, 0xa0, 0xd3, 0x4d, 0x0f,
+ 0x91, 0x25, 0xa4, 0xb6, 0xc1, 0x95, 0x90, 0x42, 0xfd, 0xe4, 0x2c, 0x1b, 0x62, 0x87, 0xa7, 0xf7,
+ 0x74, 0x38, 0x71, 0x14, 0x6a, 0x67, 0x7a, 0x48, 0xde, 0xc3, 0xd4, 0x38, 0xef, 0x3c, 0x8c, 0xb0,
+ 0xc3, 0xc1, 0x7e, 0x07, 0xe3, 0xd7, 0x5b, 0x48, 0xf8, 0x2d, 0x30, 0x2b, 0xa0, 0xba, 0x33, 0x10,
+ 0xde, 0xb7, 0x82, 0x91, 0x77, 0xf3, 0x71, 0x5c, 0x37, 0xfe, 0x15, 0x44, 0x35, 0x57, 0xaa, 0xbc,
+ 0xe6, 0xd9, 0x18, 0xa5, 0x8f, 0xf7, 0xa5, 0xdf, 0x6c, 0x91, 0x7a, 0x56, 0xfe, 0x12, 0xe0, 0x36,
+ 0x4e, 0xf2, 0xfc, 0x6e, 0xfa, 0xb3, 0x7e, 0xc6, 0xf9, 0x17, 0x98, 0xf6, 0x03, 0x24, 0x0b, 0x48,
+ 0xb6, 0x65, 0x5b, 0x56, 0x15, 0xaf, 0x84, 0xaa, 0x9d, 0xa0, 0x7f, 0x44, 0x32, 0x88, 0x6e, 0x78,
+ 0xbb, 0x69, 0x14, 0xc7, 0xfb, 0x98, 0x50, 0x0f, 0xf3, 0x87, 0x30, 0xdb, 0x8b, 0x32, 0xff, 0x3b,
+ 0x80, 0xa4, 0x17, 0x0d, 0x49, 0x21, 0x10, 0xcc, 0xf5, 0x0c, 0x04, 0x23, 0xcf, 0x00, 0x30, 0xd6,
+ 0xb5, 0x16, 0xb5, 0xed, 0x36, 0xa3, 0x31, 0x9e, 0x5c, 0x88, 0x9a, 0x93, 0x27, 0x30, 0x16, 0x72,
+ 0xbb, 0xd3, 0x2a, 0x1b, 0x2e, 0x86, 0x47, 0x31, 0x75, 0xc8, 0x38, 0x68, 0x76, 0x1a, 0x0b, 0x23,
+ 0x2c, 0x78, 0x48, 0x08, 0x8c, 0x18, 0x57, 0x97, 0x98, 0x72, 0x4c, 0xf1, 0xdb, 0xb0, 0x2f, 0x9b,
+ 0xba, 0x2e, 0x25, 0xc3, 0x04, 0x63, 0xea, 0xa1, 0xad, 0x48, 0xd5, 0x54, 0x3c, 0x8b, 0xec, 0x26,
+ 0x0e, 0xe6, 0x02, 0xa6, 0xfd, 0x3b, 0xb9, 0x63, 0xfc, 0x00, 0x26, 0x5c, 0xb2, 0xbe, 0xed, 0x88,
+ 0x4b, 0xe6, 0x4d, 0x2b, 0xbc, 0x1a, 0x7c, 0x6b, 0x8f, 0xa8, 0x43, 0xe6, 0xdc, 0xba, 0xc4, 0x17,
+ 0x14, 0x53, 0x87, 0xf2, 0xdf, 0x03, 0x88, 0xdc, 0x25, 0x92, 0x37, 0x10, 0x56, 0xfc, 0x86, 0x57,
+ 0x38, 0x29, 0xfd, 0xff, 0x99, 0x3a, 0x56, 0xf1, 0xd5, 0x50, 0xde, 0x8d, 0x4e, 0x57, 0x27, 0x67,
+ 0xd4, 0xf2, 0xcd, 0x26, 0xfe, 0x95, 0x04, 0x76, 0x47, 0x07, 0x0f, 0x5f, 0x40, 0x88, 0x7c, 0x32,
+ 0x01, 0x54, 0xcc, 0x1f, 0x90, 0x04, 0xa2, 0xef, 0x1f, 0xe9, 0xea, 0x74, 0xf5, 0x69, 0x3e, 0x20,
+ 0x31, 0x84, 0xc7, 0x94, 0x9e, 0xd1, 0x79, 0xb0, 0x24, 0x9f, 0x87, 0x3f, 0x52, 0x9c, 0xb8, 0xf6,
+ 0x7f, 0xf5, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2e, 0x8c, 0xef, 0xcb, 0xe0, 0x03, 0x00, 0x00,
+}
diff --git a/ui/status/ninja_frontend/frontend.proto b/ui/status/ninja_frontend/frontend.proto
new file mode 100644
index 00000000..13fd535d
--- /dev/null
+++ b/ui/status/ninja_frontend/frontend.proto
@@ -0,0 +1,84 @@
+// Copyright 2017 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.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package ninja;
+option go_package = "ninja_frontend";
+
+message Status {
+ message TotalEdges {
+ // New value for total edges in the build.
+ optional uint32 total_edges = 1;
+ }
+
+ message BuildStarted {
+ // Number of jobs Ninja will run in parallel.
+ optional uint32 parallelism = 1;
+ // Verbose value passed to ninja.
+ optional bool verbose = 2;
+ }
+
+ message BuildFinished {
+ }
+
+ message EdgeStarted {
+ // Edge identification number, unique to a Ninja run.
+ optional uint32 id = 1;
+ // Edge start time in milliseconds since Ninja started.
+ optional uint32 start_time = 2;
+ // List of edge inputs.
+ repeated string inputs = 3;
+ // List of edge outputs.
+ repeated string outputs = 4;
+ // Description field from the edge.
+ optional string desc = 5;
+ // Command field from the edge.
+ optional string command = 6;
+ // Edge uses console.
+ optional bool console = 7;
+ }
+
+ message EdgeFinished {
+ // Edge identification number, unique to a Ninja run.
+ optional uint32 id = 1;
+ // Edge end time in milliseconds since Ninja started.
+ optional uint32 end_time = 2;
+ // Exit status (0 for success).
+ optional sint32 status = 3;
+ // Edge output, may contain ANSI codes.
+ optional string output = 4;
+ }
+
+ message Message {
+ enum Level {
+ INFO = 0;
+ WARNING = 1;
+ ERROR = 2;
+ }
+ // Message priority level (INFO, WARNING, or ERROR).
+ optional Level level = 1 [default = INFO];
+ // Info/warning/error message from Ninja.
+ optional string message = 2;
+ }
+
+ optional TotalEdges total_edges = 1;
+ optional BuildStarted build_started = 2;
+ optional BuildFinished build_finished = 3;
+ optional EdgeStarted edge_started = 4;
+ optional EdgeFinished edge_finished = 5;
+ optional Message message = 6;
+}
diff --git a/ui/status/ninja_frontend/regen.sh b/ui/status/ninja_frontend/regen.sh
new file mode 100755
index 00000000..d270731a
--- /dev/null
+++ b/ui/status/ninja_frontend/regen.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+aprotoc --go_out=paths=source_relative:. frontend.proto
diff --git a/ui/status/status.go b/ui/status/status.go
new file mode 100644
index 00000000..c851d7f5
--- /dev/null
+++ b/ui/status/status.go
@@ -0,0 +1,340 @@
+// 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 tracks actions run by various tools, combining the counts
+// (total actions, currently running, started, finished), and giving that to
+// multiple outputs.
+package status
+
+import (
+ "sync"
+)
+
+// Action describes an action taken (or as Ninja calls them, Edges).
+type Action struct {
+ // Description is a shorter, more readable form of the command, meant
+ // for users. It's optional, but one of either Description or Command
+ // should be set.
+ Description string
+
+ // Outputs is the (optional) list of outputs. Usually these are files,
+ // but they can be any string.
+ Outputs []string
+
+ // Command is the actual command line executed to perform the action.
+ // It's optional, but one of either Description or Command should be
+ // set.
+ Command string
+}
+
+// ActionResult describes the result of running an Action.
+type ActionResult struct {
+ // Action is a pointer to the original Action struct.
+ *Action
+
+ // Output is the output produced by the command (usually stdout&stderr
+ // for Actions that run commands)
+ Output string
+
+ // Error is nil if the Action succeeded, or set to an error if it
+ // failed.
+ Error error
+}
+
+// Counts describes the number of actions in each state
+type Counts struct {
+ // TotalActions is the total number of expected changes. This can
+ // generally change up or down during a build, but it should never go
+ // below the number of StartedActions
+ TotalActions int
+
+ // RunningActions are the number of actions that are currently running
+ // -- the number that have called StartAction, but not FinishAction.
+ RunningActions int
+
+ // StartedActions are the number of actions that have been started with
+ // StartAction.
+ StartedActions int
+
+ // FinishedActions are the number of actions that have been finished
+ // with FinishAction.
+ FinishedActions int
+}
+
+// ToolStatus is the interface used by tools to report on their Actions, and to
+// present other information through a set of messaging functions.
+type ToolStatus interface {
+ // SetTotalActions sets the expected total number of actions that will
+ // be started by this tool.
+ //
+ // This call be will ignored if it sets a number that is less than the
+ // current number of started actions.
+ SetTotalActions(total int)
+
+ // StartAction specifies that the associated action has been started by
+ // the tool.
+ //
+ // A specific *Action should not be specified to StartAction more than
+ // once, even if the previous action has already been finished, and the
+ // contents rewritten.
+ //
+ // Do not re-use *Actions between different ToolStatus interfaces
+ // either.
+ StartAction(action *Action)
+
+ // FinishAction specifies the result of a particular Action.
+ //
+ // The *Action embedded in the ActionResult structure must have already
+ // been passed to StartAction (on this interface).
+ //
+ // Do not call FinishAction twice for the same *Action.
+ FinishAction(result ActionResult)
+
+ // Verbose takes a non-important message that is never printed to the
+ // screen, but is in the verbose build log, etc
+ Verbose(msg string)
+ // Status takes a less important message that may be printed to the
+ // screen, but overwritten by another status message. The full message
+ // will still appear in the verbose build log.
+ Status(msg string)
+ // Print takes an message and displays it to the screen and other
+ // output logs, etc.
+ Print(msg string)
+ // Error is similar to Print, but treats it similarly to a failed
+ // action, showing it in the error logs, etc.
+ Error(msg string)
+
+ // Finish marks the end of all Actions being run by this tool.
+ //
+ // SetTotalEdges, StartAction, and FinishAction should not be called
+ // after Finish.
+ Finish()
+}
+
+// MsgLevel specifies the importance of a particular log message. See the
+// descriptions in ToolStatus: Verbose, Status, Print, Error.
+type MsgLevel int
+
+const (
+ VerboseLvl MsgLevel = iota
+ StatusLvl
+ PrintLvl
+ ErrorLvl
+)
+
+func (l MsgLevel) Prefix() string {
+ switch l {
+ case VerboseLvl:
+ return "verbose: "
+ case StatusLvl:
+ return "status: "
+ case PrintLvl:
+ return ""
+ case ErrorLvl:
+ return "error: "
+ default:
+ panic("Unknown message level")
+ }
+}
+
+// StatusOutput is the interface used to get status information as a Status
+// output.
+//
+// All of the functions here are guaranteed to be called by Status while
+// holding it's internal lock, so it's safe to assume a single caller at any
+// time, and that the ordering of calls will be correct. It is not safe to call
+// back into the Status, or one of its ToolStatus interfaces.
+type StatusOutput interface {
+ // StartAction will be called once every time ToolStatus.StartAction is
+ // called. counts will include the current counters across all
+ // ToolStatus instances, including ones that have been finished.
+ StartAction(action *Action, counts Counts)
+
+ // FinishAction will be called once every time ToolStatus.FinishAction
+ // is called. counts will include the current counters across all
+ // ToolStatus instances, including ones that have been finished.
+ FinishAction(result ActionResult, counts Counts)
+
+ // Message is the equivalent of ToolStatus.Verbose/Status/Print/Error,
+ // but the level is specified as an argument.
+ Message(level MsgLevel, msg string)
+
+ // Flush is called when your outputs should be flushed / closed. No
+ // output is expected after this call.
+ Flush()
+}
+
+// Status is the multiplexer / accumulator between ToolStatus instances (via
+// StartTool) and StatusOutputs (via AddOutput). There's generally one of these
+// per build process (though tools like multiproduct_kati may have multiple
+// independent versions).
+type Status struct {
+ counts Counts
+ outputs []StatusOutput
+
+ // Protects counts and outputs, and allows each output to
+ // expect only a single caller at a time.
+ lock sync.Mutex
+}
+
+// AddOutput attaches an output to this object. It's generally expected that an
+// output is attached to a single Status instance.
+func (s *Status) AddOutput(output StatusOutput) {
+ if output == nil {
+ return
+ }
+
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.outputs = append(s.outputs, output)
+}
+
+// StartTool returns a new ToolStatus instance to report the status of a tool.
+func (s *Status) StartTool() ToolStatus {
+ return &toolStatus{
+ status: s,
+ }
+}
+
+// Finish will call Flush on all the outputs, generally flushing or closing all
+// of their outputs. Do not call any other functions on this instance or any
+// associated ToolStatus instances after this has been called.
+func (s *Status) Finish() {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ for _, o := range s.outputs {
+ o.Flush()
+ }
+}
+
+func (s *Status) updateTotalActions(diff int) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.counts.TotalActions += diff
+}
+
+func (s *Status) startAction(action *Action) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.counts.RunningActions += 1
+ s.counts.StartedActions += 1
+
+ for _, o := range s.outputs {
+ o.StartAction(action, s.counts)
+ }
+}
+
+func (s *Status) finishAction(result ActionResult) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.counts.RunningActions -= 1
+ s.counts.FinishedActions += 1
+
+ for _, o := range s.outputs {
+ o.FinishAction(result, s.counts)
+ }
+}
+
+func (s *Status) message(level MsgLevel, msg string) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ for _, o := range s.outputs {
+ o.Message(level, msg)
+ }
+}
+
+type toolStatus struct {
+ status *Status
+
+ counts Counts
+ // Protects counts
+ lock sync.Mutex
+}
+
+var _ ToolStatus = (*toolStatus)(nil)
+
+func (d *toolStatus) SetTotalActions(total int) {
+ diff := 0
+
+ d.lock.Lock()
+ if total >= d.counts.StartedActions && total != d.counts.TotalActions {
+ diff = total - d.counts.TotalActions
+ d.counts.TotalActions = total
+ }
+ d.lock.Unlock()
+
+ if diff != 0 {
+ d.status.updateTotalActions(diff)
+ }
+}
+
+func (d *toolStatus) StartAction(action *Action) {
+ totalDiff := 0
+
+ d.lock.Lock()
+ d.counts.RunningActions += 1
+ d.counts.StartedActions += 1
+
+ if d.counts.StartedActions > d.counts.TotalActions {
+ totalDiff = d.counts.StartedActions - d.counts.TotalActions
+ d.counts.TotalActions = d.counts.StartedActions
+ }
+ d.lock.Unlock()
+
+ if totalDiff != 0 {
+ d.status.updateTotalActions(totalDiff)
+ }
+ d.status.startAction(action)
+}
+
+func (d *toolStatus) FinishAction(result ActionResult) {
+ d.lock.Lock()
+ d.counts.RunningActions -= 1
+ d.counts.FinishedActions += 1
+ d.lock.Unlock()
+
+ d.status.finishAction(result)
+}
+
+func (d *toolStatus) Verbose(msg string) {
+ d.status.message(VerboseLvl, msg)
+}
+func (d *toolStatus) Status(msg string) {
+ d.status.message(StatusLvl, msg)
+}
+func (d *toolStatus) Print(msg string) {
+ d.status.message(PrintLvl, msg)
+}
+func (d *toolStatus) Error(msg string) {
+ d.status.message(ErrorLvl, msg)
+}
+
+func (d *toolStatus) Finish() {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ if d.counts.TotalActions != d.counts.StartedActions {
+ d.status.updateTotalActions(d.counts.StartedActions - d.counts.TotalActions)
+ }
+
+ // TODO: update status to correct running/finished edges?
+ d.counts.RunningActions = 0
+ d.counts.TotalActions = d.counts.StartedActions
+}
diff --git a/ui/status/status_test.go b/ui/status/status_test.go
new file mode 100644
index 00000000..e62785f4
--- /dev/null
+++ b/ui/status/status_test.go
@@ -0,0 +1,166 @@
+// 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 "testing"
+
+type counterOutput Counts
+
+func (c *counterOutput) StartAction(action *Action, counts Counts) {
+ *c = counterOutput(counts)
+}
+func (c *counterOutput) FinishAction(result ActionResult, counts Counts) {
+ *c = counterOutput(counts)
+}
+func (c counterOutput) Message(level MsgLevel, msg string) {}
+func (c counterOutput) Flush() {}
+
+func (c counterOutput) Expect(t *testing.T, counts Counts) {
+ if Counts(c) == counts {
+ return
+ }
+ t.Helper()
+
+ if c.TotalActions != counts.TotalActions {
+ t.Errorf("Expected %d total edges, but got %d", counts.TotalActions, c.TotalActions)
+ }
+ if c.RunningActions != counts.RunningActions {
+ t.Errorf("Expected %d running edges, but got %d", counts.RunningActions, c.RunningActions)
+ }
+ if c.StartedActions != counts.StartedActions {
+ t.Errorf("Expected %d started edges, but got %d", counts.StartedActions, c.StartedActions)
+ }
+ if c.FinishedActions != counts.FinishedActions {
+ t.Errorf("Expected %d finished edges, but got %d", counts.FinishedActions, c.FinishedActions)
+ }
+}
+
+func TestBasicUse(t *testing.T) {
+ status := &Status{}
+ counts := &counterOutput{}
+ status.AddOutput(counts)
+ s := status.StartTool()
+
+ s.SetTotalActions(2)
+
+ a := &Action{}
+ s.StartAction(a)
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 1,
+ StartedActions: 1,
+ FinishedActions: 0,
+ })
+
+ s.FinishAction(ActionResult{Action: a})
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 0,
+ StartedActions: 1,
+ FinishedActions: 1,
+ })
+
+ a = &Action{}
+ s.StartAction(a)
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 1,
+ StartedActions: 2,
+ FinishedActions: 1,
+ })
+
+ s.FinishAction(ActionResult{Action: a})
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 0,
+ StartedActions: 2,
+ FinishedActions: 2,
+ })
+}
+
+// For when a tool claims to have 2 actions, but finishes after one.
+func TestFinishEarly(t *testing.T) {
+ status := &Status{}
+ counts := &counterOutput{}
+ status.AddOutput(counts)
+ s := status.StartTool()
+
+ s.SetTotalActions(2)
+
+ a := &Action{}
+ s.StartAction(a)
+ s.FinishAction(ActionResult{Action: a})
+ s.Finish()
+
+ s = status.StartTool()
+ s.SetTotalActions(2)
+
+ a = &Action{}
+ s.StartAction(a)
+
+ counts.Expect(t, Counts{
+ TotalActions: 3,
+ RunningActions: 1,
+ StartedActions: 2,
+ FinishedActions: 1,
+ })
+}
+
+// For when a tool claims to have 1 action, but starts two.
+func TestExtraActions(t *testing.T) {
+ status := &Status{}
+ counts := &counterOutput{}
+ status.AddOutput(counts)
+ s := status.StartTool()
+
+ s.SetTotalActions(1)
+
+ s.StartAction(&Action{})
+ s.StartAction(&Action{})
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 2,
+ StartedActions: 2,
+ FinishedActions: 0,
+ })
+}
+
+// When a tool calls Finish() with a running Action
+func TestRunningWhenFinished(t *testing.T) {
+ status := &Status{}
+ counts := &counterOutput{}
+ status.AddOutput(counts)
+
+ s := status.StartTool()
+ s.SetTotalActions(1)
+ s.StartAction(&Action{})
+ s.Finish()
+
+ s = status.StartTool()
+ s.SetTotalActions(1)
+ s.StartAction(&Action{})
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 2,
+ StartedActions: 2,
+ FinishedActions: 0,
+ })
+}