// Copyright 2021 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bufio" "bytes" "context" "encoding/json" "errors" "fmt" "io/ioutil" "net/url" "os" "os/exec" "path/filepath" "strings" ) const ( toolTag = "gopackagesdriver" ) type Bazel struct { bazelBin string workspaceRoot string bazelStartupFlags []string info map[string]string version string } // Minimal BEP structs to access the build outputs type BEPNamedSet struct { NamedSetOfFiles *struct { Files []struct { Name string `json:"name"` URI string `json:"uri"` } `json:"files"` } `json:"namedSetOfFiles"` } func NewBazel(ctx context.Context, bazelBin, workspaceRoot string, bazelStartupFlags []string) (*Bazel, error) { b := &Bazel{ bazelBin: bazelBin, workspaceRoot: workspaceRoot, bazelStartupFlags: bazelStartupFlags, version: "6", } if err := b.fillInfo(ctx); err != nil { return nil, fmt.Errorf("unable to query bazel info: %w", err) } return b, nil } func (b *Bazel) fillInfo(ctx context.Context) error { b.info = map[string]string{} output, err := b.run(ctx, "info") if err != nil { return err } scanner := bufio.NewScanner(bytes.NewBufferString(output)) for scanner.Scan() { parts := strings.SplitN(strings.TrimSpace(scanner.Text()), ":", 2) b.info[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) } release := strings.Split(b.info["release"], " ") if len(release) == 2 { b.version = release[1] } return nil } func (b *Bazel) run(ctx context.Context, command string, args ...string) (string, error) { defaultArgs := []string{ command, "--tool_tag=" + toolTag, "--ui_actions_shown=0", } cmd := exec.CommandContext(ctx, b.bazelBin, concatStringsArrays(b.bazelStartupFlags, defaultArgs, args)...) fmt.Fprintln(os.Stderr, "Running:", cmd.Args) cmd.Dir = b.WorkspaceRoot() cmd.Stderr = os.Stderr output, err := cmd.Output() return string(output), err } func (b *Bazel) Build(ctx context.Context, args ...string) ([]string, error) { jsonFile, err := ioutil.TempFile("", "gopackagesdriver_bep_") if err != nil { return nil, fmt.Errorf("unable to create BEP JSON file: %w", err) } defer func() { jsonFile.Close() os.Remove(jsonFile.Name()) }() args = append([]string{ "--show_result=0", "--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) } } files := make([]string, 0) decoder := json.NewDecoder(jsonFile) for decoder.More() { var namedSet BEPNamedSet if err := decoder.Decode(&namedSet); err != nil { return nil, fmt.Errorf("unable to decode %s: %w", jsonFile.Name(), err) } if namedSet.NamedSetOfFiles != nil { for _, f := range namedSet.NamedSetOfFiles.Files { fileUrl, err := url.Parse(f.URI) if err != nil { return nil, fmt.Errorf("unable to parse file URI: %w", err) } files = append(files, filepath.FromSlash(fileUrl.Path)) } } } return files, nil } func (b *Bazel) Query(ctx context.Context, args ...string) ([]string, error) { output, err := b.run(ctx, "query", args...) if err != nil { return nil, fmt.Errorf("bazel query failed: %w", err) } trimmedOutput := strings.TrimSpace(output) if len(trimmedOutput) == 0 { return nil, nil } return strings.Split(trimmedOutput, "\n"), nil } func (b *Bazel) WorkspaceRoot() string { return b.workspaceRoot } func (b *Bazel) ExecutionRoot() string { return b.info["execution_root"] } func (b *Bazel) OutputBase() string { return b.info["output_base"] }