Skip to content

Commit

Permalink
Implement output runner from bazel command
Browse files Browse the repository at this point in the history
iBazel now watches the log warning from Bazel outputs. Users can watch for
specific commands and apply them automatically. The command matching is
implemented by regex and it is configurable.

Fixes: bazelbuild#18
  • Loading branch information
borkaehw committed Jul 31, 2018
1 parent 1e74b9c commit e9e3a1b
Show file tree
Hide file tree
Showing 21 changed files with 547 additions and 105 deletions.
85 changes: 56 additions & 29 deletions bazel/bazel.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
package bazel

import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"io"
"os"
"os/exec"
"strings"
Expand All @@ -35,9 +37,9 @@ type Bazel interface {
WriteToStdout(v bool)
Info() (map[string]string, error)
Query(args ...string) (*blaze_query.QueryResult, error)
Build(args ...string) error
Test(args ...string) error
Run(args ...string) (*exec.Cmd, error)
Build(args ...string) (*bytes.Buffer, error)
Test(args ...string) (*bytes.Buffer, error)
Run(args ...string) (*exec.Cmd, *bytes.Buffer, error)
Wait() error
Cancel()
}
Expand Down Expand Up @@ -69,17 +71,38 @@ func (b *bazel) WriteToStdout(v bool) {
b.writeToStdout = v
}

