xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/gopackagesdriver/bazel.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
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