1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package main
16
17 import (
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
32 const (
33 toolTag = "gopackagesdriver"
34 )
35
36 type Bazel struct {
37 bazelBin string
38 workspaceRoot string
39 bazelStartupFlags []string
40 info map[string]string
41 version string
42 }
43
44
45 type BEPNamedSet struct {
46 NamedSetOfFiles *struct {
47 Files []struct {
48 Name string `json:"name"`
49 URI string `json:"uri"`
50 } `json:"files"`
51 } `json:"namedSetOfFiles"`
52 }
53
54 func NewBazel(ctx context.Context, bazelBin, workspaceRoot string, bazelStartupFlags []string) (*Bazel, error) {
55 b := &Bazel{
56 bazelBin: bazelBin,
57 workspaceRoot: workspaceRoot,
58 bazelStartupFlags: bazelStartupFlags,
59 version: "6",
60 }
61 if err := b.fillInfo(ctx); err != nil {
62 return nil, fmt.Errorf("unable to query bazel info: %w", err)
63 }
64 return b, nil
65 }
66
67 func (b *Bazel) fillInfo(ctx context.Context) error {
68 b.info = map[string]string{}
69 output, err := b.run(ctx, "info")
70 if err != nil {
71 return err
72 }
73 scanner := bufio.NewScanner(bytes.NewBufferString(output))
74 for scanner.Scan() {
75 parts := strings.SplitN(strings.TrimSpace(scanner.Text()), ":", 2)
76 b.info[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
77 }
78 release := strings.Split(b.info["release"], " ")
79 if len(release) == 2 {
80 b.version = release[1]
81 }
82 return nil
83 }
84
85 func (b *Bazel) run(ctx context.Context, command string, args ...string) (string, error) {
86 defaultArgs := []string{
87 command,
88 "--tool_tag=" + toolTag,
89 "--ui_actions_shown=0",
90 }
91 cmd := exec.CommandContext(ctx, b.bazelBin, concatStringsArrays(b.bazelStartupFlags, defaultArgs, args)...)
92 fmt.Fprintln(os.Stderr, "Running:", cmd.Args)
93 cmd.Dir = b.WorkspaceRoot()
94 cmd.Stderr = os.Stderr
95 output, err := cmd.Output()
96 return string(output), err
97 }
98
99 func (b *Bazel) Build(ctx context.Context, args ...string) ([]string, error) {
100 jsonFile, err := ioutil.TempFile("", "gopackagesdriver_bep_")
101 if err != nil {
102 return nil, fmt.Errorf("unable to create BEP JSON file: %w", err)
103 }
104 defer func() {
105 jsonFile.Close()
106 os.Remove(jsonFile.Name())
107 }()
108
109 args = append([]string{
110 "--show_result=0",
111 "--build_event_json_file=" + jsonFile.Name(),
112 "--build_event_json_file_path_conversion=no",
113 }, args...)
114 if _, err := b.run(ctx, "build", args...); err != nil {
115
116
117
118 var exerr *exec.ExitError
119 if !errors.As(err, &exerr) || exerr.ExitCode() != 1 {
120 return nil, fmt.Errorf("bazel build failed: %w", err)
121 }
122 }
123
124 files := make([]string, 0)
125 decoder := json.NewDecoder(jsonFile)
126 for decoder.More() {
127 var namedSet BEPNamedSet
128 if err := decoder.Decode(&namedSet); err != nil {
129 return nil, fmt.Errorf("unable to decode %s: %w", jsonFile.Name(), err)
130 }
131
132 if namedSet.NamedSetOfFiles != nil {
133 for _, f := range namedSet.NamedSetOfFiles.Files {
134 fileUrl, err := url.Parse(f.URI)
135 if err != nil {
136 return nil, fmt.Errorf("unable to parse file URI: %w", err)
137 }
138 files = append(files, filepath.FromSlash(fileUrl.Path))
139 }
140 }
141 }
142
143 return files, nil
144 }
145
146 func (b *Bazel) Query(ctx context.Context, args ...string) ([]string, error) {
147 output, err := b.run(ctx, "query", args...)
148 if err != nil {
149 return nil, fmt.Errorf("bazel query failed: %w", err)
150 }
151
152 trimmedOutput := strings.TrimSpace(output)
153 if len(trimmedOutput) == 0 {
154 return nil, nil
155 }
156
157 return strings.Split(trimmedOutput, "\n"), nil
158 }
159
160 func (b *Bazel) WorkspaceRoot() string {
161 return b.workspaceRoot
162 }
163
164 func (b *Bazel) ExecutionRoot() string {
165 return b.info["execution_root"]
166 }
167
168 func (b *Bazel) OutputBase() string {
169 return b.info["output_base"]
170 }
171
View as plain text