1// Copyright 2021 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package main 16 17import ( 18 "bufio" 19 "bytes" 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "io/ioutil" 25 "net/url" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "strings" 30) 31 32const ( 33 toolTag = "gopackagesdriver" 34) 35 36type Bazel struct { 37 bazelBin string 38 workspaceRoot string 39 bazelStartupFlags []string 40 info map[string]string 41} 42 43// Minimal BEP structs to access the build outputs 44type BEPNamedSet struct { 45 NamedSetOfFiles *struct { 46 Files []struct { 47 Name string `json:"name"` 48 URI string `json:"uri"` 49 } `json:"files"` 50 } `json:"namedSetOfFiles"` 51} 52 53func NewBazel(ctx context.Context, bazelBin, workspaceRoot string, bazelStartupFlags []string) (*Bazel, error) { 54 b := &Bazel{ 55 bazelBin: bazelBin, 56 workspaceRoot: workspaceRoot, 57 bazelStartupFlags: bazelStartupFlags, 58 } 59 if err := b.fillInfo(ctx); err != nil { 60 return nil, fmt.Errorf("unable to query bazel info: %w", err) 61 } 62 return b, nil 63} 64 65func (b *Bazel) fillInfo(ctx context.Context) error { 66 b.info = map[string]string{} 67 output, err := b.run(ctx, "info") 68 if err != nil { 69 return err 70 } 71 scanner := bufio.NewScanner(bytes.NewBufferString(output)) 72 for scanner.Scan() { 73 parts := strings.SplitN(strings.TrimSpace(scanner.Text()), ":", 2) 74 b.info[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) 75 } 76 return nil 77} 78 79func (b *Bazel) run(ctx context.Context, command string, args ...string) (string, error) { 80 defaultArgs := []string{ 81 command, 82 "--tool_tag=" + toolTag, 83 "--ui_actions_shown=0", 84 } 85 cmd := exec.CommandContext(ctx, b.bazelBin, concatStringsArrays(b.bazelStartupFlags, defaultArgs, args)...) 86 fmt.Fprintln(os.Stderr, "Running:", cmd.Args) 87 cmd.Dir = b.WorkspaceRoot() 88 cmd.Stderr = os.Stderr 89 output, err := cmd.Output() 90 return string(output), err 91} 92 93func (b *Bazel) Build(ctx context.Context, args ...string) ([]string, error) { 94 jsonFile, err := ioutil.TempFile("", "gopackagesdriver_bep_") 95 if err != nil { 96 return nil, fmt.Errorf("unable to create BEP JSON file: %w", err) 97 } 98 defer func() { 99 jsonFile.Close() 100 os.Remove(jsonFile.Name()) 101 }() 102 103 args = append([]string{ 104 "--show_result=0", 105 "--build_event_json_file=" + jsonFile.Name(), 106 "--build_event_json_file_path_conversion=no", 107 }, args...) 108 if _, err := b.run(ctx, "build", args...); err != nil { 109 // Ignore a regular build failure to get partial data. 110 // See https://docs.bazel.build/versions/main/guide.html#what-exit-code-will-i-get on 111 // exit codes. 112 var exerr *exec.ExitError 113 if !errors.As(err, &exerr) || exerr.ExitCode() != 1 { 114 return nil, fmt.Errorf("bazel build failed: %w", err) 115 } 116 } 117 118 files := make([]string, 0) 119 decoder := json.NewDecoder(jsonFile) 120 for decoder.More() { 121 var namedSet BEPNamedSet 122 if err := decoder.Decode(&namedSet); err != nil { 123 return nil, fmt.Errorf("unable to decode %s: %w", jsonFile.Name(), err) 124 } 125 126 if namedSet.NamedSetOfFiles != nil { 127 for _, f := range namedSet.NamedSetOfFiles.Files { 128 fileUrl, err := url.Parse(f.URI) 129 if err != nil { 130 return nil, fmt.Errorf("unable to parse file URI: %w", err) 131 } 132 files = append(files, filepath.FromSlash(fileUrl.Path)) 133 } 134 } 135 } 136 137 return files, nil 138} 139 140func (b *Bazel) Query(ctx context.Context, args ...string) ([]string, error) { 141 output, err := b.run(ctx, "query", args...) 142 if err != nil { 143 return nil, fmt.Errorf("bazel query failed: %w", err) 144 } 145 146 trimmedOutput := strings.TrimSpace(output) 147 if len(trimmedOutput) == 0 { 148 return nil, nil 149 } 150 151 return strings.Split(trimmedOutput, "\n"), nil 152} 153 154func (b *Bazel) WorkspaceRoot() string { 155 return b.workspaceRoot 156} 157 158func (b *Bazel) ExecutionRoot() string { 159 return b.info["execution_root"] 160} 161 162func (b *Bazel) OutputBase() string { 163 return b.info["output_base"] 164} 165