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