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, "Run 'go help %s' for details.\n", c.LongName())
   114  	SetExitStatus(2)
   115  	Exit()
   116  }
   117  
   118  // Runnable reports whether the command can be run; otherwise
   119  // it is a documentation pseudo-command such as importpath.
   120  func (c *Command) Runnable() bool {
   121  	return c.Run != nil
   122  }
   123  
   124  var atExitFuncs []func()
   125  
   126  func AtExit(f func()) {
   127  	atExitFuncs = append(atExitFuncs, f)
   128  }
   129  
   130  func Exit() {
   131  	for _, f := range atExitFuncs {
   132  		f()
   133  	}
   134  	os.Exit(exitStatus)
   135  }
   136  
   137  func Fatalf(format string, args ...any) {
   138  	Errorf(format, args...)
   139  	Exit()
   140  }
   141  
   142  func Errorf(format string, args ...any) {
   143  	log.Printf(format, args...)
   144  	SetExitStatus(1)
   145  }
   146  
   147  func ExitIfErrors() {
   148  	if exitStatus != 0 {
   149  		Exit()
   150  	}
   151  }
   152  
   153  func Error(err error) {
   154  	// We use errors.Join to return multiple errors from various routines.
   155  	// If we receive multiple errors joined with a basic errors.Join,
   156  	// handle each one separately so that they all have the leading "go: " prefix.
   157  	// A plain interface check is not good enough because there might be
   158  	// other kinds of structured errors that are logically one unit and that
   159  	// add other context: only handling the wrapped errors would lose
   160  	// that context.
   161  	if err != nil && reflect.TypeOf(err).String() == "*errors.joinError" {
   162  		for _, e := range err.(interface{ Unwrap() []error }).Unwrap() {
   163  			Error(e)
   164  		}
   165  		return
   166  	}
   167  	Errorf("go: %v", err)
   168  }
   169  
   170  func Fatal(err error) {
   171  	Error(err)
   172  	Exit()
   173  }
   174  
   175  var exitStatus = 0
   176  var exitMu sync.Mutex
   177  
   178  // SetExitStatus sets exit status to n if
   179  // n is higher than the current exit status.
   180  func SetExitStatus(n int) {
   181  	exitMu.Lock()
   182  	if exitStatus < n {
   183  		exitStatus = n
   184  	}
   185  	exitMu.Unlock()
   186  }
   187  
   188  // GetExitStatus reports the current exit status.
   189  func GetExitStatus() int {
   190  	return exitStatus
   191  }
   192  
   193  // Run runs the command, with stdout and stderr
   194  // connected to the go command's own stdout and stderr.
   195  // If the command fails, Run reports the error using Errorf.
   196  func Run(cmdargs ...any) {
   197  	if err := RunErr(cmdargs...); err != nil {
   198  		Errorf("%v", err)
   199  	}
   200  }
   201  
   202  // Run runs the command, with stdout and stderr
   203  // connected to the go command's own stdout and stderr.
   204  // If the command fails, RunErr returns the error, which
   205  // may be an *exec.ExitError.
   206  func RunErr(cmdargs ...any) error {
   207  	cmdline := str.StringList(cmdargs...)
   208  	if cfg.BuildN || cfg.BuildX {
   209  		fmt.Printf("%s\n", strings.Join(cmdline, " "))
   210  		if cfg.BuildN {
   211  			return nil
   212  		}
   213  	}
   214  
   215  	cmd := exec.Command(cmdline[0], cmdline[1:]...)
   216  	cmd.Stdout = os.Stdout
   217  	cmd.Stderr = os.Stderr
   218  	return cmd.Run()
   219  }
   220  
   221  // RunStdin is like run but connects Stdin. It retries if it encounters an ETXTBSY.
   222  func RunStdin(cmdline []string) {
   223  	env := slices.Clip(cfg.OrigEnv)
   224  	env = AppendPATH(env)
   225  	for try := range 3 {
   226  		cmd := exec.Command(cmdline[0], cmdline[1:]...)
   227  		cmd.Stdin = os.Stdin
   228  		cmd.Stdout = os.Stdout
   229  		cmd.Stderr = os.Stderr
   230  		cmd.Env = env
   231  		StartSigHandlers()
   232  		err := cmd.Run()
   233  		if err == nil {
   234  			break // success
   235  		}
   236  
   237  		if !IsETXTBSY(err) {
   238  			Errorf("%v", err)
   239  			break // failure
   240  		}
   241  
   242  		// The error was an ETXTBSY. Sleep and try again. It's possible that
   243  		// another go command instance was racing against us to write the executable
   244  		// to the executable cache. In that case it may still have the file open, and
   245  		// we may get an ETXTBSY. That should resolve once that process closes the file
   246  		// so attempt a couple more times. See the discussion in #22220 and also
   247  		// (*runTestActor).Act in cmd/go/internal/test, which does something similar.
   248  		time.Sleep(100 * time.Millisecond << uint(try))
   249  	}
   250  }
   251  
   252  // Usage is the usage-reporting function, filled in by package main
   253  // but here for reference by other packages.
   254  var Usage func()
   255  

View as plain text