Skip to content

Commit bdb5f42

Browse files
committed
Exec bazel instead of subprocessing
1 parent 1f9a1ac commit bdb5f42

File tree

2 files changed

+100
-22
lines changed

2 files changed

+100
-22
lines changed

bazelisk.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func main() {
3030
// Fetch LTS releases & candidates, rolling releases and Bazel-at-commits from GCS, forks from GitHub.
3131
repos := core.CreateRepositories(gcs, gitHub, gcs, gcs, true)
3232

33-
exitCode, err := core.RunBazeliskWithArgsFuncAndConfig(func(string) []string { return os.Args[1:] }, repos, config)
33+
exitCode, err := core.ExecBazeliskWithArgsFuncAndConfig(func(string) []string { return os.Args[1:] }, repos, config)
3434
if err != nil {
3535
log.Fatal(err)
3636
}

core/core.go

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -76,26 +76,75 @@ func MakeDefaultConfig() config.Config {
7676
}
7777

7878
// RunBazelisk runs the main Bazelisk logic for the given arguments and Bazel repositories.
79+
//
80+
// This will run Bazel in a subprocess and return its exit code.
7981
func RunBazelisk(args []string, repos *Repositories) (int, error) {
8082
return RunBazeliskWithArgsFunc(func(_ string) []string { return args }, repos)
8183
}
8284

8385
// RunBazeliskWithArgsFunc runs the main Bazelisk logic for the given ArgsFunc and Bazel
8486
// repositories.
87+
//
88+
// This will run Bazel in a subprocess and return its exit code.
8589
func RunBazeliskWithArgsFunc(argsFunc ArgsFunc, repos *Repositories) (int, error) {
86-
8790
return RunBazeliskWithArgsFuncAndConfig(argsFunc, repos, MakeDefaultConfig())
8891
}
8992

9093
// RunBazeliskWithArgsFuncAndConfig runs the main Bazelisk logic for the given ArgsFunc and Bazel
9194
// repositories and config.
95+
//
96+
// This will run Bazel in a subprocess and return its exit code.
9297
func RunBazeliskWithArgsFuncAndConfig(argsFunc ArgsFunc, repos *Repositories, config config.Config) (int, error) {
9398
return RunBazeliskWithArgsFuncAndConfigAndOut(argsFunc, repos, config, nil)
9499
}
95100

96101
// RunBazeliskWithArgsFuncAndConfigAndOut runs the main Bazelisk logic for the given ArgsFunc and Bazel
97102
// repositories and config, writing its stdout to the passed writer.
103+
//
104+
// This will run Bazel in a subprocess and return its exit code.
98105
func RunBazeliskWithArgsFuncAndConfigAndOut(argsFunc ArgsFunc, repos *Repositories, config config.Config, out io.Writer) (int, error) {
106+
return RunOrExecBazeliskWithArgsFuncAndConfigAndOut(argsFunc, repos, config, out, false)
107+
}
108+
109+
// ExecBazelisk runs the main Bazelisk logic for the given arguments and Bazel repositories.
110+
//
111+
// If possible (i.e. on non-Windows platforms), this will replace the current process with Bazel
112+
// and will not return. On Windows, this will execute Bazel in a new process and return its exit
113+
// code.
114+
func ExecBazelisk(args []string, repos *Repositories) (int, error) {
115+
return ExecBazeliskWithArgsFunc(func(_ string) []string { return args }, repos)
116+
}
117+
118+
// ExecBazeliskWithArgsFunc runs the main Bazelisk logic for the given ArgsFunc and Bazel
119+
// repositories.
120+
//
121+
// If possible (i.e. on non-Windows platforms), this will replace the current process with Bazel
122+
// and will not return. On Windows, this will execute Bazel in a new process and return its exit
123+
// code.
124+
func ExecBazeliskWithArgsFunc(argsFunc ArgsFunc, repos *Repositories) (int, error) {
125+
return ExecBazeliskWithArgsFuncAndConfig(argsFunc, repos, MakeDefaultConfig())
126+
}
127+
128+
// ExecBazeliskWithArgsFuncAndConfig runs the main Bazelisk logic for the given ArgsFunc and Bazel
129+
// repositories and config.
130+
//
131+
// If possible (i.e. on non-Windows platforms), this will replace the current process with Bazel
132+
// and will not return. On Windows, this will execute Bazel in a new process and return its exit
133+
// code.
134+
func ExecBazeliskWithArgsFuncAndConfig(argsFunc ArgsFunc, repos *Repositories, config config.Config) (int, error) {
135+
return RunOrExecBazeliskWithArgsFuncAndConfigAndOut(argsFunc, repos, config, nil, true)
136+
}
137+
138+
// RunOrExecBazeliskWithArgsFuncAndConfigAndOut runs the main Bazelisk logic for the given ArgsFunc and Bazel
139+
// repositories and config.
140+
//
141+
// If exec is true, this will replace the current process with Bazel and will not return (this is
142+
// not possible on Windows; on Windows this will execute Bazel in a new process and return its exit
143+
// code even if exec is true). `out` is not supported in exec mode.
144+
//
145+
// If exec is false, this will run Bazel in a subprocess and return its exit code, writing its stdout
146+
// to the passed writer if provided.
147+
func RunOrExecBazeliskWithArgsFuncAndConfigAndOut(argsFunc ArgsFunc, repos *Repositories, config config.Config, out io.Writer, bool exec) (int, error) {
99148
httputil.UserAgent = getUserAgent(config)
100149

101150
bazelInstallation, err := GetBazelInstallation(repos, config)
@@ -108,8 +157,8 @@ func RunBazeliskWithArgsFuncAndConfigAndOut(argsFunc ArgsFunc, repos *Repositori
108157
// --print_env must be the first argument.
109158
if len(args) > 0 && args[0] == "--print_env" {
110159
// print environment variables for sub-processes
111-
cmd := makeBazelCmd(bazelInstallation.Path, args, nil, config)
112-
for _, val := range cmd.Env {
160+
_, _, env := makeBazelCmd(bazelInstallation.Path, args, config)
161+
for _, val := range env {
113162
fmt.Println(val)
114163
}
115164
return 0, nil
@@ -161,11 +210,22 @@ func RunBazeliskWithArgsFuncAndConfigAndOut(argsFunc ArgsFunc, repos *Repositori
161210
}
162211
}
163212

164-
exitCode, err := runBazel(bazelInstallation.Path, args, out, config)
165-
if err != nil {
166-
return -1, fmt.Errorf("could not run Bazel: %v", err)
213+
if exec {
214+
if out != nil {
215+
return -1, fmt.Errorf("cannot run bazelisk in exec mode with a non-nil output writer")
216+
}
217+
exitCode, err := execBazel(bazelInstallation.Path, args, config)
218+
if err != nil {
219+
return -1, fmt.Errorf("could not run Bazel: %v", err)
220+
}
221+
return exitCode, nil
222+
} else {
223+
exitCode, err := runBazel(bazelInstallation.Path, args, out, config)
224+
if err != nil {
225+
return -1, fmt.Errorf("could not run Bazel: %v", err)
226+
}
227+
return exitCode, nil
167228
}
168-
return exitCode, nil
169229
}
170230

171231
func isVersionCommand(args []string) (result bool, gnuFormat bool) {
@@ -636,50 +696,68 @@ func maybeDelegateToWrapper(bazel string, config config.Config) string {
636696
return maybeDelegateToWrapperFromDir(bazel, wd, config)
637697
}
638698

639-
func prependDirToPathList(cmd *exec.Cmd, dir string) {
699+
func prependDirToPathList(env []string, dir string) {
640700
found := false
641-
for idx, val := range cmd.Env {
701+
for idx, val := range env {
642702
splits := strings.Split(val, "=")
643703
if len(splits) != 2 {
644704
continue
645705
}
646706
if strings.EqualFold(splits[0], "PATH") {
647707
found = true
648-
cmd.Env[idx] = fmt.Sprintf("PATH=%s%s%s", dir, string(os.PathListSeparator), splits[1])
708+
env[idx] = fmt.Sprintf("PATH=%s%s%s", dir, string(os.PathListSeparator), splits[1])
649709
break
650710
}
651711
}
652712

653713
if !found {
654-
cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", dir))
714+
env = append(env, fmt.Sprintf("PATH=%s", dir))
655715
}
656716
}
657717

658-
func makeBazelCmd(bazel string, args []string, out io.Writer, config config.Config) *exec.Cmd {
718+
func makeBazelCmd(bazel string, args []string, config config.Config) (string, []string, []string) {
659719
execPath := maybeDelegateToWrapper(bazel, config)
660720

661-
cmd := exec.Command(execPath, args...)
662-
cmd.Env = append(os.Environ(), skipWrapperEnv+"=true")
721+
env := append(os.Environ(), skipWrapperEnv+"=true")
663722
if execPath != bazel {
664-
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", bazelReal, bazel))
723+
env = append(env, fmt.Sprintf("%s=%s", bazelReal, bazel))
665724
}
666725
selfPath, err := os.Executable()
667726
if err != nil {
668-
cmd.Env = append(cmd.Env, bazeliskEnv+"="+selfPath)
727+
env = append(env, bazeliskEnv+"="+selfPath)
669728
}
670-
prependDirToPathList(cmd, filepath.Dir(execPath))
729+
prependDirToPathList(env, filepath.Dir(execPath))
730+
731+
commandLine := []string{execPath}
732+
commandLine = append(commandLine, args...)
733+
return execPath, commandLine, env
734+
}
735+
736+
func execBazel(bazel string, args []string, config config.Config) (int, error) {
737+
if runtime.GOOS == "windows" {
738+
// syscall.Exec is not supported on windows
739+
return runBazel(bazel, args, nil, config)
740+
}
741+
742+
execPath, args, env := makeBazelCmd(bazel, args, config)
743+
744+
err := syscall.Exec(execPath, args, env)
745+
return 1, fmt.Errorf("could not start Bazel: %v", err)
746+
}
747+
748+
func runBazel(bazel string, args []string, out io.Writer, config config.Config) (int, error) {
749+
execPath, commandLine, env := makeBazelCmd(bazel, args, config)
750+
751+
cmd := exec.Command(execPath, commandLine[1:]...)
752+
cmd.Env = env
671753
cmd.Stdin = os.Stdin
672754
if out == nil {
673755
cmd.Stdout = os.Stdout
674756
} else {
675757
cmd.Stdout = out
676758
}
677759
cmd.Stderr = os.Stderr
678-
return cmd
679-
}
680760

681-
func runBazel(bazel string, args []string, out io.Writer, config config.Config) (int, error) {
682-
cmd := makeBazelCmd(bazel, args, out, config)
683761
err := cmd.Start()
684762
if err != nil {
685763
return 1, fmt.Errorf("could not start Bazel: %v", err)

0 commit comments

Comments
 (0)