github.com/bazelbuild/bazel-watcher@v0.25.2/internal/ibazel/command/command.go (about) 1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package command 16 17 import ( 18 "bytes" 19 "flag" 20 "fmt" 21 "io/ioutil" 22 "os" 23 "os/exec" 24 "runtime" 25 "strings" 26 "syscall" 27 "time" 28 29 "github.com/bazelbuild/bazel-watcher/internal/bazel" 30 "github.com/bazelbuild/bazel-watcher/internal/ibazel/log" 31 "github.com/bazelbuild/bazel-watcher/internal/ibazel/process_group" 32 ) 33 34 var ( 35 execCommand = process_group.Command 36 bazelNew = bazel.New 37 waitDuration = flag.Duration( 38 "graceful_termination_wait_duration", 39 10*time.Second, 40 "Specify the duration to wait for a graceful termination before sending SIGKILL to the subprocess") 41 ) 42 43 // Command is an object that wraps the logic of running a task in Bazel and 44 // manipulating it. 45 type Command interface { 46 Start() (*bytes.Buffer, error) 47 Terminate() 48 Kill() 49 NotifyOfChanges() *bytes.Buffer 50 IsSubprocessRunning() bool 51 } 52 53 // start will be called by most implementations since this logic is extremely 54 // common. 55 func start(b bazel.Bazel, target string, args []string) (*bytes.Buffer, process_group.ProcessGroup) { 56 var filePattern strings.Builder 57 filePattern.WriteString("bazel_script_path*") 58 if runtime.GOOS == "windows" { 59 filePattern.WriteString(".bat") 60 } 61 62 tmpfile, err := ioutil.TempFile("", filePattern.String()) 63 if err != nil { 64 fmt.Print(err) 65 } 66 // Close the file so bazel can write over it 67 if err := tmpfile.Close(); err != nil { 68 fmt.Print(err) 69 } 70 71 // Start by building the binary 72 _, outputBuffer, _ := b.Run("--script_path="+tmpfile.Name(), target) 73 74 runScriptPath := tmpfile.Name() 75 76 // Now that we have built the target, construct a executable form of it for 77 // execution in a go routine. 78 cmd := execCommand(runScriptPath, args...) 79 cmd.RootProcess().Stdout = os.Stdout 80 cmd.RootProcess().Stderr = os.Stderr 81 82 return outputBuffer, cmd 83 } 84 85 func subprocessRunning(cmd *exec.Cmd) bool { 86 if cmd == nil { 87 return false 88 } 89 if cmd.Process == nil { 90 return false 91 } 92 if cmd.ProcessState != nil { 93 if cmd.ProcessState.Exited() { 94 return false 95 } 96 } 97 98 return true 99 } 100 101 func terminate(pg process_group.ProcessGroup) { 102 pg.Signal(syscall.SIGTERM) 103 done := make(chan bool, 1) 104 go func() { 105 select { 106 case <-time.After(*waitDuration): 107 log.Logf("The subprocess wasn't terminated within %s. Forcing to close.", *waitDuration) 108 kill(pg) 109 case <-done: 110 // The subprocess was terminated with SIGTERM 111 } 112 }() 113 pg.Wait() 114 done <- true 115 pg.Close() 116 } 117 118 func kill(pg process_group.ProcessGroup) { 119 if subprocessRunning(pg.RootProcess()) { 120 log.Logf("Sending SIGKILL to the subprocess") 121 pg.Signal(syscall.SIGKILL) 122 } 123 }