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  
    21  	"cmd/go/internal/cfg"
    22  	"cmd/go/internal/str"
    23  )
    24  
    25  // A Command is an implementation of a go command
    26  // like go build or go fix.
    27  type Command struct {
    28  	// Run runs the command.
    29  	// The args are the arguments after the command name.
    30  	Run func(ctx context.Context, cmd *Command, args []string)
    31  
    32  	// UsageLine is the one-line usage message.
    33  	// The words between "go" and the first flag or argument in the line are taken to be the command name.
    34  	UsageLine string
    35  
    36  	// Short is the short description shown in the 'go help' output.
    37  	Short string
    38  
    39  	// Long is the long message shown in the 'go help <this-command>' output.
    40  	Long string
    41  
    42  	// Flag is a set of flags specific to this command.
    43  	Flag flag.FlagSet
    44  
    45  	// CustomFlags indicates that the command will do its own
    46  	// flag parsing.
    47  	CustomFlags bool
    48  
    49  	// Commands lists the available commands and help topics.
    50  	// The order here is the order in which they are printed by 'go help'.
    51  	// Note that subcommands are in general best avoided.
    52  	Commands []*Command
    53  }
    54  
    55  var Go = &Command{
    56  	UsageLine: "go",
    57  	Long:      `Go is a tool for managing Go source code.`,
    58  	// Commands initialized in package main
    59  }
    60  
    61  // Lookup returns the subcommand with the given name, if any.
    62  // Otherwise it returns nil.
    63  //
    64  // Lookup ignores subcommands that have len(c.Commands) == 0 and c.Run == nil.
    65  // Such subcommands are only for use as arguments to "help".
    66  func (c *Command) Lookup(name string) *Command {
    67  	for _, sub := range c.Commands {
    68  		if sub.Name() == name && (len(c.Commands) > 0 || c.Runnable()) {
    69  			return sub
    70  		}
    71  	}
    72  	return nil
    73  }
    74  
    75  // hasFlag reports whether a command or any of its subcommands contain the given
    76  // flag.
    77  func hasFlag(c *Command, name string) bool {
    78  	if f := c.Flag.Lookup(name); f != nil {
    79  		return true
    80  	}
    81  	for _, sub := range c.Commands {
    82  		if hasFlag(sub, name) {
    83  			return true
    84  		}
    85  	}
    86  	return false
    87  }
    88  
    89  // LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument,
    90  func (c *Command) LongName() string {
    91  	name := c.UsageLine
    92  	if i := strings.Index(name, " ["); i >= 0 {
    93  		name = name[:i]
    94  	}
    95  	if name == "go" {
    96  		return ""
    97  	}
    98  	return strings.TrimPrefix(name, "go ")
    99  }
   100  
   101  // Name returns the command's short name: the last word in the usage line before a flag or argument.
   102  func (c *Command) Name() string {
   103  	name := c.LongName()
   104  	if i := strings.LastIndex(name, " "); i >= 0 {
   105  		name = name[i+1:]
   106  	}
   107  	return name
   108  }
   109  
   110  func (c *Command) Usage() {
   111  	fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
   112  	fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", c.LongName())
   113  	SetExitStatus(2)
   114  	Exit()
   115  }
   116  
   117  // Runnable reports whether the command can be run; otherwise
   118  // it is a documentation pseudo-command such as importpath.
   119  func (c *Command) Runnable() bool {
   120  	return c.Run != nil
   121  }
   122  
   123  var atExitFuncs []func()
   124  
   125  func AtExit(f func()) {
   126  	atExitFuncs = append(atExitFuncs, f)
   127  }
   128  
   129  func Exit() {
   130  	for _, f := range atExitFuncs {
   131  		f()
   132  	}
   133  	os.Exit(exitStatus)
   134  }
   135  
   136  func Fatalf(format string, args ...any) {
   137  	Errorf(format, args...)
   138  	Exit()
   139  }
   140  
   141  func Errorf(format string, args ...any) {
   142  	log.Printf(format, args...)
   143  	SetExitStatus(1)
   144  }
   145  
   146  func ExitIfErrors() {
   147  	if exitStatus != 0 {
   148  		Exit()
   149  	}
   150  }
   151  
   152  func Error(err error) {
   153  	// We use errors.Join to return multiple errors from various routines.
   154  	// If we receive multiple errors joined with a basic errors.Join,
   155  	// handle each one separately so that they all have the leading "go: " prefix.
   156  	// A plain interface check is not good enough because there might be
   157  	// other kinds of structured errors that are logically one unit and that
   158  	// add other context: only handling the wrapped errors would lose
   159  	// that context.
   160  	if err != nil && reflect.TypeOf(err).String() == "*errors.joinError" {
   161  		for _, e := range err.(interface{ Unwrap() []error }).Unwrap() {
   162  			Error(e)
   163  		}
   164  		return
   165  	}
   166  	Errorf("go: %v", err)
   167  }
   168  
   169  func Fatal(err error) {
   170  	Error(err)
   171  	Exit()
   172  }
   173  
   174  var exitStatus = 0
   175  var exitMu sync.Mutex
   176  
   177  func SetExitStatus(n int) {
   178  	exitMu.Lock()
   179  	if exitStatus < n {
   180  		exitStatus = n
   181  	}
   182  	exitMu.Unlock()
   183  }
   184  
   185  func GetExitStatus() int {
   186  	return exitStatus
   187  }
   188  
   189  // Run runs the command, with stdout and stderr
   190  // connected to the go command's own stdout and stderr.
   191  // If the command fails, Run reports the error using Errorf.
   192  func Run(cmdargs ...any) {
   193  	cmdline := str.StringList(cmdargs...)
   194  	if cfg.BuildN || cfg.BuildX {
   195  		fmt.Printf("%s\n", strings.Join(cmdline, " "))
   196  		if cfg.BuildN {
   197  			return
   198  		}
   199  	}
   200  
   201  	cmd := exec.Command(cmdline[0], cmdline[1:]...)
   202  	cmd.Stdout = os.Stdout
   203  	cmd.Stderr = os.Stderr
   204  	if err := cmd.Run(); err != nil {
   205  		Errorf("%v", err)
   206  	}
   207  }
   208  
   209  // RunStdin is like run but connects Stdin.
   210  func RunStdin(cmdline []string) {
   211  	cmd := exec.Command(cmdline[0], cmdline[1:]...)
   212  	cmd.Stdin = os.Stdin
   213  	cmd.Stdout = os.Stdout
   214  	cmd.Stderr = os.Stderr
   215  	env := slices.Clip(cfg.OrigEnv)
   216  	env = AppendPATH(env)
   217  	cmd.Env = env
   218  	StartSigHandlers()
   219  	if err := cmd.Run(); err != nil {
   220  		Errorf("%v", err)
   221  	}
   222  }
   223  
   224  // Usage is the usage-reporting function, filled in by package main
   225  // but here for reference by other packages.
   226  var Usage func()
   227  

View as plain text