Remove terminal.Writer

terminal.Writer is now just a wrapper around stdio.Stdout() without
any useful functionality.  Replace it with stdio.Stdout() as an
io.Writer.

Test: ui/terminal/status_test.go
Change-Id: I5bc5476afdca950b505642f0135a3af9d37fbe24
This commit is contained in:
Colin Cross
2019-06-08 21:48:58 -07:00
parent ce525350f4
commit 097ed2a37c
12 changed files with 90 additions and 183 deletions

View File

@@ -156,10 +156,9 @@ type mpContext struct {
} }
func main() { func main() {
writer := terminal.NewWriter(terminal.StdioImpl{}) stdio := terminal.StdioImpl{}
defer writer.Finish()
log := logger.New(writer) log := logger.New(stdio.Stdout())
defer log.Cleanup() defer log.Cleanup()
flag.Parse() flag.Parse()
@@ -172,7 +171,7 @@ func main() {
stat := &status.Status{} stat := &status.Status{}
defer stat.Finish() defer stat.Finish()
stat.AddOutput(terminal.NewStatusOutput(writer, "", stat.AddOutput(terminal.NewStatusOutput(stdio.Stdout(), "",
build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))) build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
var failures failureCount var failures failureCount
@@ -188,7 +187,7 @@ func main() {
Context: ctx, Context: ctx,
Logger: log, Logger: log,
Tracer: trace, Tracer: trace,
Writer: writer, Writer: stdio.Stdout(),
Status: stat, Status: stat,
}} }}
@@ -341,7 +340,7 @@ func main() {
} else if failures > 1 { } else if failures > 1 {
log.Fatalf("%d failures", failures) log.Fatalf("%d failures", failures)
} else { } else {
writer.Print("Success") fmt.Fprintln(stdio.Stdout(), "Success")
} }
} }
@@ -386,7 +385,7 @@ func buildProduct(mpctx *mpContext, product string) {
Context: mpctx.Context, Context: mpctx.Context,
Logger: log, Logger: log,
Tracer: mpctx.Tracer, Tracer: mpctx.Tracer,
Writer: terminal.NewWriter(terminal.NewCustomStdio(nil, f, f)), Writer: f,
Thread: mpctx.Tracer.NewThread(product), Thread: mpctx.Tracer.NewThread(product),
Status: &status.Status{}, Status: &status.Status{},
}} }}

View File

