Skip to content

Commit

Permalink
Add Windows support (#144)
Browse files Browse the repository at this point in the history
Currently the e2e tests are tagged as `nowindows`. We will create
release binaries for windows once those are passing.
  • Loading branch information
jchv authored and achew22 committed Jan 18, 2019
1 parent b0b0fa4 commit da33b5a
Show file tree
Hide file tree
Showing 24 changed files with 529 additions and 92 deletions.
19 changes: 19 additions & 0 deletions .bazelci/presubmit.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
---
platforms:
ubuntu1404:
build_flags:
- "--build_tag_filters=-nolinux"
build_targets:
- "..."
test_flags:
- "--features=race"
- "--test_tag_filters=-nolinux"
test_targets:
- "..."
ubuntu1604:
build_flags:
- "--build_tag_filters=-nolinux"
build_targets:
- "..."
test_flags:
- "--features=race"
- "--test_tag_filters=-nolinux"
test_targets:
- "..."
macos:
build_flags:
- "--build_tag_filters=-nomacos"
build_targets:
- "..."
test_flags:
- "--features=race"
- "--test_tag_filters=-nomacos"
test_targets:
- "..."
windows:
build_flags:
- "--build_tag_filters=-nowindows"
build_targets:
- "..."
test_flags:
- "--test_tag_filters=-nowindows"
- "--experimental_enable_runfiles"
test_targets:
- "..."
13 changes: 7 additions & 6 deletions e2e/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ func GetPath(p string) string {
return path
}

var ibazelPath string
var ibazelPath = getiBazelPath()

func init() {
var err error
ibazelPath = GetPath(fmt.Sprintf("ibazel/%s_%s_pure_stripped/ibazel", runtime.GOOS, runtime.GOARCH))
if err != nil {
panic(err)
func getiBazelPath() string {
suffix := ""
// Windows expects executables to end in .exe
if runtime.GOOS == "windows" {
suffix = ".exe"
}
return GetPath(fmt.Sprintf("ibazel/%s_%s_pure_stripped/ibazel%s", runtime.GOOS, runtime.GOARCH, suffix))
}
5 changes: 1 addition & 4 deletions e2e/ibazel.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,7 @@ func (i *IBazelTester) build(target string, additionalArgs []string) {
}

func (i *IBazelTester) run(target string, additionalArgs []string) {
args := []string{
"--bazel_path=" + i.bazelPath(),
"--log_to_file=/tmp/ibazel_output.log",
}
args := []string{"--bazel_path=" + i.bazelPath()}
args = append(args, additionalArgs...)
args = append(args, "run")
args = append(args, target)
Expand Down
1 change: 1 addition & 0 deletions e2e/live_reload/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ bazel_go_integration_test(
"//ibazel",
],
importpath = "github.com/bazelbuild/bazel-watcher/e2e/live_reload",
tags = ["nowindows"],
versions = GET_LATEST_BAZEL_VERSIONS(),
deps = [
"//e2e:go_default_library",
Expand Down
1 change: 1 addition & 0 deletions e2e/output_runner/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ bazel_go_integration_test(
"//ibazel",
],
importpath = "github.com/bazelbuild/bazel-watcher/e2e/output_runner",
tags = ["nowindows"],
versions = GET_LATEST_BAZEL_VERSIONS(),
deps = [
"//e2e:go_default_library",
Expand Down
1 change: 1 addition & 0 deletions e2e/profiler/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ bazel_go_integration_test(
"//ibazel",
],
importpath = "github.com/bazelbuild/bazel-watcher/e2e/profiler",
tags = ["nowindows"],
versions = GET_LATEST_BAZEL_VERSIONS(),
deps = [
"//e2e:go_default_library",
Expand Down
1 change: 1 addition & 0 deletions e2e/simple/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ bazel_go_integration_test(
"//ibazel",
],
importpath = "github.com/bazelbuild/bazel-watcher/e2e/simple",
tags = ["nowindows"],
versions = GET_LATEST_BAZEL_VERSIONS(),
deps = [
"//e2e:go_default_library",
Expand Down
2 changes: 2 additions & 0 deletions ibazel/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ go_library(
"ibazel.go",
"lifecycle.go",
"main.go",
"main_unix.go",
"main_windows.go",
"source_event_handler.go",
],
importpath = "github.com/bazelbuild/bazel-watcher/ibazel",
Expand Down
6 changes: 5 additions & 1 deletion ibazel/command/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ go_library(
],
importpath = "github.com/bazelbuild/bazel-watcher/ibazel/command",
visibility = ["//ibazel:__subpackages__"],
deps = ["//bazel:go_default_library"],
deps = [
"//bazel:go_default_library",
"//ibazel/process_group:go_default_library",
],
)

go_test(
Expand All @@ -39,5 +42,6 @@ go_test(
deps = [
"//bazel:go_default_library",
"//bazel/testing:go_default_library",
"//ibazel/process_group:go_default_library",
],
)
15 changes: 6 additions & 9 deletions ibazel/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import (
"os/exec"
"runtime"
"strings"
"syscall"

"github.com/bazelbuild/bazel-watcher/bazel"
"github.com/bazelbuild/bazel-watcher/ibazel/process_group"
)

var execCommand = exec.Command
var execCommand = process_group.Command
var bazelNew = bazel.New

// Command is an object that wraps the logic of running a task in Bazel and
Expand All @@ -41,9 +41,9 @@ type Command interface {

// start will be called by most implementations since this logic is extremely
// common.
func start(b bazel.Bazel, target string, args []string) (*bytes.Buffer, *exec.Cmd) {
func start(b bazel.Bazel, target string, args []string) (*bytes.Buffer, process_group.ProcessGroup) {
var filePattern strings.Builder
filePattern.WriteString("bazel_script_path")
filePattern.WriteString("bazel_script_path*")
if runtime.GOOS == "windows" {
filePattern.WriteString(".bat")
}
Expand All @@ -65,11 +65,8 @@ func start(b bazel.Bazel, target string, args []string) (*bytes.Buffer, *exec.Cm
// Now that we have built the target, construct a executable form of it for
// execution in a go routine.
cmd := execCommand(runScriptPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

// Set a process group id (PGID) on the subprocess. This is
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.RootProcess().Stdout = os.Stdout
cmd.RootProcess().Stderr = os.Stderr

return outputBuffer, cmd
}
Expand Down
3 changes: 2 additions & 1 deletion ibazel/command/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"testing"

"github.com/bazelbuild/bazel-watcher/bazel"
"github.com/bazelbuild/bazel-watcher/ibazel/process_group"
)

var oldExecCommand = execCommand
Expand All @@ -36,7 +37,7 @@ func assertKilled(t *testing.T, cmd *exec.Cmd) {
}

func TestSubprocessRunning(t *testing.T) {
execCommand = func(name string, args ...string) *exec.Cmd {
execCommand = func(name string, args ...string) process_group.ProcessGroup {
return oldExecCommand("ls") // Every system has ls.
}
defer func() { execCommand = oldExecCommand }()
Expand Down
23 changes: 12 additions & 11 deletions ibazel/command/default_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ import (
"bytes"
"fmt"
"os"
"os/exec"
"syscall"

"github.com/bazelbuild/bazel-watcher/ibazel/process_group"
)

type defaultCommand struct {
target string
bazelArgs []string
args []string
cmd *exec.Cmd
pg process_group.ProcessGroup
}

// DefaultCommand is the normal mode of interacting with iBazel. If you start a
Expand All @@ -41,7 +41,7 @@ func DefaultCommand(bazelArgs []string, target string, args []string) Command {
}

func (c *defaultCommand) Terminate() {
if !subprocessRunning(c.cmd) {
if c.pg != nil && !subprocessRunning(c.pg.RootProcess()) {
return
}

Expand All @@ -50,9 +50,10 @@ func (c *defaultCommand) Terminate() {
// send to the PGID, send the signal to the negative of the process PID.
// Normally I would do this by calling c.cmd.Process.Signal, but that
// only goes to the PID not the PGID.
syscall.Kill(-c.cmd.Process.Pid, syscall.SIGKILL)
c.cmd.Wait()
c.cmd = nil
c.pg.Kill()
c.pg.Wait()
c.pg.Close()
c.pg = nil
}

func (c *defaultCommand) Start() (*bytes.Buffer, error) {
Expand All @@ -63,12 +64,12 @@ func (c *defaultCommand) Start() (*bytes.Buffer, error) {
b.WriteToStdout(true)

var outputBuffer *bytes.Buffer
outputBuffer, c.cmd = start(b, c.target, c.args)
outputBuffer, c.pg = start(b, c.target, c.args)

c.cmd.Env = os.Environ()
c.pg.RootProcess().Env = os.Environ()

var err error
if err = c.cmd.Start(); err != nil {
if err = c.pg.Start(); err != nil {
fmt.Fprintf(os.Stderr, "Error starting process: %v\n", err)
return outputBuffer, err
}
Expand All @@ -83,5 +84,5 @@ func (c *defaultCommand) NotifyOfChanges() *bytes.Buffer {
}

func (c *defaultCommand) IsSubprocessRunning() bool {
return subprocessRunning(c.cmd)
return c.pg != nil && subprocessRunning(c.pg.RootProcess())
}
45 changes: 28 additions & 17 deletions ibazel/command/default_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,65 +16,76 @@ package command

import (
"os"
"os/exec"
"syscall"
"runtime"
"testing"

mock_bazel "github.com/bazelbuild/bazel-watcher/bazel/testing"
"github.com/bazelbuild/bazel-watcher/ibazel/process_group"
)

func TestDefaultCommand(t *testing.T) {
toKill := exec.Command("sleep", "10s")
toKill.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
var toKill process_group.ProcessGroup

execCommand = func(name string, args ...string) *exec.Cmd {
if runtime.GOOS == "windows" {
// TODO(jchw): Remove hardcoded path.
toKill = process_group.Command("C:\\windows\\system32\\notepad")
} else {
toKill = process_group.Command("sleep", "10s")
}

execCommand = func(name string, args ...string) process_group.ProcessGroup {
if runtime.GOOS == "windows" {
// TODO(jchw): Remove hardcoded path.
return oldExecCommand("C:\\windows\\system32\\where")
}
return oldExecCommand("ls") // Every system has ls.
}
defer func() { execCommand = oldExecCommand }()

c := &defaultCommand{
args: []string{"moo"},
bazelArgs: []string{},
cmd: toKill,
pg: toKill,
target: "//path/to:target",
}

if c.IsSubprocessRunning() {
t.Errorf("New subprocess shouldn't have been started yet. State: %v", toKill.ProcessState)
t.Errorf("New subprocess shouldn't have been started yet. State: %v", toKill.RootProcess().ProcessState)
}

toKill.Start()

if !c.IsSubprocessRunning() {
t.Errorf("New subprocess was never started. State: %v", toKill.ProcessState)
t.Errorf("New subprocess was never started. State: %v", toKill.RootProcess().ProcessState)
}

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

func TestDefaultCommand_Start(t *testing.T) {
// Set up mock execCommand and prep it to be returned
execCommand = func(name string, args ...string) *exec.Cmd {
execCommand = func(name string, args ...string) process_group.ProcessGroup {
if runtime.GOOS == "windows" {
// TODO(jchw): Remove hardcoded path.
return oldExecCommand("C:\\windows\\system32\\where")
}
return oldExecCommand("ls") // Every system has ls.
}
defer func() { execCommand = oldExecCommand }()

b := &mock_bazel.MockBazel{}

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

if cmd.Stdout != os.Stdout {
if pg.RootProcess().Stdout != os.Stdout {
t.Errorf("Didn't set Stdout correctly")
}
if cmd.Stderr != os.Stderr {
if pg.RootProcess().Stderr != os.Stderr {
t.Errorf("Didn't set Stderr correctly")
}
if cmd.SysProcAttr.Setpgid != true {
t.Errorf("Never set PGID (will prevent killing process trees -- see notes in ibazel.go")
}

b.AssertActions(t, [][]string{
[]string{"Run", "--script_path=.*", "//path/to:target"},
Expand Down
Loading

0 comments on commit da33b5a

Please sign in to comment.