github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/base/base.go (about)

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