diff --git a/cmd/ibazel/main.go b/cmd/ibazel/main.go index fca8b885..fc3d01e6 100644 --- a/cmd/ibazel/main.go +++ b/cmd/ibazel/main.go @@ -88,6 +88,7 @@ var overrideableBazelFlags []string = []string{ var debounceDuration = flag.Duration("debounce", 100*time.Millisecond, "Debounce duration") var logToFile = flag.String("log_to_file", "-", "Log iBazel stderr to a file instead of os.Stderr") +var setPGID = flag.Bool("set_pgid", true, "Whether to set the process group ID") func usage() { fmt.Fprintf(os.Stderr, `iBazel - Version %s @@ -192,6 +193,7 @@ func main() { log.Fatalf("Error creating iBazel: %s", err) } i.SetDebounceDuration(*debounceDuration) + i.SetSetPGID(*setPGID) defer i.Cleanup() // increase the number of files that this process can @@ -210,7 +212,7 @@ func applyDefaultBazelArgs(bazelArgs []string) []string { return bazelArgs } } - if (isTerminal()) { + if isTerminal() { return append(bazelArgs, "--isatty=1") } else { return append(bazelArgs, "--isatty=0") diff --git a/go.mod b/go.mod index 752969b5..cc745413 100644 --- a/go.mod +++ b/go.mod @@ -13,9 +13,8 @@ require ( gopkg.in/fsnotify.v1 v1.4.7 // indirect ) -require ( - golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect - google.golang.org/protobuf v1.28.0 // indirect -) +require golang.org/x/sys v0.0.0-20220908164124-27713097b956 + +require google.golang.org/protobuf v1.28.0 // indirect go 1.19 diff --git a/go.sum b/go.sum index 393517b0..ef366525 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,7 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -58,6 +59,7 @@ github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hg github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -66,9 +68,12 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= diff --git a/internal/ibazel/command/command.go b/internal/ibazel/command/command.go index 4fb27685..b5d18097 100644 --- a/internal/ibazel/command/command.go +++ b/internal/ibazel/command/command.go @@ -52,7 +52,7 @@ 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, process_group.ProcessGroup) { +func start(b bazel.Bazel, target string, setPGID bool, args []string) (*bytes.Buffer, process_group.ProcessGroup) { var filePattern strings.Builder filePattern.WriteString("bazel_script_path*") if runtime.GOOS == "windows" { @@ -75,7 +75,7 @@ func start(b bazel.Bazel, target string, args []string) (*bytes.Buffer, process_ // Now that we have built the target, construct a executable form of it for // execution in a go routine. - cmd := execCommand(runScriptPath, args...) + cmd := execCommand(runScriptPath, setPGID, args...) cmd.RootProcess().Stdout = os.Stdout cmd.RootProcess().Stderr = os.Stderr diff --git a/internal/ibazel/command/command_test.go b/internal/ibazel/command/command_test.go index e4778eff..bd690298 100644 --- a/internal/ibazel/command/command_test.go +++ b/internal/ibazel/command/command_test.go @@ -41,8 +41,8 @@ func assertKilled(t *testing.T, cmd *exec.Cmd) { func TestSubprocessRunning(t *testing.T) { log.SetLogger(t) - execCommand = func(name string, args ...string) process_group.ProcessGroup { - return oldExecCommand("ls") // Every system has ls. + execCommand = func(name string, setPGID bool, args ...string) process_group.ProcessGroup { + return oldExecCommand("ls", setPGID) // Every system has ls. } defer func() { execCommand = oldExecCommand }() diff --git a/internal/ibazel/command/default_command.go b/internal/ibazel/command/default_command.go index 5ae598c1..42c7b95a 100644 --- a/internal/ibazel/command/default_command.go +++ b/internal/ibazel/command/default_command.go @@ -30,17 +30,19 @@ type defaultCommand struct { args []string pg process_group.ProcessGroup termSync sync.Once + setPGID bool } // DefaultCommand is the normal mode of interacting with iBazel. If you start a // server in this mode and notify of changes the server will be killed and // restarted. -func DefaultCommand(startupArgs []string, bazelArgs []string, target string, args []string) Command { +func DefaultCommand(startupArgs []string, bazelArgs []string, target string, setPGID bool, args []string) Command { return &defaultCommand{ target: target, startupArgs: startupArgs, bazelArgs: bazelArgs, args: args, + setPGID: setPGID, } } @@ -70,7 +72,7 @@ func (c *defaultCommand) Start() (*bytes.Buffer, error) { b.WriteToStdout(true) var outputBuffer *bytes.Buffer - outputBuffer, c.pg = start(b, c.target, c.args) + outputBuffer, c.pg = start(b, c.target, c.setPGID, c.args) c.pg.RootProcess().Env = os.Environ() diff --git a/internal/ibazel/command/default_command_test.go b/internal/ibazel/command/default_command_test.go index d1c2a90e..f0686b55 100644 --- a/internal/ibazel/command/default_command_test.go +++ b/internal/ibazel/command/default_command_test.go @@ -31,17 +31,17 @@ func TestDefaultCommand(t *testing.T) { if runtime.GOOS == "windows" { // TODO(jchw): Remove hardcoded path. - toKill = process_group.Command("C:\\windows\\system32\\notepad") + toKill = process_group.Command("C:\\windows\\system32\\notepad", true) } else { - toKill = process_group.Command("sleep", "10s") + toKill = process_group.Command("sleep", true, "10s") } - execCommand = func(name string, args ...string) process_group.ProcessGroup { + execCommand = func(name string, setPGID bool, args ...string) process_group.ProcessGroup { if runtime.GOOS == "windows" { // TODO(jchw): Remove hardcoded path. - return oldExecCommand("C:\\windows\\system32\\where") + return oldExecCommand("C:\\windows\\system32\\where", setPGID, args...) } - return oldExecCommand("ls") // Every system has ls. + return oldExecCommand("ls", setPGID) // Every system has ls. } defer func() { execCommand = oldExecCommand }() @@ -71,18 +71,18 @@ func TestDefaultCommand_Start(t *testing.T) { log.SetLogger(t) // Set up mock execCommand and prep it to be returned - execCommand = func(name string, args ...string) process_group.ProcessGroup { + execCommand = func(name string, setPGID bool, args ...string) process_group.ProcessGroup { if runtime.GOOS == "windows" { // TODO(jchw): Remove hardcoded path. - return oldExecCommand("C:\\windows\\system32\\where") + return oldExecCommand("C:\\windows\\system32\\where", setPGID, args...) } - return oldExecCommand("ls") // Every system has ls. + return oldExecCommand("ls", setPGID, args...) // Every system has ls. } defer func() { execCommand = oldExecCommand }() b := &mock_bazel.MockBazel{} - _, pg := start(b, "//path/to:target", []string{"moo"}) + _, pg := start(b, "//path/to:target", true, []string{"moo"}) pg.Start() if pg.RootProcess().Stdout != os.Stdout { diff --git a/internal/ibazel/command/notify_command.go b/internal/ibazel/command/notify_command.go index 880ec154..d4fbe633 100644 --- a/internal/ibazel/command/notify_command.go +++ b/internal/ibazel/command/notify_command.go @@ -32,16 +32,18 @@ type notifyCommand struct { pg process_group.ProcessGroup stdin io.WriteCloser termSync sync.Once + setPGID bool } // NotifyCommand is an alternate mode for starting a command. In this mode the // command will be notified on stdin that the source files have changed. -func NotifyCommand(startupArgs []string, bazelArgs []string, target string, args []string) Command { +func NotifyCommand(startupArgs []string, bazelArgs []string, target string, setPGID bool, args []string) Command { return ¬ifyCommand{ startupArgs: startupArgs, target: target, bazelArgs: bazelArgs, args: args, + setPGID: setPGID, } } @@ -71,7 +73,7 @@ func (c *notifyCommand) Start() (*bytes.Buffer, error) { b.WriteToStdout(true) var outputBuffer *bytes.Buffer - outputBuffer, c.pg = start(b, c.target, c.args) + outputBuffer, c.pg = start(b, c.target, c.setPGID, c.args) // Keep the writer around. var err error c.stdin, err = c.pg.RootProcess().StdinPipe() @@ -126,6 +128,7 @@ func (c *notifyCommand) NotifyOfChanges() *bytes.Buffer { if !c.IsSubprocessRunning() { log.Log("Restarting process...") c.Terminate() + // TODO(dmiller): Report the error to the user. c.Start() } } diff --git a/internal/ibazel/command/notify_command_test.go b/internal/ibazel/command/notify_command_test.go index 413d5364..d6cc27a4 100644 --- a/internal/ibazel/command/notify_command_test.go +++ b/internal/ibazel/command/notify_command_test.go @@ -27,7 +27,7 @@ import ( func TestNotifyCommand(t *testing.T) { log.SetLogger(t) - pg := process_group.Command("cat") + pg := process_group.Command("cat", true) c := ¬ifyCommand{ args: []string{"moo"}, @@ -92,9 +92,9 @@ func TestNotifyCommand_Restart(t *testing.T) { var pg process_group.ProcessGroup - pg = process_group.Command("ls") - execCommand = func(name string, args ...string) process_group.ProcessGroup { - return oldExecCommand("ls") + pg = process_group.Command("ls", true) + execCommand = func(name string, setPGID bool, args ...string) process_group.ProcessGroup { + return oldExecCommand("ls", setPGID, args...) } defer func() { execCommand = oldExecCommand }() diff --git a/internal/ibazel/ibazel.go b/internal/ibazel/ibazel.go index 0b9f00fd..84296a3d 100644 --- a/internal/ibazel/ibazel.go +++ b/internal/ibazel/ibazel.go @@ -70,6 +70,7 @@ type IBazel struct { args []string bazelArgs []string startupArgs []string + setPGID bool sigs chan os.Signal // Signals channel for the current process interruptCount int @@ -185,6 +186,10 @@ func (i *IBazel) SetDebounceDuration(debounceDuration time.Duration) { i.debounceDuration = debounceDuration } +func (i *IBazel) SetSetPGID(setPGID bool) { + i.setPGID = setPGID +} + func (i *IBazel) Cleanup() { i.buildFileWatcher.Close() i.sourceFileWatcher.Close() @@ -423,9 +428,9 @@ func (i *IBazel) setupRun(target string) command.Command { if commandNotify { log.Logf("Launching with notifications") - return commandNotifyCommand(i.startupArgs, i.bazelArgs, target, i.args) + return commandNotifyCommand(i.startupArgs, i.bazelArgs, target, i.setPGID, i.args) } else { - return commandDefaultCommand(i.startupArgs, i.bazelArgs, target, i.args) + return commandDefaultCommand(i.startupArgs, i.bazelArgs, target, i.setPGID, i.args) } } diff --git a/internal/ibazel/ibazel_test.go b/internal/ibazel/ibazel_test.go index 163c5d96..528469df 100644 --- a/internal/ibazel/ibazel_test.go +++ b/internal/ibazel/ibazel_test.go @@ -145,7 +145,7 @@ func getMockCommand(i *IBazel) *mockCommand { } func init() { - commandDefaultCommand = func(startupArgs []string, bazelArgs []string, target string, args []string) command.Command { + commandDefaultCommand = func(startupArgs []string, bazelArgs []string, target string, setPGID bool, args []string) command.Command { // Don't do anything return &mockCommand{ startupArgs: startupArgs, @@ -347,7 +347,7 @@ func TestIBazelTest(t *testing.T) { func TestIBazelRun_notifyPreexistiingJobWhenStarting(t *testing.T) { log.SetTesting(t) - commandDefaultCommand = func(startupArgs []string, bazelArgs []string, target string, args []string) command.Command { + commandDefaultCommand = func(startupArgs []string, bazelArgs []string, target string, setPGID bool, args []string) command.Command { assertEqual(t, startupArgs, []string{}, "Startup args") assertEqual(t, bazelArgs, []string{}, "Bazel args") assertEqual(t, target, "", "Target") diff --git a/internal/ibazel/process_group/process_group_unix.go b/internal/ibazel/process_group/process_group_unix.go index 501c4b73..d06175e2 100644 --- a/internal/ibazel/process_group/process_group_unix.go +++ b/internal/ibazel/process_group/process_group_unix.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !windows // +build !windows package process_group @@ -27,9 +28,9 @@ type unixProcessGroup struct { // Command creates a new ProcessGroup with a root command specified by the // arguments. -func Command(name string, arg ...string) ProcessGroup { +func Command(name string, setPGID bool, arg ...string) ProcessGroup { root := exec.Command(name, arg...) - root.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + root.SysProcAttr = &syscall.SysProcAttr{Setpgid: setPGID} return &unixProcessGroup{root} }