Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bazelisk.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func main() {
// Fetch LTS releases & candidates, rolling releases and Bazel-at-commits from GCS, forks from GitHub.
repos := core.CreateRepositories(gcs, gitHub, gcs, gcs, true)

exitCode, err := core.RunBazeliskWithArgsFuncAndConfig(func(string) []string { return os.Args[1:] }, repos, config)
exitCode, err := core.ExecBazeliskWithArgsFuncAndConfig(func(string) []string { return os.Args[1:] }, repos, config)
if err != nil {
log.Fatal(err)
}
Expand Down
120 changes: 99 additions & 21 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,26 +76,75 @@ func MakeDefaultConfig() config.Config {
}

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

// RunBazeliskWithArgsFunc runs the main Bazelisk logic for the given ArgsFunc and Bazel
// repositories.
//
// This will run Bazel in a subprocess and return its exit code.
func RunBazeliskWithArgsFunc(argsFunc ArgsFunc, repos *Repositories) (int, error) {

return RunBazeliskWithArgsFuncAndConfig(argsFunc, repos, MakeDefaultConfig())
}

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

// RunBazeliskWithArgsFuncAndConfigAndOut runs the main Bazelisk logic for the given ArgsFunc and Bazel
// repositories and config, writing its stdout to the passed writer.
//
// This will run Bazel in a subprocess and return its exit code.
func RunBazeliskWithArgsFuncAndConfigAndOut(argsFunc ArgsFunc, repos *Repositories, config config.Config, out io.Writer) (int, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of this function will break users (#526), so I'd like to keep it.
Also, in the current version the out parameter of runBazel is basically useless since it will always be nil.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah thanks, I didn't realize bazelisk supported use as a library so I just looked for uses in bazelisk itself. Added it back.

Are there going to be callers who need to call one of these RunBazelisk functions with out == nil and have it return instead of execing? The way I've written this right now doesn't support that, and I'm not sure how you'd rather me do that if we need that - duplicate all these public functions with ExecBazelisk copies? Or add a flag to exec if possible or not?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I think if I were a user I'd want to have the behavior of these preserved to return instead of execing, so I've added Exec versions of the ones that make sense (without out) and left the Run ones to still run in a subprocess and return

return RunOrExecBazeliskWithArgsFuncAndConfigAndOut(argsFunc, repos, config, out, false)
}

// ExecBazelisk runs the main Bazelisk logic for the given arguments and Bazel repositories.
//
// If possible (i.e. on non-Windows platforms), this will replace the current process with Bazel
// and will not return. On Windows, this will execute Bazel in a new process and return its exit
// code.
func ExecBazelisk(args []string, repos *Repositories) (int, error) {
return ExecBazeliskWithArgsFunc(func(_ string) []string { return args }, repos)
}

// ExecBazeliskWithArgsFunc runs the main Bazelisk logic for the given ArgsFunc and Bazel
// repositories.
//
// If possible (i.e. on non-Windows platforms), this will replace the current process with Bazel
// and will not return. On Windows, this will execute Bazel in a new process and return its exit
// code.
func ExecBazeliskWithArgsFunc(argsFunc ArgsFunc, repos *Repositories) (int, error) {
return ExecBazeliskWithArgsFuncAndConfig(argsFunc, repos, MakeDefaultConfig())
}

// ExecBazeliskWithArgsFuncAndConfig runs the main Bazelisk logic for the given ArgsFunc and Bazel
// repositories and config.
//
// If possible (i.e. on non-Windows platforms), this will replace the current process with Bazel
// and will not return. On Windows, this will execute Bazel in a new process and return its exit
// code.
func ExecBazeliskWithArgsFuncAndConfig(argsFunc ArgsFunc, repos *Repositories, config config.Config) (int, error) {
return RunOrExecBazeliskWithArgsFuncAndConfigAndOut(argsFunc, repos, config, nil, true)
}

// RunOrExecBazeliskWithArgsFuncAndConfigAndOut runs the main Bazelisk logic for the given ArgsFunc and Bazel
// repositories and config.
//
// If exec is true, this will replace the current process with Bazel and will not return (this is
// not possible on Windows; on Windows this will execute Bazel in a new process and return its exit
// code even if exec is true). `out` is not supported in exec mode.
//
// If exec is false, this will run Bazel in a subprocess and return its exit code, writing its stdout
// to the passed writer if provided.
func RunOrExecBazeliskWithArgsFuncAndConfigAndOut(argsFunc ArgsFunc, repos *Repositories, config config.Config, out io.Writer, exec bool) (int, error) {
httputil.UserAgent = getUserAgent(config)

bazelInstallation, err := GetBazelInstallation(repos, config)
Expand All @@ -108,8 +157,8 @@ func RunBazeliskWithArgsFuncAndConfigAndOut(argsFunc ArgsFunc, repos *Repositori
// --print_env must be the first argument.
if len(args) > 0 && args[0] == "--print_env" {
// print environment variables for sub-processes
cmd := makeBazelCmd(bazelInstallation.Path, args, nil, config)
for _, val := range cmd.Env {
_, _, env := makeBazelCmd(bazelInstallation.Path, args, config)
for _, val := range env {
fmt.Println(val)
}
return 0, nil
Expand Down Expand Up @@ -161,11 +210,22 @@ func RunBazeliskWithArgsFuncAndConfigAndOut(argsFunc ArgsFunc, repos *Repositori
}
}

exitCode, err := runBazel(bazelInstallation.Path, args, out, config)
if err != nil {
return -1, fmt.Errorf("could not run Bazel: %v", err)
if exec {
if out != nil {
return -1, fmt.Errorf("cannot run bazelisk in exec mode with a non-nil output writer")
}
exitCode, err := execBazel(bazelInstallation.Path, args, config)
if err != nil {
return -1, fmt.Errorf("could not run Bazel: %v", err)
}
return exitCode, nil
} else {
exitCode, err := runBazel(bazelInstallation.Path, args, out, config)
if err != nil {
return -1, fmt.Errorf("could not run Bazel: %v", err)
}
return exitCode, nil
}
return exitCode, nil
}

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

func prependDirToPathList(cmd *exec.Cmd, dir string) {
func prependDirToPathList(env []string, dir string) {
found := false
for idx, val := range cmd.Env {
for idx, val := range env {
splits := strings.Split(val, "=")
if len(splits) != 2 {
continue
}
if strings.EqualFold(splits[0], "PATH") {
found = true
cmd.Env[idx] = fmt.Sprintf("PATH=%s%s%s", dir, string(os.PathListSeparator), splits[1])
env[idx] = fmt.Sprintf("PATH=%s%s%s", dir, string(os.PathListSeparator), splits[1])
break
}
}

if !found {
cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", dir))
env = append(env, fmt.Sprintf("PATH=%s", dir))
}
}

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

cmd := exec.Command(execPath, args...)
cmd.Env = append(os.Environ(), skipWrapperEnv+"=true")
env := append(os.Environ(), skipWrapperEnv+"=true")
if execPath != bazel {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", bazelReal, bazel))
env = append(env, fmt.Sprintf("%s=%s", bazelReal, bazel))
}
selfPath, err := os.Executable()
if err != nil {
cmd.Env = append(cmd.Env, bazeliskEnv+"="+selfPath)
env = append(env, bazeliskEnv+"="+selfPath)
}
prependDirToPathList(cmd, filepath.Dir(execPath))
prependDirToPathList(env, filepath.Dir(execPath))

commandLine := []string{execPath}
commandLine = append(commandLine, args...)
return execPath, commandLine, env
}

func execBazel(bazel string, args []string, config config.Config) (int, error) {
if runtime.GOOS == "windows" {
// syscall.Exec is not supported on windows
return runBazel(bazel, args, nil, config)
}

execPath, args, env := makeBazelCmd(bazel, args, config)

err := syscall.Exec(execPath, args, env)
return 1, fmt.Errorf("could not start Bazel: %v", err)
}

func runBazel(bazel string, args []string, out io.Writer, config config.Config) (int, error) {
execPath, commandLine, env := makeBazelCmd(bazel, args, config)

cmd := exec.Command(execPath, commandLine[1:]...)
cmd.Env = env
cmd.Stdin = os.Stdin
if out == nil {
cmd.Stdout = os.Stdout
} else {
cmd.Stdout = out
}
cmd.Stderr = os.Stderr
return cmd
}

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