Skip to content

Commit

Permalink
cli: support tools/bazel
Browse files Browse the repository at this point in the history
  • Loading branch information
bduffany committed May 24, 2024
1 parent 2b7d207 commit ba9af0a
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 2 deletions.
40 changes: 39 additions & 1 deletion cli/bazelisk/bazelisk.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package bazelisk
import (
"fmt"
"io"
goLog "log"
"os"
"path/filepath"
"strings"
"sync"
"syscall"

goLog "log"

"github.com/bazelbuild/bazelisk/config"
"github.com/bazelbuild/bazelisk/core"
"github.com/bazelbuild/bazelisk/repositories"
Expand All @@ -23,6 +24,43 @@ var (
setVersionErr error
)

// HandleWrapper re-invokes the CLI using the tools/bazel script if present,
// setting BAZEL_REAL to point to the CLI itself.
//
// This needs to be handled by us, rather than bazelisk, in order to support
// passing --config options using tools/bazel. Otherwise, we'd canonicalize args
// before invoking tools/bazel, which sets --ignore_all_rc_files and prevents
// --config flags from working.
//
// Note that this behavior subtly differs from bazelisk in that BAZEL_REAL will
// point to bb, which is a bazel wrapper rather than a "real" bazel binary.
// Despite this difference, in practice we expect this to be a net improvement
// in compatibility.
func HandleWrapper() error {
if os.Getenv("BAZELISK_SKIP_WRAPPER") == "true" {
return nil
}
os.Setenv("BAZELISK_SKIP_WRAPPER", "true")
ws, err := workspace.Path()
if err != nil {
return nil
}
scriptPath := filepath.Join(ws, "tools/bazel")
os.Setenv("BAZEL_REAL", os.Args[0])

// Try an exec() call to invoke tools/bazel. If tools/bazel exists and is
// executable then the exec call should replace the current process with a
// tools/bazel process which should then call back into `bb` by invoking
// $BAZEL_REAL, which we've set to args[0].
//
// If tools/bazel doesn't exist or isn't executable then the exec call will
// just fail and we just silently ignore the error, which is what bazelisk
// does as well.

_ = syscall.Exec(scriptPath, append([]string{scriptPath}, os.Args[1:]...), os.Environ())
return nil
}

type RunOpts struct {
// Stdout is the Writer where bazelisk should write its stdout.
// Defaults to os.Stdout if nil.
Expand Down
2 changes: 2 additions & 0 deletions cli/cmd/bb/bb.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func main() {
}

func run() (exitCode int, err error) {
bazelisk.HandleWrapper()

start := time.Now()
// Record original arguments so we can show them in the UI.
originalArgs := append([]string{}, os.Args...)
Expand Down
6 changes: 5 additions & 1 deletion cli/metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ func runGit(dir string, args ...string) (output string, err error) {
cmd.Dir = dir
b, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("command `git %s` failed: %s", strings.Join(args, " "), string(b))
msg := strings.TrimSpace(string(b))
if msg == "" {
msg = err.Error()
}
return "", fmt.Errorf("command `git %s` failed: %s", strings.Join(args, " "), msg)
}
return strings.TrimSpace(string(b)), nil
}
Expand Down
29 changes: 29 additions & 0 deletions cli/test/integration/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,35 @@ func TestInvokeViaBazelisk(t *testing.T) {
}
}

func TestInvokeViaBazeliskWithToolsBazelWrapper(t *testing.T) {
ws := testcli.NewWorkspace(t)
testfs.WriteAllFileContents(t, ws, map[string]string{
".bazelversion": fmt.Sprintf("%s\n%s\n", testcli.BinaryPath(t), testbazel.BinaryPath(t)),
".bazelrc": `
build:foo --action_env=EXIT_CODE=0
`,
"WORKSPACE": "",
"BUILD": `
load(":defs.bzl", "run_shell")
run_shell(name = "exit_command", command = "exit ${EXIT_CODE:-1}")
`,
// Note: the tools/bazel script passes a --config flag. After expanding
// and canonicalizing flags, we set --ignore_all_rc_files, which means
// that --config flags are no longer allowed. So, this is testing that
// we invoke tools/bazel *after* calling tools/bazel.
"tools/bazel": `#!/bin/sh
exec "$BAZEL_REAL" "$@" --config=foo
`,
})

testfs.MakeExecutable(t, ws, "tools/bazel")

cmd := testcli.BazeliskCommand(t, ws, "--verbose=1", "build", ":all")
b, err := testcli.CombinedOutput(cmd)

require.NoError(t, err, "output: %s", string(b))
}

func TestBazelHelp(t *testing.T) {
ws := testcli.NewWorkspace(t)
cmd := testcli.Command(t, ws, "help", "completion")
Expand Down
19 changes: 19 additions & 0 deletions cli/testutil/testcli/testcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ func NewWorkspace(t *testing.T) string {
ws := testbazel.MakeTempWorkspace(t, map[string]string{
"WORKSPACE": "",
".bazelversion": testbazel.BinaryPath(t),
// Add some basic rules for convenience. The run_shell rule is helpful
// for running simple bazel actions. (Built-in rules like sh_test and
// genrule are relatively heavy and can slow down test execution time)
"defs.bzl": `
def _run_shell_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.run_shell(
outputs = [out],
use_default_shell_env = True, # respect --action_env
command = """
set -e
touch "%s"
%s
""" % (out.path, ctx.attr.command),
)
return [DefaultInfo(files = depset([out]))]
run_shell = rule(implementation = _run_shell_impl, attrs = {"command": attr.string()})
`,
})
// Make it a git workspace to test git metadata.
testgit.Init(t, ws)
Expand Down

0 comments on commit ba9af0a

Please sign in to comment.