1// Copyright 2022 Google LLC 2// 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5// 6// This executable runs a Bazel(isk) build command for a single label using the provided 7// config (which is assumed to be in //bazel/buildrc) and any provided Bazel args. 8// This handles any setup needed to run Bazel on our CI machines before running the task, like 9// setting up logs and the Bazel cache. 10package main 11 12import ( 13 "context" 14 "flag" 15 "fmt" 16 "io/fs" 17 "path/filepath" 18 19 infra_common "go.skia.org/infra/go/common" 20 "go.skia.org/infra/go/skerr" 21 "go.skia.org/infra/task_driver/go/lib/bazel" 22 "go.skia.org/infra/task_driver/go/lib/os_steps" 23 "go.skia.org/infra/task_driver/go/td" 24 "go.skia.org/skia/infra/bots/task_drivers/common" 25) 26 27var ( 28 // Required properties for this task. 29 projectId = flag.String("project_id", "", "ID of the Google Cloud project.") 30 taskId = flag.String("task_id", "", "ID of this task.") 31 taskName = flag.String("task_name", "", "Name of the task.") 32 workdir = flag.String("workdir", ".", "Working directory in which the build will be performed.") 33 outPath = flag.String("out_path", "", "Directory into which to copy the //bazel-bin subdirectories provided via --saved_output_dir. If unset, nothing will be copied.") 34 savedOutputDir = infra_common.NewMultiStringFlag("saved_output_dir", nil, `//bazel-bin subdirectories to copy into the path provided via --out_path (e.g. "tests" will copy the contents of //bazel-bin/tests).`) 35 36 // Optional flags. 37 local = flag.Bool("local", false, "True if running locally (as opposed to on the CI/CQ)") 38 output = flag.String("o", "", "If provided, dump a JSON blob of step data to the given file. Prints to stdout if '-' is given.") 39) 40 41func main() { 42 bazelFlags := common.MakeBazelFlags(common.MakeBazelFlagsOpts{ 43 Label: true, 44 Config: true, 45 AdditionalArgs: true, 46 }) 47 48 // StartRun calls flag.Parse() 49 ctx := td.StartRun(projectId, taskId, taskName, output, local) 50 defer td.EndRun(ctx) 51 52 bazelFlags.Validate(ctx) 53 54 if *outPath != "" && len(*savedOutputDir) == 0 { 55 td.Fatal(ctx, fmt.Errorf("at least one --saved_output_dir is required if --out_path is set")) 56 } 57 58 checkoutPath, err := os_steps.Abs(ctx, *workdir) 59 if err != nil { 60 td.Fatal(ctx, err) 61 } 62 var outputPath string 63 if *outPath != "" { 64 outputPath, err = os_steps.Abs(ctx, *outPath) 65 if err != nil { 66 td.Fatal(ctx, err) 67 } 68 } 69 70 opts := bazel.BazelOptions{ 71 // We want the cache to be on a bigger disk than default. The root disk, where the home 72 // directory (and default Bazel cache) lives, is only 15 GB on our GCE VMs. 73 CachePath: *bazelFlags.CacheDir, 74 } 75 bzl, err := bazel.New(ctx, checkoutPath, "", opts) 76 if err != nil { 77 td.Fatal(ctx, err) 78 } 79 80 // Schedule the cleanup steps. 81 defer func() { 82 if !*local { 83 // Ignore any error here until after we've run "shutdown". 84 cleanErr := common.BazelCleanIfLowDiskSpace(ctx, *bazelFlags.CacheDir, checkoutPath, "bazelisk") 85 if _, err := bzl.Do(ctx, "shutdown"); err != nil { 86 td.Fatal(ctx, err) 87 } 88 if cleanErr != nil { 89 td.Fatal(ctx, cleanErr) 90 } 91 } 92 }() 93 94 // Perform the build. 95 args := append([]string{*bazelFlags.Label, fmt.Sprintf("--config=%s", *bazelFlags.Config)}, *bazelFlags.AdditionalArgs...) 96 if _, err := bzl.Do(ctx, "build", args...); err != nil { 97 td.Fatal(ctx, err) 98 } 99 100 if outputPath != "" { 101 if err := copyBazelBinSubdirs(ctx, checkoutPath, *savedOutputDir, outputPath); err != nil { 102 td.Fatal(ctx, err) 103 } 104 } 105} 106 107// copyBazelBinSubdirs copies the contents of the bazel-bin directory into the given path. 108func copyBazelBinSubdirs(ctx context.Context, checkoutDir string, bazelBinSubdirs []string, destinationDir string) error { 109 for _, subdir := range bazelBinSubdirs { 110 if err := td.Do(ctx, td.Props(fmt.Sprintf("Copying bazel-bin subdirectory %q into %q", subdir, destinationDir)), func(ctx context.Context) error { 111 srcDir := filepath.Join(checkoutDir, "bazel-bin", subdir) 112 dstDir := filepath.Join(destinationDir, subdir) 113 114 return filepath.WalkDir(srcDir, func(path string, d fs.DirEntry, err error) error { 115 if err != nil { 116 // A non-nil err argument tells us the reason why filepath.WalkDir will not walk into 117 // that directory (see https://pkg.go.dev/io/fs#WalkDirFunc). We choose to fail loudly 118 // as this might reveal permission issues, problems with symlinks, etc. 119 return skerr.Wrap(err) 120 } 121 122 relPath, err := filepath.Rel(srcDir, path) 123 if err != nil { 124 return skerr.Wrap(err) 125 } 126 dstPath := filepath.Join(dstDir, relPath) 127 128 if d.IsDir() { 129 return skerr.Wrap(os_steps.MkdirAll(ctx, dstPath)) 130 } 131 return skerr.Wrap(os_steps.CopyFile(ctx, path, dstPath)) 132 }) 133 }); err != nil { 134 return skerr.Wrap(err) 135 } 136 } 137 return nil 138} 139