Merge "Add a unified status reporting UI"
This commit is contained in:
commit
ecc71f8a49
|
@ -17,6 +17,7 @@ blueprint_go_binary {
|
|||
deps: [
|
||||
"soong-ui-build",
|
||||
"soong-ui-logger",
|
||||
"soong-ui-terminal",
|
||||
"soong-ui-tracer",
|
||||
"soong-zip",
|
||||
],
|
||||
|
|
|
@ -29,6 +29,8 @@ import (
|
|||
|
||||
"android/soong/ui/build"
|
||||
"android/soong/ui/logger"
|
||||
"android/soong/ui/status"
|
||||
"android/soong/ui/terminal"
|
||||
"android/soong/ui/tracer"
|
||||
"android/soong/zip"
|
||||
)
|
||||
|
@ -66,98 +68,34 @@ type Product struct {
|
|||
ctx build.Context
|
||||
config build.Config
|
||||
logFile string
|
||||
action *status.Action
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
cur int
|
||||
total int
|
||||
failed int
|
||||
|
||||
ctx build.Context
|
||||
haveBlankLine bool
|
||||
smartTerminal bool
|
||||
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewStatus(ctx build.Context) *Status {
|
||||
return &Status{
|
||||
ctx: ctx,
|
||||
haveBlankLine: true,
|
||||
smartTerminal: ctx.IsTerminal(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Status) SetTotal(total int) {
|
||||
s.total = total
|
||||
}
|
||||
|
||||
func (s *Status) Fail(product string, err error, logFile string) {
|
||||
s.Finish(product)
|
||||
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.smartTerminal && !s.haveBlankLine {
|
||||
fmt.Fprintln(s.ctx.Stdout())
|
||||
s.haveBlankLine = true
|
||||
func errMsgFromLog(filename string) string {
|
||||
if filename == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
s.failed++
|
||||
fmt.Fprintln(s.ctx.Stderr(), "FAILED:", product)
|
||||
s.ctx.Verboseln("FAILED:", product)
|
||||
|
||||
if logFile != "" {
|
||||
data, err := ioutil.ReadFile(logFile)
|
||||
if err == nil {
|
||||
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
if len(lines) > errorLeadingLines+errorTrailingLines+1 {
|
||||
lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
|
||||
len(lines)-errorLeadingLines-errorTrailingLines)
|
||||
|
||||
lines = append(lines[:errorLeadingLines+1],
|
||||
lines[len(lines)-errorTrailingLines:]...)
|
||||
}
|
||||
for _, line := range lines {
|
||||
fmt.Fprintln(s.ctx.Stderr(), "> ", line)
|
||||
s.ctx.Verboseln(line)
|
||||
}
|
||||
}
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
s.ctx.Print(err)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
if len(lines) > errorLeadingLines+errorTrailingLines+1 {
|
||||
lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
|
||||
len(lines)-errorLeadingLines-errorTrailingLines)
|
||||
|
||||
func (s *Status) Finish(product string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.cur++
|
||||
line := fmt.Sprintf("[%d/%d] %s", s.cur, s.total, product)
|
||||
|
||||
if s.smartTerminal {
|
||||
if max, ok := s.ctx.TermWidth(); ok {
|
||||
if len(line) > max {
|
||||
line = line[:max]
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprint(s.ctx.Stdout(), "\r", line, "\x1b[K")
|
||||
s.haveBlankLine = false
|
||||
} else {
|
||||
s.ctx.Println(line)
|
||||
lines = append(lines[:errorLeadingLines+1],
|
||||
lines[len(lines)-errorTrailingLines:]...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Status) Finished() int {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if !s.haveBlankLine {
|
||||
fmt.Fprintln(s.ctx.Stdout())
|
||||
s.haveBlankLine = true
|
||||
var buf strings.Builder
|
||||
for _, line := range lines {
|
||||
buf.WriteString("> ")
|
||||
buf.WriteString(line)
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
return s.failed
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// TODO(b/70370883): This tool uses a lot of open files -- over the default
|
||||
|
@ -194,6 +132,9 @@ func inList(str string, list []string) bool {
|
|||
}
|
||||
|
||||
func main() {
|
||||
writer := terminal.NewWriter(terminal.StdioImpl{})
|
||||
defer writer.Finish()
|
||||
|
||||
log := logger.New(os.Stderr)
|
||||
defer log.Cleanup()
|
||||
|
||||
|
@ -205,20 +146,24 @@ func main() {
|
|||
trace := tracer.New(log)
|
||||
defer trace.Close()
|
||||
|
||||
stat := &status.Status{}
|
||||
defer stat.Finish()
|
||||
stat.AddOutput(terminal.NewStatusOutput(writer, ""))
|
||||
|
||||
build.SetupSignals(log, cancel, func() {
|
||||
trace.Close()
|
||||
log.Cleanup()
|
||||
stat.Finish()
|
||||
})
|
||||
|
||||
buildCtx := build.Context{&build.ContextImpl{
|
||||
Context: ctx,
|
||||
Logger: log,
|
||||
Tracer: trace,
|
||||
StdioInterface: build.StdioImpl{},
|
||||
Context: ctx,
|
||||
Logger: log,
|
||||
Tracer: trace,
|
||||
Writer: writer,
|
||||
Status: stat,
|
||||
}}
|
||||
|
||||
status := NewStatus(buildCtx)
|
||||
|
||||
config := build.NewConfig(buildCtx)
|
||||
if *outDir == "" {
|
||||
name := "multiproduct-" + time.Now().Format("20060102150405")
|
||||
|
@ -303,7 +248,8 @@ func main() {
|
|||
|
||||
log.Verbose("Got product list: ", products)
|
||||
|
||||
status.SetTotal(len(products))
|
||||
s := buildCtx.Status.StartTool()
|
||||
s.SetTotalActions(len(products))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
productConfigs := make(chan Product, len(products))
|
||||
|
@ -315,8 +261,18 @@ func main() {
|
|||
var stdLog string
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
action := &status.Action{
|
||||
Description: product,
|
||||
Outputs: []string{product},
|
||||
}
|
||||
s.StartAction(action)
|
||||
defer logger.Recover(func(err error) {
|
||||
status.Fail(product, err, stdLog)
|
||||
s.FinishAction(status.ActionResult{
|
||||
Action: action,
|
||||
Error: err,
|
||||
Output: errMsgFromLog(stdLog),
|
||||
})
|
||||
})
|
||||
|
||||
productOutDir := filepath.Join(config.OutDir(), product)
|
||||
|
@ -339,12 +295,14 @@ func main() {
|
|||
productLog.SetOutput(filepath.Join(productLogDir, "soong.log"))
|
||||
|
||||
productCtx := build.Context{&build.ContextImpl{
|
||||
Context: ctx,
|
||||
Logger: productLog,
|
||||
Tracer: trace,
|
||||
StdioInterface: build.NewCustomStdio(nil, f, f),
|
||||
Thread: trace.NewThread(product),
|
||||
Context: ctx,
|
||||
Logger: productLog,
|
||||
Tracer: trace,
|
||||
Writer: terminal.NewWriter(terminal.NewCustomStdio(nil, f, f)),
|
||||
Thread: trace.NewThread(product),
|
||||
Status: &status.Status{},
|
||||
}}
|
||||
productCtx.Status.AddOutput(terminal.NewStatusOutput(productCtx.Writer, ""))
|
||||
|
||||
productConfig := build.NewConfig(productCtx)
|
||||
productConfig.Environment().Set("OUT_DIR", productOutDir)
|
||||
|
@ -352,7 +310,7 @@ func main() {
|
|||
productConfig.Lunch(productCtx, product, *buildVariant)
|
||||
|
||||
build.Build(productCtx, productConfig, build.BuildProductConfig)
|
||||
productConfigs <- Product{productCtx, productConfig, stdLog}
|
||||
productConfigs <- Product{productCtx, productConfig, stdLog, action}
|
||||
}(product)
|
||||
}
|
||||
go func() {
|
||||
|
@ -369,7 +327,11 @@ func main() {
|
|||
for product := range productConfigs {
|
||||
func() {
|
||||
defer logger.Recover(func(err error) {
|
||||
status.Fail(product.config.TargetProduct(), err, product.logFile)
|
||||
s.FinishAction(status.ActionResult{
|
||||
Action: product.action,
|
||||
Error: err,
|
||||
Output: errMsgFromLog(product.logFile),
|
||||
})
|
||||
})
|
||||
|
||||
defer func() {
|
||||
|
@ -400,7 +362,9 @@ func main() {
|
|||
}
|
||||
}
|
||||
build.Build(product.ctx, product.config, buildWhat)
|
||||
status.Finish(product.config.TargetProduct())
|
||||
s.FinishAction(status.ActionResult{
|
||||
Action: product.action,
|
||||
})
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
@ -421,7 +385,5 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
if count := status.Finished(); count > 0 {
|
||||
log.Fatalln(count, "products failed")
|
||||
}
|
||||
s.Finish()
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ blueprint_go_binary {
|
|||
deps: [
|
||||
"soong-ui-build",
|
||||
"soong-ui-logger",
|
||||
"soong-ui-terminal",
|
||||
"soong-ui-tracer",
|
||||
],
|
||||
srcs: [
|
||||
|
|
|
@ -26,6 +26,8 @@ import (
|
|||
|
||||
"android/soong/ui/build"
|
||||
"android/soong/ui/logger"
|
||||
"android/soong/ui/status"
|
||||
"android/soong/ui/terminal"
|
||||
"android/soong/ui/tracer"
|
||||
)
|
||||
|
||||
|
@ -44,7 +46,10 @@ func inList(s string, list []string) bool {
|
|||
}
|
||||
|
||||
func main() {
|
||||
log := logger.New(os.Stderr)
|
||||
writer := terminal.NewWriter(terminal.StdioImpl{})
|
||||
defer writer.Finish()
|
||||
|
||||
log := logger.New(writer)
|
||||
defer log.Cleanup()
|
||||
|
||||
if len(os.Args) < 2 || !(inList("--make-mode", os.Args) ||
|
||||
|
@ -60,16 +65,23 @@ func main() {
|
|||
trace := tracer.New(log)
|
||||
defer trace.Close()
|
||||
|
||||
stat := &status.Status{}
|
||||
defer stat.Finish()
|
||||
stat.AddOutput(terminal.NewStatusOutput(writer, os.Getenv("NINJA_STATUS")))
|
||||
stat.AddOutput(trace.StatusTracer())
|
||||
|
||||
build.SetupSignals(log, cancel, func() {
|
||||
trace.Close()
|
||||
log.Cleanup()
|
||||
stat.Finish()
|
||||
})
|
||||
|
||||
buildCtx := build.Context{&build.ContextImpl{
|
||||
Context: ctx,
|
||||
Logger: log,
|
||||
Tracer: trace,
|
||||
StdioInterface: build.StdioImpl{},
|
||||
Context: ctx,
|
||||
Logger: log,
|
||||
Tracer: trace,
|
||||
Writer: writer,
|
||||
Status: stat,
|
||||
}}
|
||||
var config build.Config
|
||||
if os.Args[1] == "--dumpvars-mode" || os.Args[1] == "--dumpvar-mode" {
|
||||
|
@ -78,19 +90,19 @@ func main() {
|
|||
config = build.NewConfig(buildCtx, os.Args[1:]...)
|
||||
}
|
||||
|
||||
log.SetVerbose(config.IsVerbose())
|
||||
build.SetupOutDir(buildCtx, config)
|
||||
|
||||
logsDir := config.OutDir()
|
||||
if config.Dist() {
|
||||
logsDir := filepath.Join(config.DistDir(), "logs")
|
||||
os.MkdirAll(logsDir, 0777)
|
||||
log.SetOutput(filepath.Join(logsDir, "soong.log"))
|
||||
trace.SetOutput(filepath.Join(logsDir, "build.trace"))
|
||||
} else {
|
||||
log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
|
||||
trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
|
||||
logsDir = filepath.Join(config.DistDir(), "logs")
|
||||
}
|
||||
|
||||
os.MkdirAll(logsDir, 0777)
|
||||
log.SetOutput(filepath.Join(logsDir, "soong.log"))
|
||||
trace.SetOutput(filepath.Join(logsDir, "build.trace"))
|
||||
stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
|
||||
stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))
|
||||
|
||||
if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
|
||||
if !strings.HasSuffix(start, "N") {
|
||||
if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
|
||||
|
@ -114,6 +126,17 @@ func main() {
|
|||
} else if os.Args[1] == "--dumpvars-mode" {
|
||||
dumpVars(buildCtx, config, os.Args[2:])
|
||||
} else {
|
||||
if config.IsVerbose() {
|
||||
writer.Print("! The argument `showcommands` is no longer supported.")
|
||||
writer.Print("! Instead, the verbose log is always written to a compressed file in the output dir:")
|
||||
writer.Print("!")
|
||||
writer.Print(fmt.Sprintf("! gzip -cd %s/verbose.log.gz | less -R", logsDir))
|
||||
writer.Print("!")
|
||||
writer.Print("! Older versions are saved in verbose.log.#.gz files")
|
||||
writer.Print("")
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
toBuild := build.BuildAll
|
||||
if config.Checkbuild() {
|
||||
toBuild |= build.RunBuildTests
|
||||
|
|
|
@ -59,7 +59,7 @@ function soong_build_go
|
|||
BUILDDIR=$(getoutdir) \
|
||||
SRCDIR=${TOP} \
|
||||
BLUEPRINTDIR=${TOP}/build/blueprint \
|
||||
EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong" \
|
||||
EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path github.com/golang/protobuf=${TOP}/external/golang-protobuf" \
|
||||
build_go $@
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ bootstrap_go_package {
|
|||
deps: [
|
||||
"soong-ui-build-paths",
|
||||
"soong-ui-logger",
|
||||
"soong-ui-status",
|
||||
"soong-ui-terminal",
|
||||
"soong-ui-tracer",
|
||||
"soong-shared",
|
||||
"soong-finder",
|
||||
|
@ -62,13 +64,11 @@ bootstrap_go_package {
|
|||
darwin: {
|
||||
srcs: [
|
||||
"sandbox_darwin.go",
|
||||
"util_darwin.go"
|
||||
],
|
||||
},
|
||||
linux: {
|
||||
srcs: [
|
||||
"sandbox_linux.go",
|
||||
"util_linux.go"
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -105,9 +105,7 @@ func checkCaseSensitivity(ctx Context, config Config) {
|
|||
func help(ctx Context, config Config, what int) {
|
||||
cmd := Command(ctx, config, "help.sh", "build/make/help.sh")
|
||||
cmd.Sandbox = dumpvarsSandbox
|
||||
cmd.Stdout = ctx.Stdout()
|
||||
cmd.Stderr = ctx.Stderr()
|
||||
cmd.RunOrFatal()
|
||||
cmd.RunAndPrintOrFatal()
|
||||
}
|
||||
|
||||
// Build the tree. The 'what' argument can be used to chose which components of
|
||||
|
|
|
@ -22,13 +22,14 @@ import (
|
|||
"testing"
|
||||
|
||||
"android/soong/ui/logger"
|
||||
"android/soong/ui/terminal"
|
||||
)
|
||||
|
||||
func testContext() Context {
|
||||
return Context{&ContextImpl{
|
||||
Context: context.Background(),
|
||||
Logger: logger.New(&bytes.Buffer{}),
|
||||
StdioInterface: NewCustomStdio(&bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}),
|
||||
Context: context.Background(),
|
||||
Logger: logger.New(&bytes.Buffer{}),
|
||||
Writer: terminal.NewWriter(terminal.NewCustomStdio(&bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{})),
|
||||
}}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,45 +16,14 @@ package build
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"android/soong/ui/logger"
|
||||
"android/soong/ui/status"
|
||||
"android/soong/ui/terminal"
|
||||
"android/soong/ui/tracer"
|
||||
)
|
||||
|
||||
type StdioInterface interface {
|
||||
Stdin() io.Reader
|
||||
Stdout() io.Writer
|
||||
Stderr() io.Writer
|
||||
}
|
||||
|
||||
type StdioImpl struct{}
|
||||
|
||||
func (StdioImpl) Stdin() io.Reader { return os.Stdin }
|
||||
func (StdioImpl) Stdout() io.Writer { return os.Stdout }
|
||||
func (StdioImpl) Stderr() io.Writer { return os.Stderr }
|
||||
|
||||
var _ StdioInterface = StdioImpl{}
|
||||
|
||||
type customStdio struct {
|
||||
stdin io.Reader
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
}
|
||||
|
||||
func NewCustomStdio(stdin io.Reader, stdout, stderr io.Writer) StdioInterface {
|
||||
return customStdio{stdin, stdout, stderr}
|
||||
}
|
||||
|
||||
func (c customStdio) Stdin() io.Reader { return c.stdin }
|
||||
func (c customStdio) Stdout() io.Writer { return c.stdout }
|
||||
func (c customStdio) Stderr() io.Writer { return c.stderr }
|
||||
|
||||
var _ StdioInterface = customStdio{}
|
||||
|
||||
// Context combines a context.Context, logger.Logger, and StdIO redirection.
|
||||
// Context combines a context.Context, logger.Logger, and terminal.Writer.
|
||||
// These all are agnostic of the current build, and may be used for multiple
|
||||
// builds, while the Config objects contain per-build information.
|
||||
type Context struct{ *ContextImpl }
|
||||
|
@ -62,7 +31,8 @@ type ContextImpl struct {
|
|||
context.Context
|
||||
logger.Logger
|
||||
|
||||
StdioInterface
|
||||
Writer terminal.Writer
|
||||
Status *status.Status
|
||||
|
||||
Thread tracer.Thread
|
||||
Tracer tracer.Tracer
|
||||
|
@ -88,28 +58,3 @@ func (c ContextImpl) CompleteTrace(name string, begin, end uint64) {
|
|||
c.Tracer.Complete(name, c.Thread, begin, end)
|
||||
}
|
||||
}
|
||||
|
||||
// ImportNinjaLog imports a .ninja_log file into the tracer.
|
||||
func (c ContextImpl) ImportNinjaLog(filename string, startOffset time.Time) {
|
||||
if c.Tracer != nil {
|
||||
c.Tracer.ImportNinjaLog(c.Thread, filename, startOffset)
|
||||
}
|
||||
}
|
||||
|
||||
func (c ContextImpl) IsTerminal() bool {
|
||||
if term, ok := os.LookupEnv("TERM"); ok {
|
||||
return term != "dumb" && isTerminal(c.Stdout()) && isTerminal(c.Stderr())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c ContextImpl) IsErrTerminal() bool {
|
||||
if term, ok := os.LookupEnv("TERM"); ok {
|
||||
return term != "dumb" && isTerminal(c.Stderr())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c ContextImpl) TermWidth() (int, bool) {
|
||||
return termWidth(c.Stdout())
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"android/soong/ui/status"
|
||||
)
|
||||
|
||||
// DumpMakeVars can be used to extract the values of Make variables after the
|
||||
|
@ -60,7 +62,7 @@ func dumpMakeVars(ctx Context, config Config, goals, vars []string, write_soong_
|
|||
}
|
||||
cmd.StartOrFatal()
|
||||
// TODO: error out when Stderr contains any content
|
||||
katiRewriteOutput(ctx, pipe)
|
||||
status.KatiReader(ctx.Status.StartTool(), pipe)
|
||||
cmd.WaitOrFatal()
|
||||
|
||||
ret := make(map[string]string, len(vars))
|
||||
|
@ -175,7 +177,7 @@ func runMakeProductConfig(ctx Context, config Config) {
|
|||
}
|
||||
|
||||
// Print the banner like make does
|
||||
fmt.Fprintln(ctx.Stdout(), Banner(make_vars))
|
||||
ctx.Writer.Print(Banner(make_vars))
|
||||
|
||||
// Populate the environment
|
||||
env := config.Environment()
|
||||
|
|
|
@ -122,3 +122,20 @@ func (c *Cmd) CombinedOutputOrFatal() []byte {
|
|||
c.reportError(err)
|
||||
return ret
|
||||
}
|
||||
|
||||
// RunAndPrintOrFatal will run the command, then after finishing
|
||||
// print any output, then handling any errors with a call to
|
||||
// ctx.Fatal
|
||||
func (c *Cmd) RunAndPrintOrFatal() {
|
||||
ret, err := c.CombinedOutput()
|
||||
st := c.ctx.Status.StartTool()
|
||||
if len(ret) > 0 {
|
||||
if err != nil {
|
||||
st.Error(string(ret))
|
||||
} else {
|
||||
st.Print(string(ret))
|
||||
}
|
||||
}
|
||||
st.Finish()
|
||||
c.reportError(err)
|
||||
}
|
||||
|
|
|
@ -15,15 +15,14 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"android/soong/ui/status"
|
||||
)
|
||||
|
||||
var spaceSlashReplacer = strings.NewReplacer("/", "_", " ", "_")
|
||||
|
@ -117,77 +116,10 @@ func runKati(ctx Context, config Config) {
|
|||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
cmd.StartOrFatal()
|
||||
katiRewriteOutput(ctx, pipe)
|
||||
status.KatiReader(ctx.Status.StartTool(), pipe)
|
||||
cmd.WaitOrFatal()
|
||||
}
|
||||
|
||||
var katiIncludeRe = regexp.MustCompile(`^(\[\d+/\d+] )?including [^ ]+ ...$`)
|
||||
var katiLogRe = regexp.MustCompile(`^\*kati\*: `)
|
||||
|
||||
func katiRewriteOutput(ctx Context, pipe io.ReadCloser) {
|
||||
haveBlankLine := true
|
||||
smartTerminal := ctx.IsTerminal()
|
||||
errSmartTerminal := ctx.IsErrTerminal()
|
||||
|
||||
scanner := bufio.NewScanner(pipe)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
verbose := katiIncludeRe.MatchString(line)
|
||||
|
||||
// Only put kati debug/stat lines in our verbose log
|
||||
if katiLogRe.MatchString(line) {
|
||||
ctx.Verbose(line)
|
||||
continue
|
||||
}
|
||||
|
||||
// For verbose lines, write them on the current line without a newline,
|
||||
// then overwrite them if the next thing we're printing is another
|
||||
// verbose line.
|
||||
if smartTerminal && verbose {
|
||||
// Limit line width to the terminal width, otherwise we'll wrap onto
|
||||
// another line and we won't delete the previous line.
|
||||
//
|
||||
// Run this on every line in case the window has been resized while
|
||||
// we're printing. This could be optimized to only re-run when we
|
||||
// get SIGWINCH if it ever becomes too time consuming.
|
||||
if max, ok := termWidth(ctx.Stdout()); ok {
|
||||
if len(line) > max {
|
||||
// Just do a max. Ninja elides the middle, but that's
|
||||
// more complicated and these lines aren't that important.
|
||||
line = line[:max]
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the beginning on the line, print the output, then clear
|
||||
// the rest of the line.
|
||||
fmt.Fprint(ctx.Stdout(), "\r", line, "\x1b[K")
|
||||
haveBlankLine = false
|
||||
continue
|
||||
} else if smartTerminal && !haveBlankLine {
|
||||
// If we've previously written a verbose message, send a newline to save
|
||||
// that message instead of overwriting it.
|
||||
fmt.Fprintln(ctx.Stdout())
|
||||
haveBlankLine = true
|
||||
} else if !errSmartTerminal {
|
||||
// Most editors display these as garbage, so strip them out.
|
||||
line = string(stripAnsiEscapes([]byte(line)))
|
||||
}
|
||||
|
||||
// Assume that non-verbose lines are important enough for stderr
|
||||
fmt.Fprintln(ctx.Stderr(), line)
|
||||
}
|
||||
|
||||
// Save our last verbose line.
|
||||
if !haveBlankLine {
|
||||
fmt.Fprintln(ctx.Stdout())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
ctx.Println("Error from kati parser:", err)
|
||||
io.Copy(ctx.Stderr(), pipe)
|
||||
}
|
||||
}
|
||||
|
||||
func runKatiCleanSpec(ctx Context, config Config) {
|
||||
ctx.BeginTrace("kati cleanspec")
|
||||
defer ctx.EndTrace()
|
||||
|
@ -220,6 +152,6 @@ func runKatiCleanSpec(ctx Context, config Config) {
|
|||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
cmd.StartOrFatal()
|
||||
katiRewriteOutput(ctx, pipe)
|
||||
status.KatiReader(ctx.Status.StartTool(), pipe)
|
||||
cmd.WaitOrFatal()
|
||||
}
|
||||
|
|
|
@ -21,15 +21,21 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"android/soong/ui/status"
|
||||
)
|
||||
|
||||
func runNinja(ctx Context, config Config) {
|
||||
ctx.BeginTrace("ninja")
|
||||
defer ctx.EndTrace()
|
||||
|
||||
fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
|
||||
status.NinjaReader(ctx, ctx.Status.StartTool(), fifo)
|
||||
|
||||
executable := config.PrebuiltBuildTool("ninja")
|
||||
args := []string{
|
||||
"-d", "keepdepfile",
|
||||
fmt.Sprintf("--frontend=cat <&3 >%s", fifo),
|
||||
}
|
||||
|
||||
args = append(args, config.NinjaArgs()...)
|
||||
|
@ -47,9 +53,6 @@ func runNinja(ctx Context, config Config) {
|
|||
|
||||
args = append(args, "-f", config.CombinedNinjaFile())
|
||||
|
||||
if config.IsVerbose() {
|
||||
args = append(args, "-v")
|
||||
}
|
||||
args = append(args, "-w", "dupbuild=err")
|
||||
|
||||
cmd := Command(ctx, config, "ninja", executable, args...)
|
||||
|
@ -66,13 +69,6 @@ func runNinja(ctx Context, config Config) {
|
|||
cmd.Args = append(cmd.Args, strings.Fields(extra)...)
|
||||
}
|
||||
|
||||
if _, ok := cmd.Environment.Get("NINJA_STATUS"); !ok {
|
||||
cmd.Environment.Set("NINJA_STATUS", "[%p %f/%t] ")
|
||||
}
|
||||
|
||||
cmd.Stdin = ctx.Stdin()
|
||||
cmd.Stdout = ctx.Stdout()
|
||||
cmd.Stderr = ctx.Stderr()
|
||||
logPath := filepath.Join(config.OutDir(), ".ninja_log")
|
||||
ninjaHeartbeatDuration := time.Minute * 5
|
||||
if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok {
|
||||
|
@ -99,10 +95,7 @@ func runNinja(ctx Context, config Config) {
|
|||
}
|
||||
}()
|
||||
|
||||
startTime := time.Now()
|
||||
defer ctx.ImportNinjaLog(logPath, startTime)
|
||||
|
||||
cmd.RunOrFatal()
|
||||
cmd.RunAndPrintOrFatal()
|
||||
}
|
||||
|
||||
type statusChecker struct {
|
||||
|
|
|
@ -15,12 +15,15 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
"strings"
|
||||
|
||||
"github.com/google/blueprint/microfactory"
|
||||
|
||||
"android/soong/ui/status"
|
||||
)
|
||||
|
||||
func runSoong(ctx Context, config Config) {
|
||||
|
@ -41,9 +44,8 @@ func runSoong(ctx Context, config Config) {
|
|||
cmd.Environment.Set("SRCDIR", ".")
|
||||
cmd.Environment.Set("TOPNAME", "Android.bp")
|
||||
cmd.Sandbox = soongSandbox
|
||||
cmd.Stdout = ctx.Stdout()
|
||||
cmd.Stderr = ctx.Stderr()
|
||||
cmd.RunOrFatal()
|
||||
|
||||
cmd.RunAndPrintOrFatal()
|
||||
}()
|
||||
|
||||
func() {
|
||||
|
@ -56,12 +58,18 @@ func runSoong(ctx Context, config Config) {
|
|||
if _, err := os.Stat(envTool); err == nil {
|
||||
cmd := Command(ctx, config, "soong_env", envTool, envFile)
|
||||
cmd.Sandbox = soongSandbox
|
||||
cmd.Stdout = ctx.Stdout()
|
||||
cmd.Stderr = ctx.Stderr()
|
||||
|
||||
var buf strings.Builder
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
if err := cmd.Run(); err != nil {
|
||||
ctx.Verboseln("soong_env failed, forcing manifest regeneration")
|
||||
os.Remove(envFile)
|
||||
}
|
||||
|
||||
if buf.Len() > 0 {
|
||||
ctx.Verboseln(buf.String())
|
||||
}
|
||||
} else {
|
||||
ctx.Verboseln("Missing soong_env tool, forcing manifest regeneration")
|
||||
os.Remove(envFile)
|
||||
|
@ -100,22 +108,18 @@ func runSoong(ctx Context, config Config) {
|
|||
ctx.BeginTrace(name)
|
||||
defer ctx.EndTrace()
|
||||
|
||||
fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
|
||||
status.NinjaReader(ctx, ctx.Status.StartTool(), fifo)
|
||||
|
||||
cmd := Command(ctx, config, "soong "+name,
|
||||
config.PrebuiltBuildTool("ninja"),
|
||||
"-d", "keepdepfile",
|
||||
"-w", "dupbuild=err",
|
||||
"-j", strconv.Itoa(config.Parallel()),
|
||||
fmt.Sprintf("--frontend=cat <&3 >%s", fifo),
|
||||
"-f", filepath.Join(config.SoongOutDir(), file))
|
||||
if config.IsVerbose() {
|
||||
cmd.Args = append(cmd.Args, "-v")
|
||||
}
|
||||
cmd.Sandbox = soongSandbox
|
||||
cmd.Stdin = ctx.Stdin()
|
||||
cmd.Stdout = ctx.Stdout()
|
||||
cmd.Stderr = ctx.Stderr()
|
||||
|
||||
defer ctx.ImportNinjaLog(filepath.Join(config.OutDir(), ".ninja_log"), time.Now())
|
||||
cmd.RunOrFatal()
|
||||
cmd.RunAndPrintOrFatal()
|
||||
}
|
||||
|
||||
ninja("minibootstrap", ".minibootstrap/build.ninja")
|
||||
|
|
|
@ -15,13 +15,9 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func absPath(ctx Context, p string) string {
|
||||
|
@ -117,81 +113,3 @@ func decodeKeyValue(str string) (string, string, bool) {
|
|||
}
|
||||
return str[:idx], str[idx+1:], true
|
||||
}
|
||||
|
||||
func isTerminal(w io.Writer) bool {
|
||||
if f, ok := w.(*os.File); ok {
|
||||
var termios syscall.Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(),
|
||||
ioctlGetTermios, uintptr(unsafe.Pointer(&termios)),
|
||||
0, 0, 0)
|
||||
return err == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func termWidth(w io.Writer) (int, bool) {
|
||||
if f, ok := w.(*os.File); ok {
|
||||
var winsize struct {
|
||||
ws_row, ws_column uint16
|
||||
ws_xpixel, ws_ypixel uint16
|
||||
}
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(),
|
||||
syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize)),
|
||||
0, 0, 0)
|
||||
return int(winsize.ws_column), err == 0
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// stripAnsiEscapes strips ANSI control codes from a byte array in place.
|
||||
func stripAnsiEscapes(input []byte) []byte {
|
||||
// read represents the remaining part of input that needs to be processed.
|
||||
read := input
|
||||
// write represents where we should be writing in input.
|
||||
// It will share the same backing store as input so that we make our modifications
|
||||
// in place.
|
||||
write := input
|
||||
|
||||
// advance will copy count bytes from read to write and advance those slices
|
||||
advance := func(write, read []byte, count int) ([]byte, []byte) {
|
||||
copy(write, read[:count])
|
||||
return write[count:], read[count:]
|
||||
}
|
||||
|
||||
for {
|
||||
// Find the next escape sequence
|
||||
i := bytes.IndexByte(read, 0x1b)
|
||||
// If it isn't found, or if there isn't room for <ESC>[, finish
|
||||
if i == -1 || i+1 >= len(read) {
|
||||
copy(write, read)
|
||||
break
|
||||
}
|
||||
|
||||
// Not a CSI code, continue searching
|
||||
if read[i+1] != '[' {
|
||||
write, read = advance(write, read, i+1)
|
||||
continue
|
||||
}
|
||||
|
||||
// Found a CSI code, advance up to the <ESC>
|
||||
write, read = advance(write, read, i)
|
||||
|
||||
// Find the end of the CSI code
|
||||
i = bytes.IndexFunc(read, func(r rune) bool {
|
||||
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
|
||||
})
|
||||
if i == -1 {
|
||||
// We didn't find the end of the code, just remove the rest
|
||||
i = len(read) - 1
|
||||
}
|
||||
|
||||
// Strip off the end marker too
|
||||
i = i + 1
|
||||
|
||||
// Skip the reader forward and reduce final length by that amount
|
||||
read = read[i:]
|
||||
input = input[:len(input)-i]
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
|
|
@ -49,48 +49,3 @@ func TestEnsureEmptyDirs(t *testing.T) {
|
|||
|
||||
ensureEmptyDirectoriesExist(ctx, filepath.Join(tmpDir, "a"))
|
||||
}
|
||||
|
||||
func TestStripAnsiEscapes(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"This is a test",
|
||||
"This is a test",
|
||||
},
|
||||
{
|
||||
"interrupted: \x1b[12",
|
||||
"interrupted: ",
|
||||
},
|
||||
{
|
||||
"other \x1bescape \x1b",
|
||||
"other \x1bescape \x1b",
|
||||
},
|
||||
{ // from pretty-error macro
|
||||
"\x1b[1mart/Android.mk: \x1b[31merror:\x1b[0m\x1b[1m art: test error \x1b[0m",
|
||||
"art/Android.mk: error: art: test error ",
|
||||
},
|
||||
{ // from envsetup.sh make wrapper
|
||||
"\x1b[0;31m#### make failed to build some targets (2 seconds) ####\x1b[00m",
|
||||
"#### make failed to build some targets (2 seconds) ####",
|
||||
},
|
||||
{ // from clang (via ninja testcase)
|
||||
"\x1b[1maffixmgr.cxx:286:15: \x1b[0m\x1b[0;1;35mwarning: \x1b[0m\x1b[1musing the result... [-Wparentheses]\x1b[0m",
|
||||
"affixmgr.cxx:286:15: warning: using the result... [-Wparentheses]",
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
got := string(stripAnsiEscapes([]byte(tc.input)))
|
||||
if got != tc.output {
|
||||
t.Errorf("output strings didn't match\n"+
|
||||
"input: %#v\n"+
|
||||
" want: %#v\n"+
|
||||
" got: %#v", tc.input, tc.output, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.
|
|
@ -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,
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
aprotoc --go_out=paths=source_relative:. frontend.proto
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// 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-terminal",
|
||||
pkgPath: "android/soong/ui/terminal",
|
||||
deps: ["soong-ui-status"],
|
||||
srcs: [
|
||||
"status.go",
|
||||
"writer.go",
|
||||
"util.go",
|
||||
],
|
||||
testSrcs: [
|
||||
"util_test.go",
|
||||
],
|
||||
darwin: {
|
||||
srcs: [
|
||||
"util_darwin.go",
|
||||
],
|
||||
},
|
||||
linux: {
|
||||
srcs: [
|
||||
"util_linux.go",
|
||||
],
|
||||
},
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
// 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 terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"android/soong/ui/status"
|
||||
)
|
||||
|
||||
type statusOutput struct {
|
||||
writer Writer
|
||||
format string
|
||||
|
||||
start time.Time
|
||||
}
|
||||
|
||||
// NewStatusOutput returns a StatusOutput that represents the
|
||||
// current build status similarly to Ninja's built-in terminal
|
||||
// output.
|
||||
//
|
||||
// statusFormat takes nearly all the same options as NINJA_STATUS.
|
||||
// %c is currently unsupported.
|
||||
func NewStatusOutput(w Writer, statusFormat string) status.StatusOutput {
|
||||
return &statusOutput{
|
||||
writer: w,
|
||||
format: statusFormat,
|
||||
|
||||
start: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *statusOutput) Message(level status.MsgLevel, message string) {
|
||||
if level > status.StatusLvl {
|
||||
s.writer.Print(fmt.Sprintf("%s%s", level.Prefix(), message))
|
||||
} else if level == status.StatusLvl {
|
||||
s.writer.StatusLine(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *statusOutput) StartAction(action *status.Action, counts status.Counts) {
|
||||
if !s.writer.isSmartTerminal() {
|
||||
return
|
||||
}
|
||||
|
||||
str := action.Description
|
||||
if str == "" {
|
||||
str = action.Command
|
||||
}
|
||||
|
||||
s.writer.StatusLine(s.progress(counts) + str)
|
||||
}
|
||||
|
||||
func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
|
||||
str := result.Description
|
||||
if str == "" {
|
||||
str = result.Command
|
||||
}
|
||||
|
||||
progress := s.progress(counts) + str
|
||||
|
||||
if result.Error != nil {
|
||||
hasCommand := ""
|
||||
if result.Command != "" {
|
||||
hasCommand = "\n"
|
||||
}
|
||||
|
||||
s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s%s%s",
|
||||
strings.Join(result.Outputs, " "), result.Command, hasCommand, result.Output))
|
||||
} else if result.Output != "" {
|
||||
s.writer.StatusAndMessage(progress, result.Output)
|
||||
} else {
|
||||
s.writer.StatusLine(progress)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *statusOutput) Flush() {}
|
||||
|
||||
func (s *statusOutput) progress(counts status.Counts) string {
|
||||
if s.format == "" {
|
||||
return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
|
||||
}
|
||||
|
||||
buf := &strings.Builder{}
|
||||
for i := 0; i < len(s.format); i++ {
|
||||
c := s.format[i]
|
||||
if c != '%' {
|
||||
buf.WriteByte(c)
|
||||
continue
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
if i == len(s.format) {
|
||||
buf.WriteByte(c)
|
||||
break
|
||||
}
|
||||
|
||||
c = s.format[i]
|
||||
switch c {
|
||||
case '%':
|
||||
buf.WriteByte(c)
|
||||
case 's':
|
||||
fmt.Fprintf(buf, "%d", counts.StartedActions)
|
||||
case 't':
|
||||
fmt.Fprintf(buf, "%d", counts.TotalActions)
|
||||
case 'r':
|
||||
fmt.Fprintf(buf, "%d", counts.RunningActions)
|
||||
case 'u':
|
||||
fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
|
||||
case 'f':
|
||||
fmt.Fprintf(buf, "%d", counts.FinishedActions)
|
||||
case 'o':
|
||||
fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
|
||||
case 'c':
|
||||
// TODO: implement?
|
||||
buf.WriteRune('?')
|
||||
case 'p':
|
||||
fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
|
||||
case 'e':
|
||||
fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
|
||||
default:
|
||||
buf.WriteString("unknown placeholder '")
|
||||
buf.WriteByte(c)
|
||||
buf.WriteString("'")
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
// 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.
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func isTerminal(w io.Writer) bool {
|
||||
if f, ok := w.(*os.File); ok {
|
||||
var termios syscall.Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(),
|
||||
ioctlGetTermios, uintptr(unsafe.Pointer(&termios)),
|
||||
0, 0, 0)
|
||||
return err == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func termWidth(w io.Writer) (int, bool) {
|
||||
if f, ok := w.(*os.File); ok {
|
||||
var winsize struct {
|
||||
ws_row, ws_column uint16
|
||||
ws_xpixel, ws_ypixel uint16
|
||||
}
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(),
|
||||
syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize)),
|
||||
0, 0, 0)
|
||||
return int(winsize.ws_column), err == 0
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// stripAnsiEscapes strips ANSI control codes from a byte array in place.
|
||||
func stripAnsiEscapes(input []byte) []byte {
|
||||
// read represents the remaining part of input that needs to be processed.
|
||||
read := input
|
||||
// write represents where we should be writing in input.
|
||||
// It will share the same backing store as input so that we make our modifications
|
||||
// in place.
|
||||
write := input
|
||||
|
||||
// advance will copy count bytes from read to write and advance those slices
|
||||
advance := func(write, read []byte, count int) ([]byte, []byte) {
|
||||
copy(write, read[:count])
|
||||
return write[count:], read[count:]
|
||||
}
|
||||
|
||||
for {
|
||||
// Find the next escape sequence
|
||||
i := bytes.IndexByte(read, 0x1b)
|
||||
// If it isn't found, or if there isn't room for <ESC>[, finish
|
||||
if i == -1 || i+1 >= len(read) {
|
||||
copy(write, read)
|
||||
break
|
||||
}
|
||||
|
||||
// Not a CSI code, continue searching
|
||||
if read[i+1] != '[' {
|
||||
write, read = advance(write, read, i+1)
|
||||
continue
|
||||
}
|
||||
|
||||
// Found a CSI code, advance up to the <ESC>
|
||||
write, read = advance(write, read, i)
|
||||
|
||||
// Find the end of the CSI code
|
||||
i = bytes.IndexFunc(read, func(r rune) bool {
|
||||
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
|
||||
})
|
||||
if i == -1 {
|
||||
// We didn't find the end of the code, just remove the rest
|
||||
i = len(read) - 1
|
||||
}
|
||||
|
||||
// Strip off the end marker too
|
||||
i = i + 1
|
||||
|
||||
// Skip the reader forward and reduce final length by that amount
|
||||
read = read[i:]
|
||||
input = input[:len(input)-i]
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"syscall"
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"syscall"
|
|
@ -0,0 +1,64 @@
|
|||
// 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.
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStripAnsiEscapes(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"This is a test",
|
||||
"This is a test",
|
||||
},
|
||||
{
|
||||
"interrupted: \x1b[12",
|
||||
"interrupted: ",
|
||||
},
|
||||
{
|
||||
"other \x1bescape \x1b",
|
||||
"other \x1bescape \x1b",
|
||||
},
|
||||
{ // from pretty-error macro
|
||||
"\x1b[1mart/Android.mk: \x1b[31merror:\x1b[0m\x1b[1m art: test error \x1b[0m",
|
||||
"art/Android.mk: error: art: test error ",
|
||||
},
|
||||
{ // from envsetup.sh make wrapper
|
||||
"\x1b[0;31m#### make failed to build some targets (2 seconds) ####\x1b[00m",
|
||||
"#### make failed to build some targets (2 seconds) ####",
|
||||
},
|
||||
{ // from clang (via ninja testcase)
|
||||
"\x1b[1maffixmgr.cxx:286:15: \x1b[0m\x1b[0;1;35mwarning: \x1b[0m\x1b[1musing the result... [-Wparentheses]\x1b[0m",
|
||||
"affixmgr.cxx:286:15: warning: using the result... [-Wparentheses]",
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
got := string(stripAnsiEscapes([]byte(tc.input)))
|
||||
if got != tc.output {
|
||||
t.Errorf("output strings didn't match\n"+
|
||||
"input: %#v\n"+
|
||||
" want: %#v\n"+
|
||||
" got: %#v", tc.input, tc.output, got)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
// 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 terminal provides a set of interfaces that can be used to interact
|
||||
// with the terminal (including falling back when the terminal is detected to
|
||||
// be a redirect or other dumb terminal)
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Writer provides an interface to write temporary and permanent messages to
|
||||
// the terminal.
|
||||
//
|
||||
// The terminal is considered to be a dumb terminal if TERM==dumb, or if a
|
||||
// terminal isn't detected on stdout/stderr (generally because it's a pipe or
|
||||
// file). Dumb terminals will strip out all ANSI escape sequences, including
|
||||
// colors.
|
||||
type Writer interface {
|
||||
// Print prints the string to the terminal, overwriting any current
|
||||
// status being displayed.
|
||||
//
|
||||
// On a dumb terminal, the status messages will be kept.
|
||||
Print(str string)
|
||||
|
||||
// Status prints the first line of the string to the terminal,
|
||||
// overwriting any previous status line. Strings longer than the width
|
||||
// of the terminal will be cut off.
|
||||
//
|
||||
// On a dumb terminal, previous status messages will remain, and the
|
||||
// entire first line of the string will be printed.
|
||||
StatusLine(str string)
|
||||
|
||||
// StatusAndMessage prints the first line of status to the terminal,
|
||||
// similarly to StatusLine(), then prints the full msg below that. The
|
||||
// status line is retained.
|
||||
//
|
||||
// There is guaranteed to be no other output in between the status and
|
||||
// message.
|
||||
StatusAndMessage(status, msg string)
|
||||
|
||||
// Finish ensures that the output ends with a newline (preserving any
|
||||
// current status line that is current displayed).
|
||||
//
|
||||
// This does nothing on dumb terminals.
|
||||
Finish()
|
||||
|
||||
// Write implements the io.Writer interface. This is primarily so that
|
||||
// the logger can use this interface to print to stderr without
|
||||
// breaking the other semantics of this interface.
|
||||
//
|
||||
// Try to use any of the other functions if possible.
|
||||
Write(p []byte) (n int, err error)
|
||||
|
||||
isSmartTerminal() bool
|
||||
}
|
||||
|
||||
// NewWriter creates a new Writer based on the stdio and the TERM
|
||||
// environment variable.
|
||||
func NewWriter(stdio StdioInterface) Writer {
|
||||
w := &writerImpl{
|
||||
stdio: stdio,
|
||||
|
||||
haveBlankLine: true,
|
||||
}
|
||||
|
||||
if term, ok := os.LookupEnv("TERM"); ok && term != "dumb" {
|
||||
w.stripEscapes = !isTerminal(stdio.Stderr())
|
||||
w.smartTerminal = isTerminal(stdio.Stdout()) && !w.stripEscapes
|
||||
}
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
type writerImpl struct {
|
||||
stdio StdioInterface
|
||||
|
||||
haveBlankLine bool
|
||||
|
||||
// Protecting the above, we assume that smartTerminal and stripEscapes
|
||||
// does not change after initial setup.
|
||||
lock sync.Mutex
|
||||
|
||||
smartTerminal bool
|
||||
stripEscapes bool
|
||||
}
|
||||
|
||||
func (w *writerImpl) isSmartTerminal() bool {
|
||||
return w.smartTerminal
|
||||
}
|
||||
|
||||
func (w *writerImpl) requestLine() {
|
||||
if !w.haveBlankLine {
|
||||
fmt.Fprintln(w.stdio.Stdout())
|
||||
w.haveBlankLine = true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writerImpl) Print(str string) {
|
||||
if w.stripEscapes {
|
||||
str = string(stripAnsiEscapes([]byte(str)))
|
||||
}
|
||||
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
w.print(str)
|
||||
}
|
||||
|
||||
func (w *writerImpl) print(str string) {
|
||||
if !w.haveBlankLine {
|
||||
fmt.Fprint(w.stdio.Stdout(), "\r", "\x1b[K")
|
||||
w.haveBlankLine = true
|
||||
}
|
||||
fmt.Fprint(w.stdio.Stderr(), str)
|
||||
if len(str) == 0 || str[len(str)-1] != '\n' {
|
||||
fmt.Fprint(w.stdio.Stderr(), "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writerImpl) StatusLine(str string) {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
w.statusLine(str)
|
||||
}
|
||||
|
||||
func (w *writerImpl) statusLine(str string) {
|
||||
if !w.smartTerminal {
|
||||
fmt.Fprintln(w.stdio.Stdout(), str)
|
||||
return
|
||||
}
|
||||
|
||||
idx := strings.IndexRune(str, '\n')
|
||||
if idx != -1 {
|
||||
str = str[0:idx]
|
||||
}
|
||||
|
||||
// Limit line width to the terminal width, otherwise we'll wrap onto
|
||||
// another line and we won't delete the previous line.
|
||||
//
|
||||
// Run this on every line in case the window has been resized while
|
||||
// we're printing. This could be optimized to only re-run when we get
|
||||
// SIGWINCH if it ever becomes too time consuming.
|
||||
if max, ok := termWidth(w.stdio.Stdout()); ok {
|
||||
if len(str) > max {
|
||||
// TODO: Just do a max. Ninja elides the middle, but that's
|
||||
// more complicated and these lines aren't that important.
|
||||
str = str[:max]
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the beginning on the line, print the output, then clear
|
||||
// the rest of the line.
|
||||
fmt.Fprint(w.stdio.Stdout(), "\r", str, "\x1b[K")
|
||||
w.haveBlankLine = false
|
||||
}
|
||||
|
||||
func (w *writerImpl) StatusAndMessage(status, msg string) {
|
||||
if w.stripEscapes {
|
||||
msg = string(stripAnsiEscapes([]byte(msg)))
|
||||
}
|
||||
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
w.statusLine(status)
|
||||
w.requestLine()
|
||||
w.print(msg)
|
||||
}
|
||||
|
||||
func (w *writerImpl) Finish() {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
w.requestLine()
|
||||
}
|
||||
|
||||
func (w *writerImpl) Write(p []byte) (n int, err error) {
|
||||
w.Print(string(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// StdioInterface represents a set of stdin/stdout/stderr Reader/Writers
|
||||
type StdioInterface interface {
|
||||
Stdin() io.Reader
|
||||
Stdout() io.Writer
|
||||
Stderr() io.Writer
|
||||
}
|
||||
|
||||
// StdioImpl uses the OS stdin/stdout/stderr to implement StdioInterface
|
||||
type StdioImpl struct{}
|
||||
|
||||
func (StdioImpl) Stdin() io.Reader { return os.Stdin }
|
||||
func (StdioImpl) Stdout() io.Writer { return os.Stdout }
|
||||
func (StdioImpl) Stderr() io.Writer { return os.Stderr }
|
||||
|
||||
var _ StdioInterface = StdioImpl{}
|
||||
|
||||
type customStdio struct {
|
||||
stdin io.Reader
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
}
|
||||
|
||||
func NewCustomStdio(stdin io.Reader, stdout, stderr io.Writer) StdioInterface {
|
||||
return customStdio{stdin, stdout, stderr}
|
||||
}
|
||||
|
||||
func (c customStdio) Stdin() io.Reader { return c.stdin }
|
||||
func (c customStdio) Stdout() io.Writer { return c.stdout }
|
||||
func (c customStdio) Stderr() io.Writer { return c.stderr }
|
||||
|
||||
var _ StdioInterface = customStdio{}
|
|
@ -15,10 +15,13 @@
|
|||
bootstrap_go_package {
|
||||
name: "soong-ui-tracer",
|
||||
pkgPath: "android/soong/ui/tracer",
|
||||
deps: ["soong-ui-logger"],
|
||||
deps: [
|
||||
"soong-ui-logger",
|
||||
"soong-ui-status",
|
||||
],
|
||||
srcs: [
|
||||
"microfactory.go",
|
||||
"ninja.go",
|
||||
"status.go",
|
||||
"tracer.go",
|
||||
],
|
||||
}
|
||||
|
|
|
@ -17,10 +17,48 @@ package tracer
|
|||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type eventEntry struct {
|
||||
Name string
|
||||
Begin uint64
|
||||
End uint64
|
||||
}
|
||||
|
||||
func (t *tracerImpl) importEvents(entries []*eventEntry) {
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
return entries[i].Begin < entries[j].Begin
|
||||
})
|
||||
|
||||
cpus := []uint64{}
|
||||
for _, entry := range entries {
|
||||
tid := -1
|
||||
for cpu, endTime := range cpus {
|
||||
if endTime <= entry.Begin {
|
||||
tid = cpu
|
||||
cpus[cpu] = entry.End
|
||||
break
|
||||
}
|
||||
}
|
||||
if tid == -1 {
|
||||
tid = len(cpus)
|
||||
cpus = append(cpus, entry.End)
|
||||
}
|
||||
|
||||
t.writeEvent(&viewerEvent{
|
||||
Name: entry.Name,
|
||||
Phase: "X",
|
||||
Time: entry.Begin,
|
||||
Dur: entry.End - entry.Begin,
|
||||
Pid: 1,
|
||||
Tid: uint64(tid),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tracerImpl) ImportMicrofactoryLog(filename string) {
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
return
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
// Copyright 2016 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 tracer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type eventEntry struct {
|
||||
Name string
|
||||
Begin uint64
|
||||
End uint64
|
||||
}
|
||||
|
||||
func (t *tracerImpl) importEvents(entries []*eventEntry) {
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
return entries[i].Begin < entries[j].Begin
|
||||
})
|
||||
|
||||
cpus := []uint64{}
|
||||
for _, entry := range entries {
|
||||
tid := -1
|
||||
for cpu, endTime := range cpus {
|
||||
if endTime <= entry.Begin {
|
||||
tid = cpu
|
||||
cpus[cpu] = entry.End
|
||||
break
|
||||
}
|
||||
}
|
||||
if tid == -1 {
|
||||
tid = len(cpus)
|
||||
cpus = append(cpus, entry.End)
|
||||
}
|
||||
|
||||
t.writeEvent(&viewerEvent{
|
||||
Name: entry.Name,
|
||||
Phase: "X",
|
||||
Time: entry.Begin,
|
||||
Dur: entry.End - entry.Begin,
|
||||
Pid: 1,
|
||||
Tid: uint64(tid),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ImportNinjaLog reads a .ninja_log file from ninja and writes the events out
|
||||
// to the trace.
|
||||
//
|
||||
// startOffset is when the ninja process started, and is used to position the
|
||||
// relative times from the ninja log into the trace. It's also used to skip
|
||||
// reading the ninja log if nothing was run.
|
||||
func (t *tracerImpl) ImportNinjaLog(thread Thread, filename string, startOffset time.Time) {
|
||||
t.Begin("ninja log import", thread)
|
||||
defer t.End(thread)
|
||||
|
||||
if stat, err := os.Stat(filename); err != nil {
|
||||
t.log.Println("Missing ninja log:", err)
|
||||
return
|
||||
} else if stat.ModTime().Before(startOffset) {
|
||||
t.log.Verboseln("Ninja log not modified, not importing any entries.")
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
t.log.Println("Error opening ninja log:", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
header := true
|
||||
entries := []*eventEntry{}
|
||||
prevEnd := 0
|
||||
offset := uint64(startOffset.UnixNano()) / 1000
|
||||
for s.Scan() {
|
||||
if header {
|
||||
hdr := s.Text()
|
||||
if hdr != "# ninja log v5" {
|
||||
t.log.Printf("Unknown ninja log header: %q", hdr)
|
||||
return
|
||||
}
|
||||
header = false
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Split(s.Text(), "\t")
|
||||
begin, err := strconv.Atoi(fields[0])
|
||||
if err != nil {
|
||||
t.log.Printf("Unable to parse ninja entry %q: %v", s.Text(), err)
|
||||
return
|
||||
}
|
||||
end, err := strconv.Atoi(fields[1])
|
||||
if err != nil {
|
||||
t.log.Printf("Unable to parse ninja entry %q: %v", s.Text(), err)
|
||||
return
|
||||
}
|
||||
if end < prevEnd {
|
||||
entries = nil
|
||||
}
|
||||
prevEnd = end
|
||||
entries = append(entries, &eventEntry{
|
||||
Name: fields[3],
|
||||
Begin: offset + uint64(begin)*1000,
|
||||
End: offset + uint64(end)*1000,
|
||||
})
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
t.log.Println("Unable to parse ninja log:", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.importEvents(entries)
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// 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 tracer
|
||||
|
||||
import (
|
||||
"android/soong/ui/status"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (t *tracerImpl) StatusTracer() status.StatusOutput {
|
||||
return &statusOutput{
|
||||
tracer: t,
|
||||
|
||||
running: map[*status.Action]actionStatus{},
|
||||
}
|
||||
}
|
||||
|
||||
type actionStatus struct {
|
||||
cpu int
|
||||
start time.Time
|
||||
}
|
||||
|
||||
type statusOutput struct {
|
||||
tracer *tracerImpl
|
||||
|
||||
cpus []bool
|
||||
running map[*status.Action]actionStatus
|
||||
}
|
||||
|
||||
func (s *statusOutput) StartAction(action *status.Action, counts status.Counts) {
|
||||
cpu := -1
|
||||
for i, busy := range s.cpus {
|
||||
if !busy {
|
||||
cpu = i
|
||||
s.cpus[i] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if cpu == -1 {
|
||||
cpu = len(s.cpus)
|
||||
s.cpus = append(s.cpus, true)
|
||||
}
|
||||
|
||||
s.running[action] = actionStatus{
|
||||
cpu: cpu,
|
||||
start: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
|
||||
start, ok := s.running[result.Action]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
delete(s.running, result.Action)
|
||||
s.cpus[start.cpu] = false
|
||||
|
||||
str := result.Action.Description
|
||||
if len(result.Action.Outputs) > 0 {
|
||||
str = result.Action.Outputs[0]
|
||||
}
|
||||
|
||||
s.tracer.writeEvent(&viewerEvent{
|
||||
Name: str,
|
||||
Phase: "X",
|
||||
Time: uint64(start.start.UnixNano()) / 1000,
|
||||
Dur: uint64(time.Since(start.start).Nanoseconds()) / 1000,
|
||||
Pid: 1,
|
||||
Tid: uint64(start.cpu),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *statusOutput) Flush() {}
|
||||
func (s *statusOutput) Message(level status.MsgLevel, message string) {}
|
|
@ -31,6 +31,7 @@ import (
|
|||
"time"
|
||||
|
||||
"android/soong/ui/logger"
|
||||
"android/soong/ui/status"
|
||||
)
|
||||
|
||||
type Thread uint64
|
||||
|
@ -46,7 +47,8 @@ type Tracer interface {
|
|||
Complete(name string, thread Thread, begin, end uint64)
|
||||
|
||||
ImportMicrofactoryLog(filename string)
|
||||
ImportNinjaLog(thread Thread, filename string, startOffset time.Time)
|
||||
|
||||
StatusTracer() status.StatusOutput
|
||||
|
||||
NewThread(name string) Thread
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue