1// Copyright 2023 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 task driver runs various Go linters via Bazel, and fails on any errors or Git diffs. 7package main 8 9import ( 10 "context" 11 "flag" 12 "fmt" 13 "path/filepath" 14 "strings" 15 16 sk_exec "go.skia.org/infra/go/exec" 17 "go.skia.org/infra/task_driver/go/lib/bazel" 18 "go.skia.org/infra/task_driver/go/lib/os_steps" 19 "go.skia.org/infra/task_driver/go/td" 20 "go.skia.org/skia/infra/bots/task_drivers/common" 21) 22 23var ( 24 // Required properties for this task. 25 // We want the cache to be on a bigger disk than default. The root disk, where the home 26 // directory (and default Bazel cache) lives, is only 15 GB on our GCE VMs. 27 gitPath = flag.String("git_path", "", "Location of git binary to use for diffs.") 28 projectId = flag.String("project_id", "", "ID of the Google Cloud project.") 29 taskId = flag.String("task_id", "", "ID of this task.") 30 taskName = flag.String("task_name", "", "Name of the task.") 31 workdir = flag.String("workdir", ".", "Working directory, the root directory of a full Skia checkout") 32 // Optional flags. 33 local = flag.Bool("local", false, "True if running locally (as opposed to on the CI/CQ)") 34 output = flag.String("o", "", "If provided, dump a JSON blob of step data to the given file. Prints to stdout if '-' is given.") 35) 36 37func main() { 38 bazelFlags := common.MakeBazelFlags(common.MakeBazelFlagsOpts{ 39 AdditionalArgs: true, 40 }) 41 42 // StartRun calls flag.Parse() 43 ctx := td.StartRun(projectId, taskId, taskName, output, local) 44 defer td.EndRun(ctx) 45 46 bazelFlags.Validate(ctx) 47 48 if *gitPath == "" { 49 td.Fatal(ctx, fmt.Errorf("--git_path is required")) 50 } 51 52 wd, err := os_steps.Abs(ctx, *workdir) 53 if err != nil { 54 td.Fatal(ctx, err) 55 } 56 absGit, err := os_steps.Abs(ctx, *gitPath) 57 if err != nil { 58 td.Fatal(ctx, err) 59 } 60 if _, err := os_steps.Stat(ctx, absGit); err != nil { 61 fmt.Printf("Cannot stat git binary %s\n", absGit) 62 td.Fatal(ctx, err) 63 } 64 skiaPath := filepath.Join(wd, "skia") 65 66 // When running on the CI, there is not a git checkout here, so we make a temp one. 67 if !*local { 68 if err := gitInit(ctx, absGit, skiaPath); err != nil { 69 td.Fatal(ctx, err) 70 } 71 } 72 73 opts := bazel.BazelOptions{ 74 CachePath: *bazelFlags.CacheDir, 75 } 76 if err := bazel.EnsureBazelRCFile(ctx, opts); err != nil { 77 td.Fatal(ctx, err) 78 } 79 80 if err := bazelRun(ctx, skiaPath, "//:gofmt", append(*bazelFlags.AdditionalArgs, "--", "-s", "-w", ".")...); err != nil { 81 td.Fatal(ctx, err) 82 } 83 84 if err := bazelRun(ctx, skiaPath, "//:errcheck", append(*bazelFlags.AdditionalArgs, "--", "-ignore", ":Close", "go.skia.org/skia/...")...); err != nil { 85 td.Fatal(ctx, err) 86 } 87 88 if err := checkGitDiff(ctx, absGit, skiaPath); err != nil { 89 td.Fatal(ctx, err) 90 } 91 92 if !*local { 93 if err := common.BazelCleanIfLowDiskSpace(ctx, *bazelFlags.CacheDir, skiaPath, "bazelisk"); err != nil { 94 td.Fatal(ctx, err) 95 } 96 } 97} 98 99// bazelRun runs the given Bazel label from the Skia path with any given args using Bazelisk. 100func bazelRun(ctx context.Context, skiaPath, label string, args ...string) error { 101 return td.Do(ctx, td.Props("bazel run "+label), func(ctx context.Context) error { 102 runCmd := &sk_exec.Command{ 103 Name: "bazelisk", 104 Args: append([]string{"run", label}, args...), 105 InheritEnv: true, // Need to make sure bazelisk is on the path 106 Dir: skiaPath, 107 LogStdout: true, 108 LogStderr: true, 109 } 110 _, err := sk_exec.RunCommand(ctx, runCmd) 111 if err != nil { 112 return err 113 } 114 return nil 115 }) 116} 117 118// gitInit creates a temporary git repository with all files in the Skia path. This allows the later 119// git diff call to work properly. This is necessary because our current Swarming setup does not 120// include the .git folder when copying down files. 121func gitInit(ctx context.Context, gitPath, skiaPath string) error { 122 step := fmt.Sprintf("Setting git baseline in %s", skiaPath) 123 err := td.Do(ctx, td.Props(step), func(ctx context.Context) error { 124 initCmd := &sk_exec.Command{ 125 Name: gitPath, 126 Args: []string{"init"}, 127 InheritEnv: false, 128 Dir: skiaPath, 129 LogStdout: true, 130 LogStderr: true, 131 } 132 if _, err := sk_exec.RunCommand(ctx, initCmd); err != nil { 133 return err 134 } 135 addCmd := &sk_exec.Command{ 136 Name: gitPath, 137 Args: []string{"add", "."}, 138 InheritEnv: false, 139 Dir: skiaPath, 140 LogStdout: true, 141 LogStderr: true, 142 } 143 if _, err := sk_exec.RunCommand(ctx, addCmd); err != nil { 144 return err 145 } 146 commitCmd := &sk_exec.Command{ 147 Name: gitPath, 148 Args: []string{"commit", "-m", "baseline commit"}, 149 InheritEnv: false, 150 Dir: skiaPath, 151 LogStdout: true, 152 LogStderr: true, 153 } 154 if _, err := sk_exec.RunCommand(ctx, commitCmd); err != nil { 155 return err 156 } 157 return nil 158 }) 159 return err 160} 161 162// checkGitDiff runs git diff and returns error if the diff is non-empty. 163func checkGitDiff(ctx context.Context, gitPath, skiaPath string) error { 164 step := fmt.Sprintf("git diff %s", skiaPath) 165 return td.Do(ctx, td.Props(step), func(ctx context.Context) error { 166 runCmd := &sk_exec.Command{ 167 Name: gitPath, 168 Args: []string{"diff", "--no-ext-diff"}, 169 InheritEnv: false, 170 Dir: skiaPath, 171 LogStdout: true, 172 LogStderr: true, 173 } 174 rv, err := sk_exec.RunCommand(ctx, runCmd) 175 if err != nil { 176 return err 177 } 178 if strings.TrimSpace(rv) != "" { 179 return fmt.Errorf("Non-empty diff:\n" + rv) 180 } 181 return nil 182 }) 183} 184