diff --git a/dev/sg/ci/subcommands.go b/dev/sg/ci/subcommands.go index 0b9b6481335..6db2c91b02d 100644 --- a/dev/sg/ci/subcommands.go +++ b/dev/sg/ci/subcommands.go @@ -654,7 +654,7 @@ var docsCommand = &cli.Command{ Usage: "Render reference documentation for build pipeline types", Action: func(ctx *cli.Context) error { cmd := exec.Command("go", "run", "./dev/ci/gen-pipeline.go", "-docs") - out, err := run.InRoot(cmd) + out, err := run.InRoot(cmd, run.InRootArgs{}) if err != nil { return err } diff --git a/dev/sg/generates.go b/dev/sg/generates.go index 0971e54c83b..341daf90e5f 100644 --- a/dev/sg/generates.go +++ b/dev/sg/generates.go @@ -56,7 +56,8 @@ var allGenerateTargets = generateTargets{ } func generateBazelRunner(ctx context.Context, args []string) *generate.Report { - return generate.RunScript("bazel run //dev:write_all_generated")(ctx, args) + runnerFunc := generate.RunScript("bazel run //dev:write_all_generated", true) + return runnerFunc(ctx, args) } func generateGoRunner(ctx context.Context, args []string) *generate.Report { diff --git a/dev/sg/internal/generate/generate.go b/dev/sg/internal/generate/generate.go index 4d13faa8ee1..d2785661855 100644 --- a/dev/sg/internal/generate/generate.go +++ b/dev/sg/internal/generate/generate.go @@ -34,10 +34,12 @@ type Target struct { // RunScript runs the given script from the root of sourcegraph/sourcegraph. // If arguments are to be to passed down the script, they should be incorporated // in the script variable. -func RunScript(command string) Runner { +func RunScript(command string, extractBazelError bool) Runner { return func(ctx context.Context, args []string) *Report { start := time.Now() - out, err := run.BashInRoot(ctx, command, nil) + out, err := run.BashInRoot(ctx, command, run.BashInRootArgs{ + ExtractBazelError: extractBazelError, + }) return &Report{ Output: out, Err: err, diff --git a/dev/sg/internal/migration/squash.go b/dev/sg/internal/migration/squash.go index 550e5a59bf7..9aa7f62f9ab 100644 --- a/dev/sg/internal/migration/squash.go +++ b/dev/sg/internal/migration/squash.go @@ -327,13 +327,13 @@ func setupLocalDatabase(databaseName string) (_ func(error) error, err error) { createLocalDatabase := func() error { cmd := exec.Command("createdb", databaseName) - _, err := run.InRoot(cmd) + _, err := run.InRoot(cmd, run.InRootArgs{}) return err } dropLocalDatabase := func() error { cmd := exec.Command("dropdb", databaseName) - _, err := run.InRoot(cmd) + _, err := run.InRoot(cmd, run.InRootArgs{}) return err } diff --git a/dev/sg/internal/run/BUILD.bazel b/dev/sg/internal/run/BUILD.bazel index 5c1e00a909c..a96ab47ea4e 100644 --- a/dev/sg/internal/run/BUILD.bazel +++ b/dev/sg/internal/run/BUILD.bazel @@ -26,6 +26,7 @@ go_library( "//dev/sg/interrupt", "//dev/sg/root", "//internal/download", + "//internal/lazyregexp", "//lib/errors", "//lib/output", "//lib/process", diff --git a/dev/sg/internal/run/command.go b/dev/sg/internal/run/command.go index cbc2076fae6..6657ff2217e 100644 --- a/dev/sg/internal/run/command.go +++ b/dev/sg/internal/run/command.go @@ -108,9 +108,11 @@ func (cmd Command) hasBashInstaller() bool { } func (cmd Command) bashInstall(ctx context.Context, parentEnv map[string]string) error { - output, err := BashInRoot(ctx, cmd.Install, makeEnv(parentEnv, cmd.Config.Env)) + out, err := BashInRoot(ctx, cmd.Install, BashInRootArgs{ + Env: makeEnv(parentEnv, cmd.Config.Env), + }) if err != nil { - return installErr{cmdName: cmd.Config.Name, output: output, originalErr: err} + return installErr{cmdName: cmd.Config.Name, output: out, originalErr: err} } return nil } diff --git a/dev/sg/internal/run/helpers.go b/dev/sg/internal/run/helpers.go index 659be119c99..0c59216df14 100644 --- a/dev/sg/internal/run/helpers.go +++ b/dev/sg/internal/run/helpers.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/sourcegraph/sourcegraph/dev/sg/root" + "github.com/sourcegraph/sourcegraph/internal/lazyregexp" ) func GitCmd(args ...string) (string, error) { @@ -19,11 +20,11 @@ func GitCmd(args ...string) (string, error) { // And also not any other, because they can mess up output, change defaults, .. which can do unexpected things. "GIT_CONFIG=/dev/null") - return InRoot(cmd) + return InRoot(cmd, InRootArgs{}) } func DockerCmd(args ...string) (string, error) { - return InRoot(exec.Command("docker", args...)) + return InRoot(exec.Command("docker", args...), InRootArgs{}) } type errorWithoutOutputer interface { @@ -37,7 +38,8 @@ type cmdInRootErr struct { } func (e cmdInRootErr) Error() string { - return fmt.Sprintf("'%s' failed: err = %q, output = %q", strings.Join(e.args, " "), e.err.Error(), e.output) + return fmt.Sprintf(`'%s' failed: err = "%s", output = "%s"`, strings.Join(e.args, " "), e.err.Error(), e.output) + } func (e cmdInRootErr) ErrorWithoutOutput() string { @@ -46,7 +48,11 @@ func (e cmdInRootErr) ErrorWithoutOutput() string { func (e cmdInRootErr) Unwrap() error { return e.err } -func InRoot(cmd *exec.Cmd) (string, error) { +type InRootArgs struct { + ExtractBazelError bool +} + +func InRoot(cmd *exec.Cmd, args InRootArgs) (string, error) { repoRoot, err := root.RepositoryRoot() if err != nil { return "", err @@ -55,12 +61,33 @@ func InRoot(cmd *exec.Cmd) (string, error) { cmd.Dir = repoRoot out, err := cmd.CombinedOutput() if err != nil { - return string(out), cmdInRootErr{err: err, args: cmd.Args, output: string(out)} + output := string(out) + if args.ExtractBazelError { + // this is still experimental and currently only works for bazel errors + output = bazelErrorExtractor(out) + } + return string(out), cmdInRootErr{err: err, args: cmd.Args, output: output} } return string(out), nil } +// `(?m)` enables multiline mode, `^` matches the start of each line +var errorRegex = lazyregexp.New(`(?m)^ERROR:.*$`) + +func bazelErrorExtractor(input []byte) string { + // Find all matches + matches := errorRegex.FindAll(input, -1) + + // Convert [][]byte to string + var errorLines string + for _, match := range matches { + errorLines += string(match) + "\n" + } + + return errorLines +} + func SplitOutputInRoot(cmd *exec.Cmd, stdout, stderr io.Writer) error { cmd.Stdout = stdout cmd.Stderr = stderr @@ -72,10 +99,17 @@ func SplitOutputInRoot(cmd *exec.Cmd, stdout, stderr io.Writer) error { return cmd.Run() } -func BashInRoot(ctx context.Context, cmd string, env []string) (string, error) { +type BashInRootArgs struct { + Env []string + ExtractBazelError bool +} + +func BashInRoot(ctx context.Context, cmd string, args BashInRootArgs) (string, error) { c := exec.CommandContext(ctx, "bash", "-c", cmd) - c.Env = env - return InRoot(c) + c.Env = args.Env + return InRoot(c, InRootArgs{ + ExtractBazelError: args.ExtractBazelError, + }) } func TrimResult(s string, err error) (string, error) { diff --git a/dev/sg/sg_doctor.go b/dev/sg/sg_doctor.go index 29638c9fd40..6708b5667c1 100644 --- a/dev/sg/sg_doctor.go +++ b/dev/sg/sg_doctor.go @@ -103,7 +103,9 @@ func (d *diagnosticRunner) Run(ctx context.Context) DiagnosticReport { for group, diagnostics := range d.diagnostics.Diagnostic { d.reporter.WriteLine(output.Emojif("💊", "Running %s diagnostics", group)) for _, diagnostic := range diagnostics { - out, err := run.BashInRoot(ctx, diagnostic.Cmd, env) + out, err := run.BashInRoot(ctx, diagnostic.Cmd, run.BashInRootArgs{ + Env: env, + }) diag := diagnostic report.Add(group, &DiagnosticResult{ &diag, diff --git a/dev/sg/sg_version.go b/dev/sg/sg_version.go index 787eca75a25..a01240eab04 100644 --- a/dev/sg/sg_version.go +++ b/dev/sg/sg_version.go @@ -93,7 +93,7 @@ func changelogExec(ctx *cli.Context) error { gitLog := exec.Command("git", append(logArgs, "--", "./dev/sg")...) gitLog.Env = os.Environ() - out, err := run.InRoot(gitLog) + out, err := run.InRoot(gitLog, run.InRootArgs{}) if err != nil { return err } diff --git a/internal/lazyregexp/lazyre.go b/internal/lazyregexp/lazyre.go index dc55d78f1aa..370a5078f75 100644 --- a/internal/lazyregexp/lazyre.go +++ b/internal/lazyregexp/lazyre.go @@ -85,6 +85,10 @@ func (r *Regexp) FindAllIndex(b []byte, n int) [][]int { return r.Re().FindAllIndex(b, n) } +func (r *Regexp) FindAll(b []byte, n int) [][]byte { + return r.Re().FindAll(b, n) +} + func (r *Regexp) Match(b []byte) bool { return r.Re().Match(b) }