gopackagesdriver: ignore bazel analysis errors (#4567)
**What type of PR is this?**
> Bug fix
**What does this PR do? Why is it needed?**
Bazel exits with code 7 (ANALYSIS_FAILURE) when querying a file that's
not mentioned in BUILD. gopackagesdriver can returns partial results for
code 1 (BUILD_FAILURE), but not 7.
gopackagesdriver often hits this for a new file not added to BUILD. A
file= query may still fail in this case because the query will match no
labels, but at least that's no longer a bazel error.
**Which issues(s) does this PR fix?**
Fixes #4565
**Other notes for review**
diff --git a/go/tools/gopackagesdriver/bazel.go b/go/tools/gopackagesdriver/bazel.go
index 323ac15..9ef3254 100644
--- a/go/tools/gopackagesdriver/bazel.go
+++ b/go/tools/gopackagesdriver/bazel.go
@@ -133,14 +133,8 @@
"--build_event_json_file=" + jsonFile.Name(),
"--build_event_json_file_path_conversion=no",
}, args...)
- if _, err := b.run(ctx, "build", args...); err != nil {
- // Ignore a regular build failure to get partial data.
- // See https://docs.bazel.build/versions/main/guide.html#what-exit-code-will-i-get on
- // exit codes.
- var exerr *exec.ExitError
- if !errors.As(err, &exerr) || exerr.ExitCode() != 1 {
- return nil, fmt.Errorf("bazel build failed: %w", err)
- }
+ if _, err := b.run(ctx, "build", args...); err != nil && !isAllowedBazelError(err) {
+ return nil, fmt.Errorf("bazel build failed: %w", err)
}
files := make([]string, 0)
@@ -169,7 +163,7 @@
func (b *Bazel) Query(ctx context.Context, args ...string) ([]string, error) {
output, err := b.run(ctx, "query", args...)
- if err != nil {
+ if err != nil && !isAllowedBazelError(err) {
return nil, fmt.Errorf("bazel query failed: %w", err)
}
@@ -202,6 +196,26 @@
return b.info["output_base"]
}
+// isAllowedBazelError if err is an *exec.ExitError with an expected exit code
+// for a bazel command. gopackagesdriver is often invoked when the user
+// is editing code, and the build isn't expected to work. When we get one of
+// these codes, we should suppress the error and return partial results.
+func isAllowedBazelError(err error) bool {
+ var exitErr *exec.ExitError
+ if !errors.As(err, &exitErr) {
+ return false
+ }
+ switch exitErr.ExitCode() {
+ // Refer to https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/util/ExitCode.java.
+ case 1, // BUILD_FAILURE
+ 3, // PARTIAL_ANALYSIS_FAILURE
+ 7: // ANALYSIS_FAILURE
+ return true
+ default:
+ return false
+ }
+}
+
type bazelVersion [3]int
func parseBazelVersion(raw string) (bazelVersion, bool) {
diff --git a/go/tools/gopackagesdriver/gopackagesdriver_test.go b/go/tools/gopackagesdriver/gopackagesdriver_test.go
index 44094ac..06798b9 100644
--- a/go/tools/gopackagesdriver/gopackagesdriver_test.go
+++ b/go/tools/gopackagesdriver/gopackagesdriver_test.go
@@ -109,6 +109,11 @@
func main() {
fmt.Fprintln(os.Stderr, "Subdirectory Hello World!")
}
+
+-- unattached.go --
+package unattached
+
+// not mentioned in any target
`,
})
}
@@ -141,9 +146,9 @@
}
wantCompiledGoFiles := map[string]struct{}{
- "hello.go": {},
- "_cgo_gotypes.go": {},
- "_cgo_imports.go": {},
+ "hello.go": {},
+ "_cgo_gotypes.go": {},
+ "_cgo_imports.go": {},
"hellocgo.cgo1.go": {},
}
for _, file := range pkg.CompiledGoFiles {
@@ -159,7 +164,7 @@
}
wantGoFiles := map[string]struct{}{
- "hello.go": {},
+ "hello.go": {},
"hellocgo.go": {},
}
for _, file := range pkg.GoFiles {
@@ -376,7 +381,25 @@
}
}
-func runForTest(t *testing.T, driverRequest packages.DriverRequest, relativeWorkingDir string, args ...string) packages.DriverResponse {
+func TestUnattached(t *testing.T) {
+ runForTestExpectError(t, "found no labels matching the requests", packages.DriverRequest{}, ".", "file=unattached.go")
+}
+
+func runForTest(
+ t *testing.T,
+ driverRequest packages.DriverRequest,
+ relativeWorkingDir string,
+ args ...string) packages.DriverResponse {
+ t.Helper()
+ return runForTestExpectError(t, "", driverRequest, relativeWorkingDir, args...)
+}
+
+func runForTestExpectError(
+ t *testing.T,
+ wantError string,
+ driverRequest packages.DriverRequest,
+ relativeWorkingDir string,
+ args ...string) packages.DriverResponse {
t.Helper()
// Remove most environment variables, other than those on an allowlist.
@@ -441,8 +464,17 @@
}
in := bytes.NewReader(driverRequestJson)
out := &bytes.Buffer{}
- if err := run(context.Background(), in, out, args); err != nil {
- t.Fatalf("running gopackagesdriver: %v", err)
+ err = run(context.Background(), in, out, args)
+ if err == nil && wantError != "" {
+ t.Fatal("unexpected success")
+ } else if err != nil {
+ errMsg := err.Error()
+ if wantError == "" {
+ t.Fatalf("running gopackagesdriver: %s", errMsg)
+ } else if !strings.Contains(errMsg, wantError) {
+ t.Fatalf("running gopackagesdriver: %s; error did not contain %q", errMsg, wantError)
+ }
+ return packages.DriverResponse{}
}
var resp packages.DriverResponse
if err := json.Unmarshal(out.Bytes(), &resp); err != nil {