golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/greplogs/broken.go (about)

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	_ "embed"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"sort"
    15  	"strings"
    16  )
    17  
    18  //go:embed _embed/broken.go
    19  var brokenScript []byte
    20  
    21  // listBrokenBuilders returns the builders that are marked
    22  // as broken in golang.org/x/build/dashboard at HEAD.
    23  func listBrokenBuilders() (broken []string, err error) {
    24  	defer func() {
    25  		if err != nil {
    26  			err = fmt.Errorf("identifying broken builders: %v", err)
    27  		}
    28  	}()
    29  
    30  	// Though this be madness, yet there is method in 't.
    31  	//
    32  	// Our goals here are:
    33  	//
    34  	// 	1. Always use the most up-to-date information about broken builders, even
    35  	// 	   if the user hasn't recently updated the greplogs binary.
    36  	//
    37  	// 	2. Avoid the need to massively refactor the builder configuration right
    38  	// 	   now. (Currently, the Go builders are configured programmatically in the
    39  	// 	   x/build/dashboard package, not in external configuration files.)
    40  	//
    41  	// 	3. Avoid the need to redeploy a production x/build/cmd/coordinator or
    42  	// 	   x/build/devapp to pick up changes. (A user triaging test failures might
    43  	// 	   not have access to deploy the coordinator, or might not want to disrupt
    44  	// 	   running tests or active gomotes by pushing it.)
    45  	//
    46  	// Goals (2) and (3) imply that we must use x/build/dashboard, not fetch the
    47  	// list from build.golang.org or dev.golang.org. Since that is a Go package,
    48  	// we must run it as a Go program in order to evaluate it.
    49  	//
    50  	// Goal (1) implies that we must use x/build at HEAD, not (say) at whatever
    51  	// version of x/build this command was built with. We could perhaps relax that
    52  	// constraint if we move greplogs itself into x/build and consistently triage
    53  	// using 'go run golang.org/x/build/cmd/greplogs@HEAD' instead of an installed
    54  	// 'greplogs'.
    55  
    56  	if os.Getenv("GO111MODULE") == "off" {
    57  		return nil, errors.New("operation requires GO111MODULE=on or auto")
    58  	}
    59  
    60  	modDir, err := os.MkdirTemp("", "greplogs")
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	defer func() {
    65  		removeErr := os.RemoveAll(modDir)
    66  		if err == nil {
    67  			err = removeErr
    68  		}
    69  	}()
    70  
    71  	runCommand := func(name string, args ...string) ([]byte, error) {
    72  		cmd := exec.Command(name, args...)
    73  		cmd.Dir = modDir
    74  		cmd.Env = append(os.Environ(),
    75  			"PWD="+modDir,                  // match cmd.Dir
    76  			"GOPRIVATE=golang.org/x/build", // avoid proxy cache; see https://go.dev/issue/38065
    77  		)
    78  		cmd.Stderr = new(strings.Builder)
    79  
    80  		out, err := cmd.Output()
    81  		if err != nil {
    82  			return out, fmt.Errorf("%s: %w\nstderr:\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
    83  		}
    84  		return out, nil
    85  	}
    86  
    87  	_, err = runCommand("go", "mod", "init", "github.com/aclements/go-misc/greplogs/_embed")
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	_, err = runCommand("go", "get", "golang.org/x/build/dashboard@HEAD")
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	err = os.WriteFile(filepath.Join(modDir, "broken.go"), brokenScript, 0644)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	out, err := runCommand("go", "run", "broken.go")
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	broken = strings.Split(strings.TrimSpace(string(out)), "\n")
   108  	sort.Strings(broken)
   109  	return broken, nil
   110  }