Skip to content

Commit 166e156

Browse files
authored
Report a proper exit code when Bazel is terminated by signal and use exec on Unix in the case of bazelisk run (#757)
* Better handle Bazel process termination caused by signals on POSIX-compatible systems * Use `exec` for running bazel on Unix instead of starting it as a child process in the case of `run` command Fixes #512 and #556
1 parent b1bce1b commit 166e156

File tree

1 file changed

+45
-11
lines changed

1 file changed

+45
-11
lines changed

core/core.go

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func RunBazeliskWithArgsFuncAndConfigAndOut(argsFunc ArgsFunc, repos *Repositori
180180
return 0, nil
181181
}
182182

183-
exitCode, err := runBazel(bazelInstallation.Path, args, out, config)
183+
exitCode, err := runBazel(bazelInstallation.Path, args, out, config, isRunCommand(args))
184184
if err != nil {
185185
return -1, fmt.Errorf("could not run Bazel: %v", err)
186186
}
@@ -727,11 +727,35 @@ func makeBazelCmd(bazel string, args []string, out io.Writer, config config.Conf
727727
return cmd, nil
728728
}
729729

730-
func runBazel(bazel string, args []string, out io.Writer, config config.Config) (int, error) {
730+
func isRunCommand(args []string) bool {
731+
for _, arg := range args {
732+
if arg == "--" {
733+
return false
734+
}
735+
if arg == "run" {
736+
return true
737+
}
738+
}
739+
return false
740+
}
741+
742+
func runBazel(bazel string, args []string, out io.Writer, config config.Config, useExecOnUnix bool) (int, error) {
731743
cmd, makeCmdErr := makeBazelCmd(bazel, args, out, config)
732744
if makeCmdErr != nil {
733745
return 1, makeCmdErr
734746
}
747+
if useExecOnUnix && runtime.GOOS != "windows" {
748+
execPath := cmd.Path
749+
execArgs := cmd.Args
750+
execEnv := cmd.Env
751+
err := syscall.Exec(execPath, execArgs, execEnv)
752+
if err != nil {
753+
return 1, fmt.Errorf("could not exec Bazel: %v", err)
754+
}
755+
// This code is unreachable if exec succeeds
756+
return 0, nil
757+
}
758+
735759
err := cmd.Start()
736760
if err != nil {
737761
return 1, fmt.Errorf("could not start Bazel: %v", err)
@@ -750,7 +774,6 @@ func runBazel(bazel string, args []string, out io.Writer, config config.Config)
750774
// by the terminal. As a side effect, we also suppress the printing of a
751775
// Go stack trace upon receiving SIGQUIT, which is unhelpful as users tend
752776
// to report it instead of the far more valuable Java thread dump.
753-
// TODO(#512): We may want to treat a `bazel run` command differently.
754777
// Since signal handlers are process-wide global state and bazelisk may be
755778
// used as a library, reset the signal handlers after the process exits.
756779
sigCh := make(chan os.Signal, 1)
@@ -761,7 +784,18 @@ func runBazel(bazel string, args []string, out io.Writer, config config.Config)
761784
if err != nil {
762785
if exitError, ok := err.(*exec.ExitError); ok {
763786
waitStatus := exitError.Sys().(syscall.WaitStatus)
764-
return waitStatus.ExitStatus(), nil
787+
// it's only correct to use waitStatus.ExitStatus when the process terminated normally, i.e. waitStatus.Exited() == true
788+
if waitStatus.Exited() {
789+
return waitStatus.ExitStatus(), nil
790+
}
791+
// if the process was terminated by a signal on a POSIX-compatible system, let's report its exit code in the same way
792+
// as shells do - as 128 + signal number.
793+
// It's not a perfect solution, because information that a signal has terminated the process is lost,
794+
// but at least we propagate a proper exit code.
795+
if runtime.GOOS != "windows" && waitStatus.Signaled() {
796+
return 128 + int(waitStatus.Signal()), nil
797+
}
798+
return 1, fmt.Errorf("unexpected wait status of Bazel: %v", waitStatus)
765799
}
766800
return 1, fmt.Errorf("could not launch Bazel: %v", err)
767801
}
@@ -776,7 +810,7 @@ func getIncompatibleFlags(bazelPath, cmd string, config config.Config) ([]string
776810
}
777811

778812
out := strings.Builder{}
779-
if _, err := runBazel(bazelPath, []string{"help", cmd, "--short"}, &out, config); err != nil {
813+
if _, err := runBazel(bazelPath, []string{"help", cmd, "--short"}, &out, config, false); err != nil {
780814
return nil, fmt.Errorf("unable to determine incompatible flags with binary %s: %v", bazelPath, err)
781815
}
782816

@@ -852,7 +886,7 @@ func shutdownIfNeeded(bazelPath string, startupOptions []string, config config.C
852886

853887
args := append(startupOptions, "shutdown")
854888
fmt.Printf("bazel %s\n", strings.Join(args, " "))
855-
exitCode, err := runBazel(bazelPath, args, nil, config)
889+
exitCode, err := runBazel(bazelPath, args, nil, config, false)
856890
fmt.Printf("\n")
857891
if err != nil {
858892
log.Fatalf("failed to run bazel shutdown: %v", err)
@@ -871,7 +905,7 @@ func cleanIfNeeded(bazelPath string, startupOptions []string, config config.Conf
871905

872906
args := append(startupOptions, "clean", "--expunge")
873907
fmt.Printf("bazel %s\n", strings.Join(args, " "))
874-
exitCode, err := runBazel(bazelPath, args, nil, config)
908+
exitCode, err := runBazel(bazelPath, args, nil, config, false)
875909
fmt.Printf("\n")
876910
if err != nil {
877911
log.Fatalf("failed to run clean: %v", err)
@@ -1070,7 +1104,7 @@ func testWithBazelAtCommit(bazelCommit string, args []string, bazeliskHome strin
10701104
shutdownIfNeeded(bazelPath, startupOptions, config)
10711105
cleanIfNeeded(bazelPath, startupOptions, config)
10721106
fmt.Printf("bazel %s\n", strings.Join(args, " "))
1073-
bazelExitCode, err := runBazel(bazelPath, args, nil, config)
1107+
bazelExitCode, err := runBazel(bazelPath, args, nil, config, false)
10741108
if err != nil {
10751109
return -1, fmt.Errorf("could not run Bazel: %v", err)
10761110
}
@@ -1087,7 +1121,7 @@ func migrate(bazelPath string, baseArgs []string, flags []string, config config.
10871121
shutdownIfNeeded(bazelPath, startupOptions, config)
10881122
cleanIfNeeded(bazelPath, startupOptions, config)
10891123
fmt.Printf("bazel %s\n", strings.Join(args, " "))
1090-
exitCode, err := runBazel(bazelPath, args, nil, config)
1124+
exitCode, err := runBazel(bazelPath, args, nil, config, false)
10911125
if err != nil {
10921126
log.Fatalf("could not run Bazel: %v", err)
10931127
}
@@ -1102,7 +1136,7 @@ func migrate(bazelPath string, baseArgs []string, flags []string, config config.
11021136
shutdownIfNeeded(bazelPath, startupOptions, config)
11031137
cleanIfNeeded(bazelPath, startupOptions, config)
11041138
fmt.Printf("bazel %s\n", strings.Join(args, " "))
1105-
exitCode, err = runBazel(bazelPath, args, nil, config)
1139+
exitCode, err = runBazel(bazelPath, args, nil, config, false)
11061140
if err != nil {
11071141
log.Fatalf("could not run Bazel: %v", err)
11081142
}
@@ -1120,7 +1154,7 @@ func migrate(bazelPath string, baseArgs []string, flags []string, config config.
11201154
shutdownIfNeeded(bazelPath, startupOptions, config)
11211155
cleanIfNeeded(bazelPath, startupOptions, config)
11221156
fmt.Printf("bazel %s\n", strings.Join(args, " "))
1123-
exitCode, err = runBazel(bazelPath, args, nil, config)
1157+
exitCode, err = runBazel(bazelPath, args, nil, config, false)
11241158
if err != nil {
11251159
log.Fatalf("could not run Bazel: %v", err)
11261160
}

0 commit comments

Comments
 (0)