Source file src/cmd/go/internal/base/base.go

     1  // Copyright 2017 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 base defines shared basic pieces of the go command,
     6  // in particular logging and the Command structure.
     7  package base
     8  
     9  import (
    10  	"context"
    11  	"flag"
    12  	"fmt"
    13  	"log"
    14  	"os"
    15  	"os/exec"
    16  	"reflect"
    17  	"slices"
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  
    22  	"cmd/go/internal/cfg"
    23  	"cmd/go/internal/str"
    24  )
    25  
    26  // A Command is an implementation of a go command
    27  // like go build or go fix.
    28  type Command struct {
    29  	// Run runs the command.
    30  	// The args are the arguments after the command name.
    31  	Run func(ctx context.Context, cmd *Command, args []string)
    32  
    33  	// UsageLine is the one-line usage message.
    34  	// The words between "go" and the first flag or argument in the line are taken to be the command name.
    35  	UsageLine string
    36  
    37  	// Short is the short description shown in the 'go help' output.
    38  	Short string
    39  
    40  	// Long is the long message shown in the 'go help <this-command>' output.
    41  	Long string
    42  
    43  	// Flag is a set of flags specific to this command.
    44  	Flag flag.FlagSet
    45  
    46  	// CustomFlags indicates that the command will do its own
    47  	// flag parsing.
    48  	CustomFlags bool
    49  
    50  	// Commands lists the available commands and help topics.
    51  	// The order here is the order in which they are printed by 'go help'.
    52  	// Note that subcommands are in general best avoided.
    53  	Commands []*Command
    54  }
    55  
    56  var Go = &Command{
    57  	UsageLine: "go",
    58  	Long:      `Go is a tool for managing Go source code.`,
    59  	// Commands initialized in package main
    60  }
    61  
    62  // Lookup returns the subcommand with the given name, if any.
    63  // Otherwise it returns nil.
    64  //
    65  // Lookup ignores any subcommand `sub` that has len(sub.Commands) == 0 and sub.Run == nil.
    66  // Such subcommands are only for use as arguments to "help".
    67  func (c *Command) Lookup(name string) *Command {
    68  	for _, sub := range c.Commands {
    69  		if sub.Name() == name && (len(sub.Commands) > 0 || sub.Runnable()) {
    70  			return sub
    71  		}
    72  	}
    73  	return nil
    74  }
    75  
    76  // hasFlag reports whether a command or any of its subcommands contain the given
    77  // flag.
    78  func hasFlag(c *Command, name string) bool {
    79  	if f := c.Flag.Lookup(name); f != nil {
    80  		return true
    81  	}
    82  	for _, sub := range c.Commands {
    83  		if hasFlag(sub, name) {
    84  			return true
    85  		}
    86  	}
    87  	return false
    88  }
    89  
    90  // LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument,
    91  func (c *Command) LongName() string {
    92  	name := c.UsageLine
    93  	if i := strings.Index(name, " ["); i >= 0 {
    94  		name = name[:i]
    95  	}
    96  	if name == "go" {
    97  		return ""
    98  	}
    99  	return strings.TrimPrefix(name, "go ")
   100  }
   101  
   102  // Name returns the command's short name: the last word in the usage line before a flag or argument.
   103  func (c *Command) Name() string {
   104  	name := c.LongName()
   105  	if i := strings.LastIndex(name, " "); i >= 0 {
   106  		name = name[i+1:]
   107  	}
   108  	return name
   109  }
   110  
   111  func (c *Command) Usage() {
   112  	fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
   113  	fmt.Fprintf(os.Stderr, "\nFlags:\n")
   114  	c.Flag.SetOutput(os.Stderr)
   115  	c.Flag.PrintDefaults()
   116  	fmt.Fprintf(os.Stderr, "\nRun 'go help %s' for details.\n", c.LongName())
   117  	SetExitStatus(2)
   118  	Exit()
   119  }
   120  
   121  // Runnable reports whether the command can be run; otherwise
   122  // it is a documentation pseudo-command such as importpath.
   123  func (c *Command) Runnable() bool {
   124  	return c.Run != nil
   125  }
   126  
   127  var atExitFuncs []func()
   128  
   129  func AtExit(f func()) {
   130  	atExitFuncs = append(atExitFuncs, f)
   131  }
   132  
   133  func Exit() {
   134  	for _, f := range atExitFuncs {
   135  		f()
   136  	}
   137  	os.Exit(exitStatus)
   138  }
   139  
   140  func Fatalf(format string, args ...any) {
   141  	Errorf(format, args...)
   142  	Exit()
   143  }
   144  
   145  func Errorf(format string, args ...any) {
   146  	log.Printf(format, args...)
   147  	SetExitStatus(1)
   148  }
   149  
   150  func ExitIfErrors() {
   151  	if exitStatus != 0 {
   152  		Exit()
   153  	}
   154  }
   155  
   156  func Error(err error) {
   157  	// We use errors.Join to return multiple errors from various routines.
   158  	// If we receive multiple errors joined with a basic errors.Join,
   159  	// handle each one separately so that they all have the leading "go: " prefix.
   160  	// A plain interface check is not good enough because there might be
   161  	// other kinds of structured errors that are logically one unit and that
   162  	// add other context: only handling the wrapped errors would lose
   163  	// that context.
   164  	if err != nil && reflect.TypeOf(err).String() == "*errors.joinError" {
   165  		for _, e := range err.(interface{ Unwrap() []error }).Unwrap() {
   166  			Error(e)
   167  		}
   168  		return
   169  	}
   170  	Errorf("go: %v", err)
   171  }
   172  
   173  func Fatal(err error) {
   174  	Error(err)
   175  	Exit()
   176  }
   177  
   178  var (
   179  	exitStatus = 0
   180  	exitMu     sync.Mutex
   181  )
   182  
   183  // SetExitStatus sets exit status to n if
   184  // n is higher than the current exit status.
   185  func SetExitStatus(n int) {
   186  	exitMu.Lock()
   187  	if exitStatus < n {
   188  		exitStatus = n
   189  	}
   190  	exitMu.Unlock()
   191  }
   192  
   193  // GetExitStatus reports the current exit status.
   194  func GetExitStatus() int {
   195  	return exitStatus
   196  }
   197  
   198  // Run runs the command, with stdout and stderr
   199  // connected to the go command's own stdout and stderr.
   200  // If the command fails, Run reports the error using Errorf.
   201  func Run(cmdargs ...any) {
   202  	if err := RunErr(cmdargs...); err != nil {
   203  		Errorf("%v", err)
   204  	}
   205  }
   206  
   207  // RunErr runs the command, with stdout and stderr
   208  // connected to the go command's own stdout and stderr.
   209  // If the command fails, RunErr returns the error, which
   210  // may be an *exec.ExitError.
   211  func RunErr(cmdargs ...any) error {
   212  	cmdline := str.StringList(cmdargs...)
   213  	if cfg.BuildN || cfg.BuildX {
   214  		fmt.Printf("%s\n", strings.Join(cmdline, " "))
   215  		if cfg.BuildN {
   216  			return nil
   217  		}
   218  	}
   219  
   220  	cmd := exec.Command(cmdline[0], cmdline[1:]...)
   221  	cmd.Stdout = os.Stdout
   222  	cmd.Stderr = os.Stderr
   223  	return cmd.Run()
   224  }
   225  
   226  // RunStdin is like run but connects Stdin. It retries if it encounters an ETXTBSY.
   227  func RunStdin(cmdline []string) {
   228  	env := slices.Clip(cfg.OrigEnv)
   229  	env = AppendPATH(env)
   230  	for try := range 3 {
   231  		cmd := exec.Command(cmdline[0], cmdline[1:]...)
   232  		cmd.Stdin = os.Stdin
   233  		cmd.Stdout = os.Stdout
   234  		cmd.Stderr = os.Stderr
   235  		cmd.Env = env
   236  		StartSigHandlers()
   237  		err := cmd.Run()
   238  		if err == nil {
   239  			break // success
   240  		}
   241  
   242  		if !IsETXTBSY(err) {
   243  			Errorf("%v", err)
   244  			break // failure
   245  		}
   246  
   247  		// The error was an ETXTBSY. Sleep and try again. It's possible that
   248  		// another go command instance was racing against us to write the executable
   249  		// to the executable cache. In that case it may still have the file open, and
   250  		// we may get an ETXTBSY. That should resolve once that process closes the file
   251  		// so attempt a couple more times. See the discussion in #22220 and also
   252  		// (*runTestActor).Act in cmd/go/internal/test, which does something similar.
   253  		time.Sleep(100 * time.Millisecond << uint(try))
   254  	}
   255  }
   256  
   257  // Usage is the usage-reporting function, filled in by package main
   258  // but here for reference by other packages.
   259  var Usage func()
   260  

View as plain text