diff --git a/greplogs/_embed/broken.go b/greplogs/_embed/broken.go new file mode 100644 index 0000000..498ed28 --- /dev/null +++ b/greplogs/_embed/broken.go @@ -0,0 +1,26 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Command broken lists the current Go builders with known issues. +// +// To test this program, cd to its directory and run: +// go mod init +// go get golang.org/x/build/dashboard@HEAD +// go run . +// rm go.mod go.sum +package main + +import ( + "fmt" + + "golang.org/x/build/dashboard" +) + +func main() { + for _, b := range dashboard.Builders { + if len(b.KnownIssues) > 0 { + fmt.Println(b.Name) + } + } +} diff --git a/greplogs/broken.go b/greplogs/broken.go new file mode 100644 index 0000000..15166cd --- /dev/null +++ b/greplogs/broken.go @@ -0,0 +1,110 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + _ "embed" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" +) + +//go:embed _embed/broken.go +var brokenScript []byte + +// listBrokenBuilders returns the builders that are marked +// as broken in golang.org/x/build/dashboard at HEAD. +func listBrokenBuilders() (broken []string, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("identifying broken builders: %v", err) + } + }() + + // Though this be madness, yet there is method in 't. + // + // Our goals here are: + // + // 1. Always use the most up-to-date information about broken builders, even + // if the user hasn't recently updated the greplogs binary. + // + // 2. Avoid the need to massively refactor the builder configuration right + // now. (Currently, the Go builders are configured programmatically in the + // x/build/dashboard package, not in external configuration files.) + // + // 3. Avoid the need to redeploy a production x/build/cmd/coordinator or + // x/build/devapp to pick up changes. (A user triaging test failures might + // not have access to deploy the coordinator, or might not want to disrupt + // running tests or active gomotes by pushing it.) + // + // Goals (2) and (3) imply that we must use x/build/dashboard, not fetch the + // list from build.golang.org or dev.golang.org. Since that is a Go package, + // we must run it as a Go program in order to evaluate it. + // + // Goal (1) implies that we must use x/build at HEAD, not (say) at whatever + // version of x/build this command was built with. We could perhaps relax that + // constraint if we move greplogs itself into x/build and consistently triage + // using 'go run golang.org/x/build/cmd/greplogs@HEAD' instead of an installed + // 'greplogs'. + + if os.Getenv("GO111MODULE") == "off" { + return nil, errors.New("operation requires GO111MODULE=on or auto") + } + + modDir, err := os.MkdirTemp("", "greplogs") + if err != nil { + return nil, err + } + defer func() { + removeErr := os.RemoveAll(modDir) + if err == nil { + err = removeErr + } + }() + + runCommand := func(name string, args ...string) ([]byte, error) { + cmd := exec.Command(name, args...) + cmd.Dir = modDir + cmd.Env = append(os.Environ(), + "PWD="+modDir, // match cmd.Dir + "GOPRIVATE=golang.org/x/build", // avoid proxy cache; see https://go.dev/issue/38065 + ) + cmd.Stderr = new(strings.Builder) + + out, err := cmd.Output() + if err != nil { + return out, fmt.Errorf("%s: %w\nstderr:\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr) + } + return out, nil + } + + _, err = runCommand("go", "mod", "init", "github.com/aclements/go-misc/greplogs/_embed") + if err != nil { + return nil, err + } + + _, err = runCommand("go", "get", "golang.org/x/build/dashboard@HEAD") + if err != nil { + return nil, err + } + + err = os.WriteFile(filepath.Join(modDir, "broken.go"), brokenScript, 0644) + if err != nil { + return nil, err + } + + out, err := runCommand("go", "run", "broken.go") + if err != nil { + return nil, err + } + + broken = strings.Split(strings.TrimSpace(string(out)), "\n") + sort.Strings(broken) + return broken, nil +} diff --git a/greplogs/main.go b/greplogs/main.go index d7de266..fe96789 100644 --- a/greplogs/main.go +++ b/greplogs/main.go @@ -64,6 +64,8 @@ const ( colorMatch = colorBold | colorFgRed ) +var brokenBuilders []string + func main() { // XXX What I want right now is just to point it at a bunch of // logs and have it extract the failures. @@ -91,6 +93,28 @@ func main() { os.Exit(2) } + if *flagTriage { + *flagFilesOnly = true + if len(failRegexps) == 0 && len(fileRegexps) == 0 { + failRegexps.Set(".") + } + + if before.Time.IsZero() { + year, month, day := time.Now().UTC().Date() + before = timeFlag{Time: time.Date(year, month, day, 0, 0, 0, 0, time.UTC)} + } + + var err error + brokenBuilders, err = listBrokenBuilders() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + if len(brokenBuilders) > 0 { + fmt.Fprintf(os.Stderr, "omitting builders with known issues:\n\t%s\n\n", strings.Join(brokenBuilders, "\n\t")) + } + } + status := 1 defer func() { os.Exit(status) }() @@ -169,6 +193,11 @@ var pathDateRE = regexp.MustCompile(`^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})-([0- func process(path, nicePath string) (found bool, err error) { // If this is from the dashboard, filter by builder and date and get the builder URL. builder := filepath.Base(nicePath) + for _, b := range brokenBuilders { + if builder == b { + return false, nil + } + } if omit.AnyMatchString(builder) { return false, nil }