xref: /aosp_15_r20/external/skia/infra/bots/task_drivers/bazel_build/bazel_build.go (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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