From 41988f96d2155385de137d87e2ab14a93bcabba2 Mon Sep 17 00:00:00 2001 From: Greg Magolan Date: Sun, 15 Jan 2023 20:59:07 -0800 Subject: [PATCH] fix(cli): fix case where linkLocalBazel fails when os.UserCacheDir returns path starting with ~ (#1182) Also run `help flags-as-proto` command in a UserCacheDir() location instead of the tmp dir and fix order of error handling for the result of that call. UserCacheDir() is more robust since tmpdir is not guaranteed to exist or to be writable (as per [golang docs](https://pkg.go.dev/os#TempDir)). We already depend on UserCacheDir() in the CLI code. GitOrigin-RevId: df084e1effce5ba4a813f56b33260e9749c5526d --- pkg/bazel/bazel.go | 34 ++++++++++++++++++++++++++-------- pkg/bazel/bazelisk.go | 22 +++++++++++++++++++--- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/pkg/bazel/bazel.go b/pkg/bazel/bazel.go index 767dfa2c5..4a151cf8c 100644 --- a/pkg/bazel/bazel.go +++ b/pkg/bazel/bazel.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "os" + "path" "path/filepath" "strings" @@ -162,6 +163,23 @@ func (b *bazel) Flags() (map[string]*flags.FlagInfo, error) { if allFlags != nil { return allFlags, nil } + + // create a directory in the user cache dir with an empty WORKSPACE file to run + // `bazel help flags-as-proto` in so it doesn't affect the bazel server in the user's WORKSPACE + userCacheDir, err := UserCacheDir() + if err != nil { + return nil, fmt.Errorf("failed to get user cache dir: %w", err) + } + tmpdir := path.Join(userCacheDir, ".aspect/cli-flags-as-proto") + err = os.MkdirAll(tmpdir, os.ModePerm) + if err != nil { + return nil, fmt.Errorf("failed write create directory %s: %w", tmpdir, err) + } + err = os.WriteFile(path.Join(tmpdir, "WORKSPACE"), []byte{}, 0644) + if err != nil { + return nil, fmt.Errorf("failed write WORKSPACE file in %s: %w", tmpdir, err) + } + var stdout bytes.Buffer var stderr bytes.Buffer streams := ioutils.Streams{ @@ -174,23 +192,23 @@ func (b *bazel) Flags() (map[string]*flags.FlagInfo, error) { bazelExitCode := make(chan int, 1) defer close(bazelErrs) defer close(bazelExitCode) - go func() { + + go func(wd string) { // Running in batch mode will prevent bazel from spawning a daemon. Spawning a bazel daemon takes time which is something we don't want here. // Also, instructing bazel to ignore all rc files will protect it from failing if any of the rc files is broken. - tmpdir := os.TempDir() - exitCode, err := b.RunCommand(streams, &tmpdir, "--nobatch", "--ignore_all_rc_files", "help", "flags-as-proto") + exitCode, err := b.RunCommand(streams, &wd, "--nobatch", "--ignore_all_rc_files", "help", "flags-as-proto") bazelErrs <- err bazelExitCode <- exitCode - }() - - if exitCode := <-bazelExitCode; exitCode != 0 { - return nil, fmt.Errorf("failed to get bazel flags: %w", fmt.Errorf("bazel has quit with code %d\nstderr:\n%s", exitCode, stderr.String())) - } + }(tmpdir) if err := <-bazelErrs; err != nil { return nil, fmt.Errorf("failed to get bazel flags: %w", err) } + if exitCode := <-bazelExitCode; exitCode != 0 { + return nil, fmt.Errorf("failed to get bazel flags running in %s: %w", tmpdir, fmt.Errorf("bazel has quit with code %d\nstderr:\n%s", exitCode, stderr.String())) + } + helpProtoBytes, err := io.ReadAll(stdoutDecoder) if err != nil { return nil, fmt.Errorf("failed to get bazel flags: %w", err) diff --git a/pkg/bazel/bazelisk.go b/pkg/bazel/bazelisk.go index 15334fb09..7b68f435f 100644 --- a/pkg/bazel/bazelisk.go +++ b/pkg/bazel/bazelisk.go @@ -67,15 +67,31 @@ func NewBazelisk(workspaceRoot string) *Bazelisk { return &Bazelisk{workspaceRoot: workspaceRoot} } +func UserCacheDir() (string, error) { + userCacheDir, err := os.UserCacheDir() + if err != nil { + return "", fmt.Errorf("could not get the user's cache directory: %v", err) + } + + // We hit a case in a bazel-in-bazel test where os.UserCacheDir() return a path starting with '~'. + // Run it through homedir.Expand to turn it into an absolute path incase that happens. + userCacheDir, err = homedir.Expand(userCacheDir) + if err != nil { + return "", fmt.Errorf("could not expand home directory in path: %v", err) + } + + return userCacheDir, err +} + // Run runs the main Bazelisk logic for the given arguments and Bazel repositories. func (bazelisk *Bazelisk) Run(args []string, repos *core.Repositories, streams ioutils.Streams, env []string, wd *string) (int, error) { httputil.UserAgent = bazelisk.getUserAgent() bazeliskHome := bazelisk.GetEnvOrConfig("BAZELISK_HOME") if len(bazeliskHome) == 0 { - userCacheDir, err := os.UserCacheDir() + userCacheDir, err := UserCacheDir() if err != nil { - return -1, fmt.Errorf("could not get the user's cache directory: %v", err) + return -1, err } bazeliskHome = filepath.Join(userCacheDir, "bazelisk") @@ -128,7 +144,7 @@ func (bazelisk *Bazelisk) Run(args []string, repos *core.Repositories, streams i baseDirectory := filepath.Join(bazeliskHome, "local") bazelPath, err = linkLocalBazel(baseDirectory, bazelPath) if err != nil { - return -1, fmt.Errorf("cound not link local Bazel: %v", err) + return -1, fmt.Errorf("could not link local Bazel: %v", err) } }