aboutsummaryrefslogtreecommitdiffstats
path: root/ui/status
diff options
context:
space:
mode:
Diffstat (limited to 'ui/status')
-rw-r--r--ui/status/Android.bp12
-rw-r--r--ui/status/build_error_proto/build_error.pb.go175
-rw-r--r--ui/status/build_error_proto/build_error.proto46
-rwxr-xr-xui/status/build_error_proto/regen.sh3
-rw-r--r--ui/status/critical_path.go154
-rw-r--r--ui/status/critical_path_test.go166
-rw-r--r--ui/status/log.go80
-rw-r--r--ui/status/ninja.go1
-rw-r--r--ui/status/status.go7
-rw-r--r--ui/status/status_test.go5
10 files changed, 641 insertions, 8 deletions
diff --git a/ui/status/Android.bp b/ui/status/Android.bp
index 901a7130..ec929b34 100644
--- a/ui/status/Android.bp
+++ b/ui/status/Android.bp
@@ -19,14 +19,17 @@ bootstrap_go_package {
"golang-protobuf-proto",
"soong-ui-logger",
"soong-ui-status-ninja_frontend",
+ "soong-ui-status-build_error_proto",
],
srcs: [
+ "critical_path.go",
"kati.go",
"log.go",
"ninja.go",
"status.go",
],
testSrcs: [
+ "critical_path_test.go",
"kati_test.go",
"ninja_test.go",
"status_test.go",
@@ -41,3 +44,12 @@ bootstrap_go_package {
"ninja_frontend/frontend.pb.go",
],
}
+
+bootstrap_go_package {
+ name: "soong-ui-status-build_error_proto",
+ pkgPath: "android/soong/ui/status/build_error_proto",
+ deps: ["golang-protobuf-proto"],
+ srcs: [
+ "build_error_proto/build_error.pb.go",
+ ],
+}
diff --git a/ui/status/build_error_proto/build_error.pb.go b/ui/status/build_error_proto/build_error.pb.go
new file mode 100644
index 00000000..d4d0a6ea
--- /dev/null
+++ b/ui/status/build_error_proto/build_error.pb.go
@@ -0,0 +1,175 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: build_error.proto
+
+package soong_build_error_proto
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ 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.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type BuildError struct {
+ // List of error messages of the overall build. The error messages
+ // are not associated with a build action.
+ ErrorMessages []string `protobuf:"bytes,1,rep,name=error_messages,json=errorMessages" json:"error_messages,omitempty"`
+ // List of build action errors.
+ ActionErrors []*BuildActionError `protobuf:"bytes,2,rep,name=action_errors,json=actionErrors" json:"action_errors,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *BuildError) Reset() { *m = BuildError{} }
+func (m *BuildError) String() string { return proto.CompactTextString(m) }
+func (*BuildError) ProtoMessage() {}
+func (*BuildError) Descriptor() ([]byte, []int) {
+ return fileDescriptor_a2e15b05802a5501, []int{0}
+}
+
+func (m *BuildError) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_BuildError.Unmarshal(m, b)
+}
+func (m *BuildError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_BuildError.Marshal(b, m, deterministic)
+}
+func (m *BuildError) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_BuildError.Merge(m, src)
+}
+func (m *BuildError) XXX_Size() int {
+ return xxx_messageInfo_BuildError.Size(m)
+}
+func (m *BuildError) XXX_DiscardUnknown() {
+ xxx_messageInfo_BuildError.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_BuildError proto.InternalMessageInfo
+
+func (m *BuildError) GetErrorMessages() []string {
+ if m != nil {
+ return m.ErrorMessages
+ }
+ return nil
+}
+
+func (m *BuildError) GetActionErrors() []*BuildActionError {
+ if m != nil {
+ return m.ActionErrors
+ }
+ return nil
+}
+
+// Build is composed of a list of build action. There can be a set of build
+// actions that can failed.
+type BuildActionError struct {
+ // Description of the command.
+ Description *string `protobuf:"bytes,1,opt,name=description" json:"description,omitempty"`
+ // The command name that raised the error.
+ Command *string `protobuf:"bytes,2,opt,name=command" json:"command,omitempty"`
+ // The command output stream.
+ Output *string `protobuf:"bytes,3,opt,name=output" json:"output,omitempty"`
+ // List of artifacts (i.e. files) that was produced by the command.
+ Artifacts []string `protobuf:"bytes,4,rep,name=artifacts" json:"artifacts,omitempty"`
+ // The error string produced by the build action.
+ Error *string `protobuf:"bytes,5,opt,name=error" json:"error,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *BuildActionError) Reset() { *m = BuildActionError{} }
+func (m *BuildActionError) String() string { return proto.CompactTextString(m) }
+func (*BuildActionError) ProtoMessage() {}
+func (*BuildActionError) Descriptor() ([]byte, []int) {
+ return fileDescriptor_a2e15b05802a5501, []int{1}
+}
+
+func (m *BuildActionError) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_BuildActionError.Unmarshal(m, b)
+}
+func (m *BuildActionError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_BuildActionError.Marshal(b, m, deterministic)
+}
+func (m *BuildActionError) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_BuildActionError.Merge(m, src)
+}
+func (m *BuildActionError) XXX_Size() int {
+ return xxx_messageInfo_BuildActionError.Size(m)
+}
+func (m *BuildActionError) XXX_DiscardUnknown() {
+ xxx_messageInfo_BuildActionError.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_BuildActionError proto.InternalMessageInfo
+
+func (m *BuildActionError) GetDescription() string {
+ if m != nil && m.Description != nil {
+ return *m.Description
+ }
+ return ""
+}
+
+func (m *BuildActionError) GetCommand() string {
+ if m != nil && m.Command != nil {
+ return *m.Command
+ }
+ return ""
+}
+
+func (m *BuildActionError) GetOutput() string {
+ if m != nil && m.Output != nil {
+ return *m.Output
+ }
+ return ""
+}
+
+func (m *BuildActionError) GetArtifacts() []string {
+ if m != nil {
+ return m.Artifacts
+ }
+ return nil
+}
+
+func (m *BuildActionError) GetError() string {
+ if m != nil && m.Error != nil {
+ return *m.Error
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*BuildError)(nil), "soong_build_error.BuildError")
+ proto.RegisterType((*BuildActionError)(nil), "soong_build_error.BuildActionError")
+}
+
+func init() { proto.RegisterFile("build_error.proto", fileDescriptor_a2e15b05802a5501) }
+
+var fileDescriptor_a2e15b05802a5501 = []byte{
+ // 229 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xc1, 0x4a, 0xc3, 0x40,
+ 0x10, 0x86, 0x49, 0x63, 0x95, 0x4c, 0xad, 0xd8, 0x41, 0x74, 0x04, 0x0f, 0xa1, 0x22, 0xe4, 0x94,
+ 0x83, 0x6f, 0x60, 0x41, 0xf0, 0xe2, 0x25, 0x47, 0x2f, 0x61, 0xdd, 0xac, 0x65, 0xc1, 0x64, 0xc2,
+ 0xce, 0xe6, 0xe8, 0x8b, 0xf8, 0xb4, 0x92, 0x69, 0xa5, 0xa5, 0x39, 0x7e, 0xdf, 0x3f, 0xfb, 0xef,
+ 0xce, 0xc2, 0xea, 0x73, 0xf0, 0xdf, 0x4d, 0xed, 0x42, 0xe0, 0x50, 0xf6, 0x81, 0x23, 0xe3, 0x4a,
+ 0x98, 0xbb, 0x6d, 0x7d, 0x14, 0xac, 0x7f, 0x00, 0x36, 0x23, 0xbe, 0x8e, 0x84, 0x4f, 0x70, 0xa5,
+ 0xba, 0x6e, 0x9d, 0x88, 0xd9, 0x3a, 0xa1, 0x24, 0x4f, 0x8b, 0xac, 0x5a, 0xaa, 0x7d, 0xdf, 0x4b,
+ 0x7c, 0x83, 0xa5, 0xb1, 0xd1, 0x73, 0xb7, 0x2b, 0x11, 0x9a, 0xe5, 0x69, 0xb1, 0x78, 0x7e, 0x2c,
+ 0x27, 0xfd, 0xa5, 0x96, 0xbf, 0xe8, 0xb0, 0x5e, 0x51, 0x5d, 0x9a, 0x03, 0xc8, 0xfa, 0x37, 0x81,
+ 0xeb, 0xd3, 0x11, 0xcc, 0x61, 0xd1, 0x38, 0xb1, 0xc1, 0xf7, 0xa3, 0xa3, 0x24, 0x4f, 0x8a, 0xac,
+ 0x3a, 0x56, 0x48, 0x70, 0x61, 0xb9, 0x6d, 0x4d, 0xd7, 0xd0, 0x4c, 0xd3, 0x7f, 0xc4, 0x5b, 0x38,
+ 0xe7, 0x21, 0xf6, 0x43, 0xa4, 0x54, 0x83, 0x3d, 0xe1, 0x03, 0x64, 0x26, 0x44, 0xff, 0x65, 0x6c,
+ 0x14, 0x3a, 0xd3, 0xa5, 0x0e, 0x02, 0x6f, 0x60, 0xae, 0xcf, 0xa5, 0xb9, 0x1e, 0xda, 0xc1, 0xe6,
+ 0xfe, 0xe3, 0x6e, 0xb2, 0x50, 0xad, 0x3f, 0xf9, 0x17, 0x00, 0x00, 0xff, 0xff, 0xb6, 0x18, 0x9e,
+ 0x17, 0x5d, 0x01, 0x00, 0x00,
+}
diff --git a/ui/status/build_error_proto/build_error.proto b/ui/status/build_error_proto/build_error.proto
new file mode 100644
index 00000000..9c8470d8
--- /dev/null
+++ b/ui/status/build_error_proto/build_error.proto
@@ -0,0 +1,46 @@
+// Copyright 2019 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";
+
+package soong_build_error;
+option go_package = "soong_build_error_proto";
+
+message BuildError {
+ // List of error messages of the overall build. The error messages
+ // are not associated with a build action.
+ repeated string error_messages = 1;
+
+ // List of build action errors.
+ repeated BuildActionError action_errors = 2;
+}
+
+// Build is composed of a list of build action. There can be a set of build
+// actions that can failed.
+message BuildActionError {
+ // Description of the command.
+ optional string description = 1;
+
+ // The command name that raised the error.
+ optional string command = 2;
+
+ // The command output stream.
+ optional string output = 3;
+
+ // List of artifacts (i.e. files) that was produced by the command.
+ repeated string artifacts = 4;
+
+ // The error string produced by the build action.
+ optional string error = 5;
+}
diff --git a/ui/status/build_error_proto/regen.sh b/ui/status/build_error_proto/regen.sh
new file mode 100755
index 00000000..7c3ec8fa
--- /dev/null
+++ b/ui/status/build_error_proto/regen.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+aprotoc --go_out=paths=source_relative:. build_error.proto
diff --git a/ui/status/critical_path.go b/ui/status/critical_path.go
new file mode 100644
index 00000000..8065c60f
--- /dev/null
+++ b/ui/status/critical_path.go
@@ -0,0 +1,154 @@
+// Copyright 2019 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 (
+ "time"
+
+ "android/soong/ui/logger"
+)
+
+func NewCriticalPath(log logger.Logger) StatusOutput {
+ return &criticalPath{
+ log: log,
+ running: make(map[*Action]time.Time),
+ nodes: make(map[string]*node),
+ clock: osClock{},
+ }
+}
+
+type criticalPath struct {
+ log logger.Logger
+
+ nodes map[string]*node
+ running map[*Action]time.Time
+
+ start, end time.Time
+
+ clock clock
+}
+
+type clock interface {
+ Now() time.Time
+}
+
+type osClock struct{}
+
+func (osClock) Now() time.Time { return time.Now() }
+
+// A critical path node stores the critical path (the minimum time to build the node and all of its dependencies given
+// perfect parallelism) for an node.
+type node struct {
+ action *Action
+ cumulativeDuration time.Duration
+ duration time.Duration
+ input *node
+}
+
+func (cp *criticalPath) StartAction(action *Action, counts Counts) {
+ start := cp.clock.Now()
+ if cp.start.IsZero() {
+ cp.start = start
+ }
+ cp.running[action] = start
+}
+
+func (cp *criticalPath) FinishAction(result ActionResult, counts Counts) {
+ if start, ok := cp.running[result.Action]; ok {
+ delete(cp.running, result.Action)
+
+ // Determine the input to this edge with the longest cumulative duration
+ var criticalPathInput *node
+ for _, input := range result.Action.Inputs {
+ if x := cp.nodes[input]; x != nil {
+ if criticalPathInput == nil || x.cumulativeDuration > criticalPathInput.cumulativeDuration {
+ criticalPathInput = x
+ }
+ }
+ }
+
+ end := cp.clock.Now()
+ duration := end.Sub(start)
+
+ cumulativeDuration := duration
+ if criticalPathInput != nil {
+ cumulativeDuration += criticalPathInput.cumulativeDuration
+ }
+
+ node := &node{
+ action: result.Action,
+ cumulativeDuration: cumulativeDuration,
+ duration: duration,
+ input: criticalPathInput,
+ }
+
+ for _, output := range result.Action.Outputs {
+ cp.nodes[output] = node
+ }
+
+ cp.end = end
+ }
+}
+
+func (cp *criticalPath) Flush() {
+ criticalPath := cp.criticalPath()
+
+ if len(criticalPath) > 0 {
+ // Log the critical path to the verbose log
+ criticalTime := criticalPath[0].cumulativeDuration.Round(time.Second)
+ cp.log.Verbosef("critical path took %s", criticalTime.String())
+ if !cp.start.IsZero() {
+ elapsedTime := cp.end.Sub(cp.start).Round(time.Second)
+ cp.log.Verbosef("elapsed time %s", elapsedTime.String())
+ if elapsedTime > 0 {
+ cp.log.Verbosef("perfect parallelism ratio %d%%",
+ int(float64(criticalTime)/float64(elapsedTime)*100))
+ }
+ }
+ cp.log.Verbose("critical path:")
+ for i := len(criticalPath) - 1; i >= 0; i-- {
+ duration := criticalPath[i].duration
+ duration = duration.Round(time.Second)
+ seconds := int(duration.Seconds())
+ cp.log.Verbosef(" %2d:%02d %s",
+ seconds/60, seconds%60, criticalPath[i].action.Description)
+ }
+ }
+}
+
+func (cp *criticalPath) Message(level MsgLevel, msg string) {}
+
+func (cp *criticalPath) Write(p []byte) (n int, err error) { return len(p), nil }
+
+func (cp *criticalPath) criticalPath() []*node {
+ var max *node
+
+ // Find the node with the longest critical path
+ for _, node := range cp.nodes {
+ if max == nil || node.cumulativeDuration > max.cumulativeDuration {
+ max = node
+ }
+ }
+
+ // Follow the critical path back to the leaf node
+ var criticalPath []*node
+ node := max
+ for node != nil {
+ criticalPath = append(criticalPath, node)
+ node = node.input
+ }
+
+ return criticalPath
+}
diff --git a/ui/status/critical_path_test.go b/ui/status/critical_path_test.go
new file mode 100644
index 00000000..965e0ad1
--- /dev/null
+++ b/ui/status/critical_path_test.go
@@ -0,0 +1,166 @@
+// Copyright 2019 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 (
+ "reflect"
+ "testing"
+ "time"
+)
+
+type testCriticalPath struct {
+ *criticalPath
+ Counts
+
+ actions map[int]*Action
+}
+
+type testClock time.Time
+
+func (t testClock) Now() time.Time { return time.Time(t) }
+
+func (t *testCriticalPath) start(id int, startTime time.Duration, outputs, inputs []string) {
+ t.clock = testClock(time.Unix(0, 0).Add(startTime))
+ action := &Action{
+ Description: outputs[0],
+ Outputs: outputs,
+ Inputs: inputs,
+ }
+
+ t.actions[id] = action
+ t.StartAction(action, t.Counts)
+}
+
+func (t *testCriticalPath) finish(id int, endTime time.Duration) {
+ t.clock = testClock(time.Unix(0, 0).Add(endTime))
+ t.FinishAction(ActionResult{
+ Action: t.actions[id],
+ }, t.Counts)
+}
+
+func TestCriticalPath(t *testing.T) {
+ tests := []struct {
+ name string
+ msgs func(*testCriticalPath)
+ want []string
+ wantTime time.Duration
+ }{
+ {
+ name: "empty",
+ msgs: func(cp *testCriticalPath) {},
+ },
+ {
+ name: "duplicate",
+ msgs: func(cp *testCriticalPath) {
+ cp.start(0, 0, []string{"a"}, nil)
+ cp.start(1, 0, []string{"a"}, nil)
+ cp.finish(0, 1000)
+ cp.finish(0, 2000)
+ },
+ want: []string{"a"},
+ wantTime: 1000,
+ },
+ {
+ name: "linear",
+ // a
+ // |
+ // b
+ // |
+ // c
+ msgs: func(cp *testCriticalPath) {
+ cp.start(0, 0, []string{"a"}, nil)
+ cp.finish(0, 1000)
+ cp.start(1, 1000, []string{"b"}, []string{"a"})
+ cp.finish(1, 2000)
+ cp.start(2, 3000, []string{"c"}, []string{"b"})
+ cp.finish(2, 4000)
+ },
+ want: []string{"c", "b", "a"},
+ wantTime: 3000,
+ },
+ {
+ name: "diamond",
+ // a
+ // |\
+ // b c
+ // |/
+ // d
+ msgs: func(cp *testCriticalPath) {
+ cp.start(0, 0, []string{"a"}, nil)
+ cp.finish(0, 1000)
+ cp.start(1, 1000, []string{"b"}, []string{"a"})
+ cp.start(2, 1000, []string{"c"}, []string{"a"})
+ cp.finish(1, 2000)
+ cp.finish(2, 3000)
+ cp.start(3, 3000, []string{"d"}, []string{"b", "c"})
+ cp.finish(3, 4000)
+ },
+ want: []string{"d", "c", "a"},
+ wantTime: 4000,
+ },
+ {
+ name: "multiple",
+ // a d
+ // | |
+ // b e
+ // |
+ // c
+ msgs: func(cp *testCriticalPath) {
+ cp.start(0, 0, []string{"a"}, nil)
+ cp.start(3, 0, []string{"d"}, nil)
+ cp.finish(0, 1000)
+ cp.finish(3, 1000)
+ cp.start(1, 1000, []string{"b"}, []string{"a"})
+ cp.start(4, 1000, []string{"e"}, []string{"d"})
+ cp.finish(1, 2000)
+ cp.start(2, 2000, []string{"c"}, []string{"b"})
+ cp.finish(2, 3000)
+ cp.finish(4, 4000)
+
+ },
+ want: []string{"e", "d"},
+ wantTime: 4000,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ cp := &testCriticalPath{
+ criticalPath: NewCriticalPath(nil).(*criticalPath),
+ actions: make(map[int]*Action),
+ }
+
+ tt.msgs(cp)
+
+ criticalPath := cp.criticalPath.criticalPath()
+
+ var descs []string
+ for _, x := range criticalPath {
+ descs = append(descs, x.action.Description)
+ }
+
+ if !reflect.DeepEqual(descs, tt.want) {
+ t.Errorf("criticalPath.criticalPath() = %v, want %v", descs, tt.want)
+ }
+
+ var gotTime time.Duration
+ if len(criticalPath) > 0 {
+ gotTime = criticalPath[0].cumulativeDuration
+ }
+ if gotTime != tt.wantTime {
+ t.Errorf("cumulativeDuration[0].cumulativeDuration = %v, want %v", gotTime, tt.wantTime)
+ }
+ })
+ }
+}
diff --git a/ui/status/log.go b/ui/status/log.go
index 921aa440..9090f499 100644
--- a/ui/status/log.go
+++ b/ui/status/log.go
@@ -15,11 +15,17 @@
package status
import (
- "android/soong/ui/logger"
"compress/gzip"
+ "errors"
"fmt"
"io"
+ "io/ioutil"
"strings"
+
+ "github.com/golang/protobuf/proto"
+
+ "android/soong/ui/logger"
+ "android/soong/ui/status/build_error_proto"
)
type verboseLog struct {
@@ -71,9 +77,13 @@ func (v *verboseLog) Message(level MsgLevel, message string) {
fmt.Fprintf(v.w, "%s%s\n", level.Prefix(), message)
}
-type errorLog struct {
- w io.WriteCloser
+func (v *verboseLog) Write(p []byte) (int, error) {
+ fmt.Fprint(v.w, string(p))
+ return len(p), nil
+}
+type errorLog struct {
+ w io.WriteCloser
empty bool
}
@@ -97,20 +107,17 @@ func (e *errorLog) FinishAction(result ActionResult, counts Counts) {
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)
@@ -134,3 +141,60 @@ func (e *errorLog) Message(level MsgLevel, message string) {
fmt.Fprintf(e.w, "error: %s\n", message)
}
+
+func (e *errorLog) Write(p []byte) (int, error) {
+ fmt.Fprint(e.w, string(p))
+ return len(p), nil
+}
+
+type errorProtoLog struct {
+ errorProto soong_build_error_proto.BuildError
+ filename string
+ log logger.Logger
+}
+
+func NewProtoErrorLog(log logger.Logger, filename string) StatusOutput {
+ return &errorProtoLog{
+ errorProto: soong_build_error_proto.BuildError{},
+ filename: filename,
+ log: log,
+ }
+}
+
+func (e *errorProtoLog) StartAction(action *Action, counts Counts) {}
+
+func (e *errorProtoLog) FinishAction(result ActionResult, counts Counts) {
+ if result.Error == nil {
+ return
+ }
+
+ e.errorProto.ActionErrors = append(e.errorProto.ActionErrors, &soong_build_error_proto.BuildActionError{
+ Description: proto.String(result.Description),
+ Command: proto.String(result.Command),
+ Output: proto.String(result.Output),
+ Artifacts: result.Outputs,
+ Error: proto.String(result.Error.Error()),
+ })
+}
+
+func (e *errorProtoLog) Flush() {
+ data, err := proto.Marshal(&e.errorProto)
+ if err != nil {
+ e.log.Println("Failed to marshal build status proto: %v", err)
+ return
+ }
+ err = ioutil.WriteFile(e.filename, []byte(data), 0644)
+ if err != nil {
+ e.log.Println("Failed to write file %s: %v", e.errorProto, err)
+ }
+}
+
+func (e *errorProtoLog) Message(level MsgLevel, message string) {
+ if level > ErrorLvl {
+ e.errorProto.ErrorMessages = append(e.errorProto.ErrorMessages, message)
+ }
+}
+
+func (e *errorProtoLog) Write(p []byte) (int, error) {
+ return 0, errors.New("not supported")
+}
diff --git a/ui/status/ninja.go b/ui/status/ninja.go
index ee2a2daa..9cf2f6a8 100644
--- a/ui/status/ninja.go
+++ b/ui/status/ninja.go
@@ -142,6 +142,7 @@ func (n *NinjaReader) run() {
action := &Action{
Description: msg.EdgeStarted.GetDesc(),
Outputs: msg.EdgeStarted.Outputs,
+ Inputs: msg.EdgeStarted.Inputs,
Command: msg.EdgeStarted.GetCommand(),
}
n.status.StartAction(action)
diff --git a/ui/status/status.go b/ui/status/status.go
index 46ec72e8..df33baa8 100644
--- a/ui/status/status.go
+++ b/ui/status/status.go
@@ -32,6 +32,10 @@ type Action struct {
// but they can be any string.
Outputs []string
+ // Inputs is the (optional) list of inputs. Usually these are files,
+ // but they can be any string.
+ Inputs []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.
@@ -173,6 +177,9 @@ type StatusOutput interface {
// Flush is called when your outputs should be flushed / closed. No
// output is expected after this call.
Flush()
+
+ // Write lets StatusOutput implement io.Writer
+ Write(p []byte) (n int, err error)
}
// Status is the multiplexer / accumulator between ToolStatus instances (via
diff --git a/ui/status/status_test.go b/ui/status/status_test.go
index e62785f4..94945822 100644
--- a/ui/status/status_test.go
+++ b/ui/status/status_test.go
@@ -27,6 +27,11 @@ func (c *counterOutput) FinishAction(result ActionResult, counts Counts) {
func (c counterOutput) Message(level MsgLevel, msg string) {}
func (c counterOutput) Flush() {}
+func (c counterOutput) Write(p []byte) (int, error) {
+ // Discard writes
+ return len(p), nil
+}
+
func (c counterOutput) Expect(t *testing.T, counts Counts) {
if Counts(c) == counts {
return