@@ -109,10 +109,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
writer := terminal.NewWriter(c.stdio()) log := logger.New(c.stdio().Stdout())
defer writer.Finish()
log := logger.New(writer)
defer log.Cleanup() defer log.Cleanup()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@@ -125,7 +122,7 @@ func main() {
stat := &status.Status{} stat := &status.Status{}
defer stat.Finish() defer stat.Finish()
stat.AddOutput(terminal.NewStatusOutput(writer, os.Getenv("NINJA_STATUS"), stat.AddOutput(terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"),
build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))) build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
stat.AddOutput(trace.StatusTracer()) stat.AddOutput(trace.StatusTracer())
@@ -140,7 +137,7 @@ func main() {
Logger: log, Logger: log,
Metrics: met, Metrics: met,
Tracer: trace, Tracer: trace,
Writer: writer, Writer: c.stdio().Stdout(),
Status: stat, Status: stat,
}} }}
@@ -312,13 +309,13 @@ func dumpVarConfig(ctx build.Context, args ...string) build.Config {
func make(ctx build.Context, config build.Config, _ []string, logsDir string) { func make(ctx build.Context, config build.Config, _ []string, logsDir string) {
if config.IsVerbose() { if config.IsVerbose() {
writer := ctx.Writer writer := ctx.Writer
writer.Print("! The argument `showcommands` is no longer supported.") fmt.Fprintln(writer, "! The argument `showcommands` is no longer supported.")
writer.Print("! Instead, the verbose log is always written to a compressed file in the output dir:") fmt.Fprintln(writer, "! Instead, the verbose log is always written to a compressed file in the output dir:")
writer.Print("!") fmt.Fprintln(writer, "!")
writer.Print(fmt.Sprintf("! gzip -cd %s/verbose.log.gz | less -R", logsDir)) fmt.Fprintf(writer, "! gzip -cd %s/verbose.log.gz | less -R\n", logsDir)
writer.Print("!") fmt.Fprintln(writer, "!")
writer.Print("! Older versions are saved in verbose.log.#.gz files") fmt.Fprintln(writer, "! Older versions are saved in verbose.log.#.gz files")
writer.Print("") fmt.Fprintln(writer, "")
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
} }

View File

@@ -22,14 +22,13 @@ import (
"testing" "testing"
"android/soong/ui/logger" "android/soong/ui/logger"
"android/soong/ui/terminal"
) )
func testContext() Context { func testContext() Context {
return Context{&ContextImpl{ return Context{&ContextImpl{
Context: context.Background(), Context: context.Background(),
Logger: logger.New(&bytes.Buffer{}), Logger: logger.New(&bytes.Buffer{}),
Writer: terminal.NewWriter(terminal.NewCustomStdio(&bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{})), Writer: &bytes.Buffer{},
}} }}
} }

View File

@@ -16,12 +16,12 @@ package build
import ( import (
"context" "context"
"io"
"android/soong/ui/logger" "android/soong/ui/logger"
"android/soong/ui/metrics" "android/soong/ui/metrics"
"android/soong/ui/metrics/metrics_proto" "android/soong/ui/metrics/metrics_proto"
"android/soong/ui/status" "android/soong/ui/status"
"android/soong/ui/terminal"
"android/soong/ui/tracer" "android/soong/ui/tracer"
) )
@@ -35,7 +35,7 @@ type ContextImpl struct {
Metrics *metrics.Metrics Metrics *metrics.Metrics
Writer terminal.Writer Writer io.Writer
Status *status.Status Status *status.Status
Thread tracer.Thread Thread tracer.Thread

View File

@@ -249,7 +249,7 @@ func runMakeProductConfig(ctx Context, config Config) {
env := config.Environment() env := config.Environment()
// Print the banner like make does // Print the banner like make does
if !env.IsEnvTrue("ANDROID_QUIET_BUILD") { if !env.IsEnvTrue("ANDROID_QUIET_BUILD") {
ctx.Writer.Print(Banner(make_vars)) fmt.Fprintln(ctx.Writer, Banner(make_vars))
} }
// Populate the environment // Populate the environment

View File

@@ -21,7 +21,7 @@ bootstrap_go_package {
"format.go", "format.go",
"smart_status.go", "smart_status.go",
"status.go", "status.go",
"writer.go", "stdio.go",
"util.go", "util.go",
], ],
testSrcs: [ testSrcs: [

View File

@@ -16,19 +16,20 @@ package terminal
import ( import (
"fmt" "fmt"
"io"
"android/soong/ui/status" "android/soong/ui/status"
) )
type dumbStatusOutput struct { type dumbStatusOutput struct {
writer Writer writer io.Writer
formatter formatter formatter formatter
} }
// NewDumbStatusOutput returns a StatusOutput that represents the // NewDumbStatusOutput returns a StatusOutput that represents the
// current build status similarly to Ninja's built-in terminal // current build status similarly to Ninja's built-in terminal
// output. // output.
func NewDumbStatusOutput(w Writer, formatter formatter) status.StatusOutput { func NewDumbStatusOutput(w io.Writer, formatter formatter) status.StatusOutput {
return &dumbStatusOutput{ return &dumbStatusOutput{
writer: w, writer: w,
formatter: formatter, formatter: formatter,

View File

@@ -16,6 +16,7 @@ package terminal
import ( import (
"fmt" "fmt"
"io"
"strings" "strings"
"sync" "sync"
@@ -23,7 +24,7 @@ import (
) )
type smartStatusOutput struct { type smartStatusOutput struct {
writer Writer writer io.Writer
formatter formatter formatter formatter
lock sync.Mutex lock sync.Mutex
@@ -34,7 +35,7 @@ type smartStatusOutput struct {
// NewSmartStatusOutput returns a StatusOutput that represents the // NewSmartStatusOutput returns a StatusOutput that represents the
// current build status similarly to Ninja's built-in terminal // current build status similarly to Ninja's built-in terminal
// output. // output.
func NewSmartStatusOutput(w Writer, formatter formatter) status.StatusOutput { func NewSmartStatusOutput(w io.Writer, formatter formatter) status.StatusOutput {
return &smartStatusOutput{ return &smartStatusOutput{
writer: w, writer: w,
formatter: formatter, formatter: formatter,
@@ -133,7 +134,7 @@ func (s *smartStatusOutput) statusLine(str string) {
// Run this on every line in case the window has been resized while // 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 // we're printing. This could be optimized to only re-run when we get
// SIGWINCH if it ever becomes too time consuming. // SIGWINCH if it ever becomes too time consuming.
if max, ok := s.writer.termWidth(); ok { if max, ok := termWidth(s.writer); ok {
if len(str) > max { if len(str) > max {
// TODO: Just do a max. Ninja elides the middle, but that's // TODO: Just do a max. Ninja elides the middle, but that's
// more complicated and these lines aren't that important. // more complicated and these lines aren't that important.

View File

@@ -15,6 +15,8 @@
package terminal package terminal
import ( import (
"io"
"android/soong/ui/status" "android/soong/ui/status"
) )
@@ -24,10 +26,10 @@ import (
// //
// statusFormat takes nearly all the same options as NINJA_STATUS. // statusFormat takes nearly all the same options as NINJA_STATUS.
// %c is currently unsupported. // %c is currently unsupported.
func NewStatusOutput(w Writer, statusFormat string, quietBuild bool) status.StatusOutput { func NewStatusOutput(w io.Writer, statusFormat string, quietBuild bool) status.StatusOutput {
formatter := newFormatter(statusFormat, quietBuild) formatter := newFormatter(statusFormat, quietBuild)
if w.isSmartTerminal() { if isSmartTerminal(w) {
return NewSmartStatusOutput(w, formatter) return NewSmartStatusOutput(w, formatter)
} else { } else {
return NewDumbStatusOutput(w, formatter) return NewDumbStatusOutput(w, formatter)

View File

@@ -89,17 +89,9 @@ func TestStatusOutput(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Run("smart", func(t *testing.T) { t.Run("smart", func(t *testing.T) {
smart := &fakeSmartTerminal{termWidth: 40} smart := &fakeSmartTerminal{termWidth: 40}
stdio := customStdio{ stat := NewStatusOutput(smart, "", false)
stdin: nil,
stdout: smart,
stderr: nil,
}
writer := NewWriter(stdio)
stat := NewStatusOutput(writer, "", false)
tt.calls(stat) tt.calls(stat)
stat.Flush() stat.Flush()
writer.Finish()
if g, w := smart.String(), tt.smart; g != w { if g, w := smart.String(), tt.smart; g != w {
t.Errorf("want:\n%q\ngot:\n%q", w, g) t.Errorf("want:\n%q\ngot:\n%q", w, g)
@@ -108,17 +100,9 @@ func TestStatusOutput(t *testing.T) {
t.Run("dumb", func(t *testing.T) { t.Run("dumb", func(t *testing.T) {
dumb := &bytes.Buffer{} dumb := &bytes.Buffer{}
stdio := customStdio{ stat := NewStatusOutput(dumb, "", false)
stdin: nil,
stdout: dumb,
stderr: nil,
}
writer := NewWriter(stdio)
stat := NewStatusOutput(writer, "", false)
tt.calls(stat) tt.calls(stat)
stat.Flush() stat.Flush()
writer.Finish()
if g, w := dumb.String(), tt.dumb; g != w { if g, w := dumb.String(), tt.dumb; g != w {
t.Errorf("want:\n%q\ngot:\n%q", w, g) t.Errorf("want:\n%q\ngot:\n%q", w, g)
@@ -267,15 +251,7 @@ func actionWithOuptutWithAnsiCodes(stat status.StatusOutput) {
func TestSmartStatusOutputWidthChange(t *testing.T) { func TestSmartStatusOutputWidthChange(t *testing.T) {
smart := &fakeSmartTerminal{termWidth: 40} smart := &fakeSmartTerminal{termWidth: 40}
stat := NewStatusOutput(smart, "", false)
stdio := customStdio{
stdin: nil,
stdout: smart,
stderr: nil,
}
writer := NewWriter(stdio)
stat := NewStatusOutput(writer, "", false)
runner := newRunner(stat, 2) runner := newRunner(stat, 2)
@@ -287,7 +263,6 @@ func TestSmartStatusOutputWidthChange(t *testing.T) {
runner.finishAction(result) runner.finishAction(result)
stat.Flush() stat.Flush()
writer.Finish()
w := "\r[ 0% 0/2] action with very long descrip\x1b[K\r[ 50% 1/2] action with very lo\x1b[K\n" w := "\r[ 0% 0/2] action with very long descrip\x1b[K\r[ 50% 1/2] action with very lo\x1b[K\n"

55
ui/terminal/stdio.go Normal file
View File

@@ -0,0 +1,55 @@
// 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 (
"io"
"os"
)
// 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{}

View File

@@ -1,122 +0,0 @@
// 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"
)
// 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)
// 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
termWidth() (int, bool)
}
// NewWriter creates a new Writer based on the stdio and the TERM
// environment variable.
func NewWriter(stdio StdioInterface) Writer {
w := &writerImpl{
stdio: stdio,
}
return w
}
type writerImpl struct {
stdio StdioInterface
}
func (w *writerImpl) Print(str string) {
fmt.Fprint(w.stdio.Stdout(), str)
if len(str) == 0 || str[len(str)-1] != '\n' {
fmt.Fprint(w.stdio.Stdout(), "\n")
}
}
func (w *writerImpl) Finish() {}
func (w *writerImpl) Write(p []byte) (n int, err error) {
return w.stdio.Stdout().Write(p)
}
func (w *writerImpl) isSmartTerminal() bool {
return isSmartTerminal(w.stdio.Stdout())
}
func (w *writerImpl) termWidth() (int, bool) {
return termWidth(w.stdio.Stdout())
}
// 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{}