blob: 98bf5dc590272952301babcd52cc961a915c6682 [file]
package bazel
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/task_driver/go/lib/os_steps"
)
// Bazel provides a Task Driver API for working with Bazel (via the Bazelisk launcher, see
// https://github.com/bazelbuild/bazelisk).
type Bazel struct {
cachePath string
rbeCredentialFile string
repositoryCachePath string
workspace string
cleanup func()
}
type BazelOptions struct {
CachePath string
RepositoryCachePath string
}
// OverrideBazelRC masks the user .bazelrc file using the provided configuration
// by setting the BAZELRC environment variable. This makes it easy for all
// subsequent calls to Bazel use the right command line args, even if Bazel is
// not invoked directly from task_driver (e.g. from a Makefile). Returns a
// function which should be run deferred for cleanup.
func OverrideBazelRC(ctx context.Context, bazelOpts BazelOptions) (func(), error) {
tmp, err := os.MkdirTemp("", "")
if err != nil {
return nil, skerr.Wrap(err)
}
cleanup := func() {
if err := os_steps.RemoveAll(ctx, tmp); err != nil {
sklog.Errorf("Failed to cleanup temporary BazelRC directory: %s", err)
}
}
tmpBazelRCPath := filepath.Join(tmp, ".bazelrc")
if err := WriteBazelRC(ctx, tmpBazelRCPath, bazelOpts); err != nil {
cleanup()
return nil, skerr.Wrap(err)
}
if err := os.Setenv("BAZELRC", tmpBazelRCPath); err != nil {
cleanup()
return nil, skerr.Wrap(err)
}
return cleanup, nil
}
// WriteBazelRC writes the given BazelOptions to the given file path.
func WriteBazelRC(ctx context.Context, path string, bazelOpts BazelOptions) error {
var bazelRCLines []string
if bazelOpts.CachePath != "" {
// https://docs.bazel.build/versions/main/output_directories.html#current-layout
bazelRCLines = append(bazelRCLines, fmt.Sprintf("startup --output_user_root=%s", bazelOpts.CachePath))
}
if bazelOpts.RepositoryCachePath != "" {
// Also keep the repository cache on disk so that it survives reboots.
// https://bazel.build/docs/build#repository-cache
bazelRCLines = append(bazelRCLines, fmt.Sprintf("build --repository_cache=%s", bazelOpts.RepositoryCachePath))
}
bazelRCContent := strings.Join(bazelRCLines, "\n")
return os_steps.WriteFile(ctx, path, []byte(bazelRCContent), 0666)
}
// New returns a new Bazel instance.
func New(ctx context.Context, workspace, rbeCredentialFile string, opts BazelOptions) (*Bazel, error) {
cleanup, err := OverrideBazelRC(ctx, opts)
if err != nil {
return nil, skerr.Wrap(err)
}
absCredentialFile := ""
if rbeCredentialFile != "" {
absCredentialFile, err = os_steps.Abs(ctx, rbeCredentialFile)
if err != nil {
return nil, skerr.Wrap(err)
}
}
return &Bazel{
cachePath: opts.CachePath,
rbeCredentialFile: absCredentialFile,
repositoryCachePath: opts.RepositoryCachePath,
workspace: workspace,
cleanup: cleanup,
}, nil
}
// Do executes a Bazel subcommand.
func (b *Bazel) Do(ctx context.Context, subCmd string, args ...string) (string, error) {
cmd := []string{"bazelisk", subCmd}
cmd = append(cmd, args...)
return exec.RunCwd(ctx, b.workspace, cmd...)
}
// DoOnRBE executes a Bazel subcommand on RBE.
func (b *Bazel) DoOnRBE(ctx context.Context, subCmd string, args ...string) (string, error) {
// See https://bazel.build/reference/command-line-reference
cmd := []string{
"--config=remote",
"--sandbox_base=/dev/shm", // Make builds faster by using a RAM disk for the sandbox.
}
if b.rbeCredentialFile != "" {
cmd = append(cmd, "--google_credentials="+b.rbeCredentialFile)
} else {
cmd = append(cmd, "--google_default_credentials")
}
cmd = append(cmd, args...)
return b.Do(ctx, subCmd, cmd...)
}
// Cleanup must be run after the caller is finished with this Bazel instance.
func (b *Bazel) Cleanup() {
b.cleanup()
}