func (b *bazel) newCommand(command string, args ...string) {
func (b *bazel) newCommand(command string, args ...string) (*bytes.Buffer, *bytes.Buffer) {
b.ctx, b.cancel = context.WithCancel(context.Background())

args = append([]string{command}, args...)

if b.writeToStderr || b.writeToStdout {
containsColor := false
for _, arg := range args {
if strings.HasPrefix(arg, "--color") {
containsColor = true
}
}
if !containsColor {
args = append(args, "--color=yes")
}
}
b.cmd = exec.CommandContext(b.ctx, *bazelPath, args...)

stdoutBuffer := new(bytes.Buffer)
stderrBuffer := new(bytes.Buffer)
if b.writeToStderr {
b.cmd.Stderr = os.Stderr
b.cmd.Stderr = io.MultiWriter(os.Stderr, stdoutBuffer)
} else {
b.cmd.Stderr = stdoutBuffer
}
if b.writeToStdout {
b.cmd.Stdout = os.Stdout
b.cmd.Stdout = io.MultiWriter(os.Stdout, stderrBuffer)
} else {
b.cmd.Stdout = stderrBuffer
}

return stdoutBuffer, stderrBuffer
}

// Displays information about the state of the bazel process in the
Expand All @@ -99,13 +122,13 @@ func (b *bazel) newCommand(command string, args ...string) {
func (b *bazel) Info() (map[string]string, error) {
b.WriteToStderr(false)
b.WriteToStdout(false)
b.newCommand("info")
stdoutBuffer, _ := b.newCommand("info")

info, err := b.cmd.Output()
err := b.cmd.Run()
if err != nil {
return nil, err
}
return b.processInfo(string(info))
return b.processInfo(stdoutBuffer.String())
}

func (b *bazel) processInfo(info string) (map[string]string, error) {
Expand Down Expand Up @@ -139,58 +162,62 @@ func (b *bazel) processInfo(info string) (map[string]string, error) {
//
// res, err := b.Query('somepath(//path/to/package:target, //dependency)')
func (b *bazel) Query(args ...string) (*blaze_query.QueryResult, error) {
blazeArgs := append([]string(nil), "--output=proto", "--order_output=no")
blazeArgs := append([]string(nil), "--output=proto", "--order_output=no", "--color=no")
blazeArgs = append(blazeArgs, args...)

b.WriteToStderr(true)
b.WriteToStdout(false)
b.newCommand("query", blazeArgs...)
b.WriteToStderr(true)
b.WriteToStdout(false)
_, stderrBuffer := b.newCommand("query", blazeArgs...)

err := b.cmd.Run()

out, err := b.cmd.Output()
if err != nil {
return nil, err
}
return b.processQuery(out)
return b.processQuery(stderrBuffer.Bytes())
}

func (b *bazel) processQuery(out []byte) (*blaze_query.QueryResult, error) {
var qr blaze_query.QueryResult
if err := proto.Unmarshal(out, &qr); err != nil {
fmt.Printf("Could not read blaze query response: %s %s", err, out)
fmt.Fprintf(os.Stderr, "Could not read blaze query response. Error: %s\nOutput: %s\n", err, out)
return nil, err
}

return &qr, nil
}

func (b *bazel) Build(args ...string) error {
b.newCommand("build", append(b.args, args...)...)

func (b *bazel) Build(args ...string) (*bytes.Buffer, error) {
stdoutBuffer, stderrBuffer := b.newCommand("build", append(b.args, args...)...)
err := b.cmd.Run()

return err
_, _= stdoutBuffer.Write(stderrBuffer.Bytes())
return stdoutBuffer, err
}

func (b *bazel) Test(args ...string) error {
b.newCommand("test", append(b.args, args...)...)

func (b *bazel) Test(args ...string) (*bytes.Buffer, error) {
stdoutBuffer, stderrBuffer := b.newCommand("test", append(b.args, args...)...)
err := b.cmd.Run()

return err
_, _ = stdoutBuffer.Write(stderrBuffer.Bytes())
return stdoutBuffer, err
}

// Build the specified target (singular) and run it with the given arguments.
func (b *bazel) Run(args ...string) (*exec.Cmd, error) {
func (b *bazel) Run(args ...string) (*exec.Cmd, *bytes.Buffer, error) {
b.WriteToStderr(true)
b.WriteToStdout(true)
b.newCommand("run", args...)
b.WriteToStdout(true)
stdoutBuffer, stderrBuffer := b.newCommand("run", args...)
b.cmd.Stdin = os.Stdin

_, _ = stdoutBuffer.Write(stderrBuffer.Bytes())

err := b.cmd.Run()
if err != nil {
return nil, err
return nil, stdoutBuffer, err
}

return b.cmd, err
return b.cmd, stdoutBuffer, err
}

func (b *bazel) Wait() error {
Expand Down
28 changes: 16 additions & 12 deletions bazel/bazel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package bazel

import (
"bytes"
"io"
"os"
"reflect"
"testing"
Expand Down Expand Up @@ -49,36 +51,38 @@ KEY3: value`)

func TestWriteToStderrAndStdout(t *testing.T) {
b := &bazel{}
stdoutBuffer := new(bytes.Buffer)
stderrBuffer := new(bytes.Buffer)

// By default it should write to its own pipe.
b.newCommand("version")
if b.cmd.Stdout == os.Stdout {
t.Errorf("Set stdout to os.Stdout")
if reflect.DeepEqual(b.cmd.Stdout, io.MultiWriter(os.Stdout, stderrBuffer)) {
t.Errorf("Set stdout to os.Stdout and stderrBuffer")
}
if b.cmd.Stderr == os.Stderr {
t.Errorf("Set stderr to os.Stderr")
if reflect.DeepEqual(b.cmd.Stderr, io.MultiWriter(os.Stderr, stdoutBuffer)) {
t.Errorf("Set stderr to os.Stderr and stdoutBuffer")
}

// If set to true it should write to the os version
b.WriteToStderr(true)
b.WriteToStdout(true)
b.newCommand("version")
if b.cmd.Stdout != os.Stdout {
t.Errorf("Didn't set stdout to os.Stdout")
if !reflect.DeepEqual(b.cmd.Stdout, io.MultiWriter(os.Stdout, stderrBuffer)) {
t.Errorf("Didn't set stdout to os.Stdout and stderrBuffer")
}
if b.cmd.Stderr != os.Stderr {
t.Errorf("Didn't set stderr to os.Stderr")
if !reflect.DeepEqual(b.cmd.Stderr, io.MultiWriter(os.Stderr, stdoutBuffer)) {
t.Errorf("Didn't set stderr to os.Stderr and stdoutBuffer")
}

// If set to false it should not write to the os version
b.WriteToStderr(false)
b.WriteToStdout(false)
b.newCommand("version")
if b.cmd.Stdout == os.Stdout {
t.Errorf("Set stdout to os.Stdout")
if reflect.DeepEqual(b.cmd.Stdout, io.MultiWriter(os.Stdout, stderrBuffer)) {
t.Errorf("Set stdout to os.Stdout and stderrBuffer")
}
if b.cmd.Stderr == os.Stderr {
t.Errorf("Set stderr to os.Stderr")
if reflect.DeepEqual(b.cmd.Stderr, io.MultiWriter(os.Stderr, stdoutBuffer)) {
t.Errorf("Set stderr to os.Stderr and stdoutBuffer")
}
}

Expand Down
13 changes: 7 additions & 6 deletions bazel/testing/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package testing

import (
"bytes"
"os/exec"
"regexp"
"testing"
Expand Down Expand Up @@ -62,20 +63,20 @@ func (b *MockBazel) Query(args ...string) (*blaze_query.QueryResult, error) {

return res, nil
}
func (b *MockBazel) Build(args ...string) error {
func (b *MockBazel) Build(args ...string) (*bytes.Buffer, error) {
b.actions = append(b.actions, append([]string{"Build"}, args...))
return b.buildError
return nil, b.buildError
}
func (b *MockBazel) BuildError(e error) {
b.buildError = e
}
func (b *MockBazel) Test(args ...string) error {
func (b *MockBazel) Test(args ...string) (*bytes.Buffer, error) {
b.actions = append(b.actions, append([]string{"Test"}, args...))
return nil
return nil, nil
}
func (b *MockBazel) Run(args ...string) (*exec.Cmd, error) {
func (b *MockBazel) Run(args ...string) (*exec.Cmd, *bytes.Buffer, error) {
b.actions = append(b.actions, append([]string{"Run"}, args...))
return nil, nil
return nil, nil, nil
}
func (b *MockBazel) WaitError(e error) {
b.waitError = e
Expand Down
3 changes: 2 additions & 1 deletion ibazel/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,16 @@ go_library(
"lifecycle.go",
"main.go",
"source_event_handler.go",
"workspace_finder.go"
],
importpath = "github.com/bazelbuild/bazel-watcher/ibazel",
visibility = ["//visibility:private"],
deps = [
"//bazel:go_default_library",
"//ibazel/command:go_default_library",
"//ibazel/output_runner:go_default_library",
"//ibazel/profiler:go_default_library",
"//ibazel/live_reload:go_default_library",
"//ibazel/workspace_finder:go_default_library",
"//third_party/bazel/master/src/main/protobuf:go_default_library",
"@com_github_fsnotify_fsnotify//:go_default_library",
],
Expand Down
11 changes: 6 additions & 5 deletions ibazel/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package command

import (
"bytes"
"fmt"
"io/ioutil"
"os"
Expand All @@ -30,15 +31,15 @@ var bazelNew = bazel.New
// Command is an object that wraps the logic of running a task in Bazel and
// manipulating it.
type Command interface {
Start() error
Start() (*bytes.Buffer, error)
Terminate()
NotifyOfChanges()
NotifyOfChanges() *bytes.Buffer
IsSubprocessRunning() bool
}

// start will be called by most implementations since this logic is extremely
// common.
func start(b bazel.Bazel, target string, args []string) *exec.Cmd {
func start(b bazel.Bazel, target string, args []string) (*bytes.Buffer, *exec.Cmd) {
tmpfile, err := ioutil.TempFile("", "bazel_script_path")
if err != nil {
fmt.Print(err)
Expand All @@ -49,7 +50,7 @@ func start(b bazel.Bazel, target string, args []string) *exec.Cmd {
}

// Start by building the binary
b.Run("--script_path="+tmpfile.Name(), target)
_, outputBuffer, _ := b.Run("--script_path="+tmpfile.Name(), target)

runScriptPath := tmpfile.Name()

Expand All @@ -62,7 +63,7 @@ func start(b bazel.Bazel, target string, args []string) *exec.Cmd {
// Set a process group id (PGID) on the subprocess. This is
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

return cmd
return outputBuffer, cmd
}

func subprocessRunning(cmd *exec.Cmd) bool {
Expand Down
13 changes: 8 additions & 5 deletions ibazel/command/default_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package command

import (
"bytes"
"fmt"
"os"
"os/exec"
Expand Down Expand Up @@ -54,29 +55,31 @@ func (c *defaultCommand) Terminate() {
c.cmd = nil
}

func (c *defaultCommand) Start() error {
func (c *defaultCommand) Start() (*bytes.Buffer, error) {
b := bazelNew()
b.SetArguments(c.bazelArgs)

b.WriteToStderr(true)
b.WriteToStdout(true)

c.cmd = start(b, c.target, c.args)
outputBuffer, foo := start(b, c.target, c.args)
c.cmd = foo

c.cmd.Env = os.Environ()

var err error
if err = c.cmd.Start(); err != nil {
fmt.Fprintf(os.Stderr, "Error starting process: %v\n", err)
return err
return outputBuffer, err
}
fmt.Fprintf(os.Stderr, "Starting...")
return nil
return outputBuffer, nil
}

func (c *defaultCommand) NotifyOfChanges() {
func (c *defaultCommand) NotifyOfChanges() *bytes.Buffer {
c.Terminate()
c.Start()
return nil
}

func (c *defaultCommand) IsSubprocessRunning() bool {
Expand Down
4 changes: 2 additions & 2 deletions ibazel/command/default_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestDefaultCommand(t *testing.T) {
}

// This is synonymous with killing the job so use it to kill the job and test everything.
c.NotifyOfChanges()
c.NotifyOfChanges(nil)
assertKilled(t, toKill)
}

Expand All @@ -63,7 +63,7 @@ func TestDefaultCommand_Start(t *testing.T) {

b := &mock_bazel.MockBazel{}

cmd := start(b, "//path/to:target", []string{"moo"})
_, cmd := start(b, "//path/to:target", []string{"moo"}, nil)
cmd.Start()

if cmd.Stdout != os.Stdout {
Expand Down
Loading

0 comments on commit e9e3a1b

Please sign in to comment.