summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/base
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/internal/base')
-rw-r--r--src/cmd/go/internal/base/base.go223
-rw-r--r--src/cmd/go/internal/base/env.go46
-rw-r--r--src/cmd/go/internal/base/flag.go85
-rw-r--r--src/cmd/go/internal/base/goflags.go162
-rw-r--r--src/cmd/go/internal/base/limit.go84
-rw-r--r--src/cmd/go/internal/base/path.go79
-rw-r--r--src/cmd/go/internal/base/signal.go31
-rw-r--r--src/cmd/go/internal/base/signal_notunix.go17
-rw-r--r--src/cmd/go/internal/base/signal_unix.go18
-rw-r--r--src/cmd/go/internal/base/tool.go41
10 files changed, 786 insertions, 0 deletions
diff --git a/src/cmd/go/internal/base/base.go b/src/cmd/go/internal/base/base.go
new file mode 100644
index 0000000..2171d13
--- /dev/null
+++ b/src/cmd/go/internal/base/base.go
@@ -0,0 +1,223 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package base defines shared basic pieces of the go command,
+// in particular logging and the Command structure.
+package base
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "reflect"
+ "strings"
+ "sync"
+
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/str"
+)
+
+// A Command is an implementation of a go command
+// like go build or go fix.
+type Command struct {
+ // Run runs the command.
+ // The args are the arguments after the command name.
+ Run func(ctx context.Context, cmd *Command, args []string)
+
+ // UsageLine is the one-line usage message.
+ // The words between "go" and the first flag or argument in the line are taken to be the command name.
+ UsageLine string
+
+ // Short is the short description shown in the 'go help' output.
+ Short string
+
+ // Long is the long message shown in the 'go help <this-command>' output.
+ Long string
+
+ // Flag is a set of flags specific to this command.
+ Flag flag.FlagSet
+
+ // CustomFlags indicates that the command will do its own
+ // flag parsing.
+ CustomFlags bool
+
+ // Commands lists the available commands and help topics.
+ // The order here is the order in which they are printed by 'go help'.
+ // Note that subcommands are in general best avoided.
+ Commands []*Command
+}
+
+var Go = &Command{
+ UsageLine: "go",
+ Long: `Go is a tool for managing Go source code.`,
+ // Commands initialized in package main
+}
+
+// Lookup returns the subcommand with the given name, if any.
+// Otherwise it returns nil.
+//
+// Lookup ignores subcommands that have len(c.Commands) == 0 and c.Run == nil.
+// Such subcommands are only for use as arguments to "help".
+func (c *Command) Lookup(name string) *Command {
+ for _, sub := range c.Commands {
+ if sub.Name() == name && (len(c.Commands) > 0 || c.Runnable()) {
+ return sub
+ }
+ }
+ return nil
+}
+
+// hasFlag reports whether a command or any of its subcommands contain the given
+// flag.
+func hasFlag(c *Command, name string) bool {
+ if f := c.Flag.Lookup(name); f != nil {
+ return true
+ }
+ for _, sub := range c.Commands {
+ if hasFlag(sub, name) {
+ return true
+ }
+ }
+ return false
+}
+
+// LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument,
+func (c *Command) LongName() string {
+ name := c.UsageLine
+ if i := strings.Index(name, " ["); i >= 0 {
+ name = name[:i]
+ }
+ if name == "go" {
+ return ""
+ }
+ return strings.TrimPrefix(name, "go ")
+}
+
+// Name returns the command's short name: the last word in the usage line before a flag or argument.
+func (c *Command) Name() string {
+ name := c.LongName()
+ if i := strings.LastIndex(name, " "); i >= 0 {
+ name = name[i+1:]
+ }
+ return name
+}
+
+func (c *Command) Usage() {
+ fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
+ fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", c.LongName())
+ SetExitStatus(2)
+ Exit()
+}
+
+// Runnable reports whether the command can be run; otherwise
+// it is a documentation pseudo-command such as importpath.
+func (c *Command) Runnable() bool {
+ return c.Run != nil
+}
+
+var atExitFuncs []func()
+
+func AtExit(f func()) {
+ atExitFuncs = append(atExitFuncs, f)
+}
+
+func Exit() {
+ for _, f := range atExitFuncs {
+ f()
+ }
+ os.Exit(exitStatus)
+}
+
+func Fatalf(format string, args ...any) {
+ Errorf(format, args...)
+ Exit()
+}
+
+func Errorf(format string, args ...any) {
+ log.Printf(format, args...)
+ SetExitStatus(1)
+}
+
+func ExitIfErrors() {
+ if exitStatus != 0 {
+ Exit()
+ }
+}
+
+func Error(err error) {
+ // We use errors.Join to return multiple errors from various routines.
+ // If we receive multiple errors joined with a basic errors.Join,
+ // handle each one separately so that they all have the leading "go: " prefix.
+ // A plain interface check is not good enough because there might be
+ // other kinds of structured errors that are logically one unit and that
+ // add other context: only handling the wrapped errors would lose
+ // that context.
+ if err != nil && reflect.TypeOf(err).String() == "*errors.joinError" {
+ for _, e := range err.(interface{ Unwrap() []error }).Unwrap() {
+ Error(e)
+ }
+ return
+ }
+ Errorf("go: %v", err)
+}
+
+func Fatal(err error) {
+ Error(err)
+ Exit()
+}
+
+var exitStatus = 0
+var exitMu sync.Mutex
+
+func SetExitStatus(n int) {
+ exitMu.Lock()
+ if exitStatus < n {
+ exitStatus = n
+ }
+ exitMu.Unlock()
+}
+
+func GetExitStatus() int {
+ return exitStatus
+}
+
+// Run runs the command, with stdout and stderr
+// connected to the go command's own stdout and stderr.
+// If the command fails, Run reports the error using Errorf.
+func Run(cmdargs ...any) {
+ cmdline := str.StringList(cmdargs...)
+ if cfg.BuildN || cfg.BuildX {
+ fmt.Printf("%s\n", strings.Join(cmdline, " "))
+ if cfg.BuildN {
+ return
+ }
+ }
+
+ cmd := exec.Command(cmdline[0], cmdline[1:]...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ Errorf("%v", err)
+ }
+}
+
+// RunStdin is like run but connects Stdin.
+func RunStdin(cmdline []string) {
+ cmd := exec.Command(cmdline[0], cmdline[1:]...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Env = cfg.OrigEnv
+ StartSigHandlers()
+ if err := cmd.Run(); err != nil {
+ Errorf("%v", err)
+ }
+}
+
+// Usage is the usage-reporting function, filled in by package main
+// but here for reference by other packages.
+var Usage func()
diff --git a/src/cmd/go/internal/base/env.go b/src/cmd/go/internal/base/env.go
new file mode 100644
index 0000000..20ae06d
--- /dev/null
+++ b/src/cmd/go/internal/base/env.go
@@ -0,0 +1,46 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "cmd/go/internal/cfg"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+)
+
+// AppendPWD returns the result of appending PWD=dir to the environment base.
+//
+// The resulting environment makes os.Getwd more efficient for a subprocess
+// running in dir, and also improves the accuracy of paths relative to dir
+// if one or more elements of dir is a symlink.
+func AppendPWD(base []string, dir string) []string {
+ // POSIX requires PWD to be absolute.
+ // Internally we only use absolute paths, so dir should already be absolute.
+ if !filepath.IsAbs(dir) {
+ panic(fmt.Sprintf("AppendPWD with relative path %q", dir))
+ }
+ return append(base, "PWD="+dir)
+}
+
+// AppendPATH returns the result of appending PATH=$GOROOT/bin:$PATH
+// (or the platform equivalent) to the environment base.
+func AppendPATH(base []string) []string {
+ if cfg.GOROOTbin == "" {
+ return base
+ }
+
+ pathVar := "PATH"
+ if runtime.GOOS == "plan9" {
+ pathVar = "path"
+ }
+
+ path := os.Getenv(pathVar)
+ if path == "" {
+ return append(base, pathVar+"="+cfg.GOROOTbin)
+ }
+ return append(base, pathVar+"="+cfg.GOROOTbin+string(os.PathListSeparator)+path)
+}
diff --git a/src/cmd/go/internal/base/flag.go b/src/cmd/go/internal/base/flag.go
new file mode 100644
index 0000000..74e1275
--- /dev/null
+++ b/src/cmd/go/internal/base/flag.go
@@ -0,0 +1,85 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "flag"
+ "fmt"
+
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/fsys"
+ "cmd/internal/quoted"
+)
+
+// A StringsFlag is a command-line flag that interprets its argument
+// as a space-separated list of possibly-quoted strings.
+type StringsFlag []string
+
+func (v *StringsFlag) Set(s string) error {
+ var err error
+ *v, err = quoted.Split(s)
+ if *v == nil {
+ *v = []string{}
+ }
+ return err
+}
+
+func (v *StringsFlag) String() string {
+ return "<StringsFlag>"
+}
+
+// explicitStringFlag is like a regular string flag, but it also tracks whether
+// the string was set explicitly to a non-empty value.
+type explicitStringFlag struct {
+ value *string
+ explicit *bool
+}
+
+func (f explicitStringFlag) String() string {
+ if f.value == nil {
+ return ""
+ }
+ return *f.value
+}
+
+func (f explicitStringFlag) Set(v string) error {
+ *f.value = v
+ if v != "" {
+ *f.explicit = true
+ }
+ return nil
+}
+
+// AddBuildFlagsNX adds the -n and -x build flags to the flag set.
+func AddBuildFlagsNX(flags *flag.FlagSet) {
+ flags.BoolVar(&cfg.BuildN, "n", false, "")
+ flags.BoolVar(&cfg.BuildX, "x", false, "")
+}
+
+// AddChdirFlag adds the -C flag to the flag set.
+func AddChdirFlag(flags *flag.FlagSet) {
+ // The usage message is never printed, but it's used in chdir_test.go
+ // to identify that the -C flag is from AddChdirFlag.
+ flags.Func("C", "AddChdirFlag", ChdirFlag)
+}
+
+// AddModFlag adds the -mod build flag to the flag set.
+func AddModFlag(flags *flag.FlagSet) {
+ flags.Var(explicitStringFlag{value: &cfg.BuildMod, explicit: &cfg.BuildModExplicit}, "mod", "")
+}
+
+// AddModCommonFlags adds the module-related flags common to build commands
+// and 'go mod' subcommands.
+func AddModCommonFlags(flags *flag.FlagSet) {
+ flags.BoolVar(&cfg.ModCacheRW, "modcacherw", false, "")
+ flags.StringVar(&cfg.ModFile, "modfile", "", "")
+ flags.StringVar(&fsys.OverlayFile, "overlay", "", "")
+}
+
+func ChdirFlag(s string) error {
+ // main handles -C by removing it from the command line.
+ // If we see one during flag parsing, that's an error.
+ return fmt.Errorf("-C flag must be first flag on command line")
+}
diff --git a/src/cmd/go/internal/base/goflags.go b/src/cmd/go/internal/base/goflags.go
new file mode 100644
index 0000000..eced2c5
--- /dev/null
+++ b/src/cmd/go/internal/base/goflags.go
@@ -0,0 +1,162 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "flag"
+ "fmt"
+ "runtime"
+ "strings"
+
+ "cmd/go/internal/cfg"
+ "cmd/internal/quoted"
+)
+
+var goflags []string // cached $GOFLAGS list; can be -x or --x form
+
+// GOFLAGS returns the flags from $GOFLAGS.
+// The list can be assumed to contain one string per flag,
+// with each string either beginning with -name or --name.
+func GOFLAGS() []string {
+ InitGOFLAGS()
+ return goflags
+}
+
+// InitGOFLAGS initializes the goflags list from $GOFLAGS.
+// If goflags is already initialized, it does nothing.
+func InitGOFLAGS() {
+ if goflags != nil { // already initialized
+ return
+ }
+
+ // Ignore bad flag in go env and go bug, because
+ // they are what people reach for when debugging
+ // a problem, and maybe they're debugging GOFLAGS.
+ // (Both will show the GOFLAGS setting if let succeed.)
+ hideErrors := cfg.CmdName == "env" || cfg.CmdName == "bug"
+
+ var err error
+ goflags, err = quoted.Split(cfg.Getenv("GOFLAGS"))
+ if err != nil {
+ if hideErrors {
+ return
+ }
+ Fatalf("go: parsing $GOFLAGS: %v", err)
+ }
+
+ if len(goflags) == 0 {
+ // nothing to do; avoid work on later InitGOFLAGS call
+ goflags = []string{}
+ return
+ }
+
+ // Each of the words returned by strings.Fields must be its own flag.
+ // To set flag arguments use -x=value instead of -x value.
+ // For boolean flags, -x is fine instead of -x=true.
+ for _, f := range goflags {
+ // Check that every flag looks like -x --x -x=value or --x=value.
+ if !strings.HasPrefix(f, "-") || f == "-" || f == "--" || strings.HasPrefix(f, "---") || strings.HasPrefix(f, "-=") || strings.HasPrefix(f, "--=") {
+ if hideErrors {
+ continue
+ }
+ Fatalf("go: parsing $GOFLAGS: non-flag %q", f)
+ }
+
+ name := f[1:]
+ if name[0] == '-' {
+ name = name[1:]
+ }
+ if i := strings.Index(name, "="); i >= 0 {
+ name = name[:i]
+ }
+ if !hasFlag(Go, name) {
+ if hideErrors {
+ continue
+ }
+ Fatalf("go: parsing $GOFLAGS: unknown flag -%s", name)
+ }
+ }
+}
+
+// boolFlag is the optional interface for flag.Value known to the flag package.
+// (It is not clear why package flag does not export this interface.)
+type boolFlag interface {
+ flag.Value
+ IsBoolFlag() bool
+}
+
+// SetFromGOFLAGS sets the flags in the given flag set using settings in $GOFLAGS.
+func SetFromGOFLAGS(flags *flag.FlagSet) {
+ InitGOFLAGS()
+
+ // This loop is similar to flag.Parse except that it ignores
+ // unknown flags found in goflags, so that setting, say, GOFLAGS=-ldflags=-w
+ // does not break commands that don't have a -ldflags.
+ // It also adjusts the output to be clear that the reported problem is from $GOFLAGS.
+ where := "$GOFLAGS"
+ if runtime.GOOS == "windows" {
+ where = "%GOFLAGS%"
+ }
+ for _, goflag := range goflags {
+ name, value, hasValue := goflag, "", false
+ // Ignore invalid flags like '=' or '=value'.
+ // If it is not reported in InitGOFlags it means we don't want to report it.
+ if i := strings.Index(goflag, "="); i == 0 {
+ continue
+ } else if i > 0 {
+ name, value, hasValue = goflag[:i], goflag[i+1:], true
+ }
+ if strings.HasPrefix(name, "--") {
+ name = name[1:]
+ }
+ f := flags.Lookup(name[1:])
+ if f == nil {
+ continue
+ }
+
+ // Use flags.Set consistently (instead of f.Value.Set) so that a subsequent
+ // call to flags.Visit will correctly visit the flags that have been set.
+
+ if fb, ok := f.Value.(boolFlag); ok && fb.IsBoolFlag() {
+ if hasValue {
+ if err := flags.Set(f.Name, value); err != nil {
+ fmt.Fprintf(flags.Output(), "go: invalid boolean value %q for flag %s (from %s): %v\n", value, name, where, err)
+ flags.Usage()
+ }
+ } else {
+ if err := flags.Set(f.Name, "true"); err != nil {
+ fmt.Fprintf(flags.Output(), "go: invalid boolean flag %s (from %s): %v\n", name, where, err)
+ flags.Usage()
+ }
+ }
+ } else {
+ if !hasValue {
+ fmt.Fprintf(flags.Output(), "go: flag needs an argument: %s (from %s)\n", name, where)
+ flags.Usage()
+ }
+ if err := flags.Set(f.Name, value); err != nil {
+ fmt.Fprintf(flags.Output(), "go: invalid value %q for flag %s (from %s): %v\n", value, name, where, err)
+ flags.Usage()
+ }
+ }
+ }
+}
+
+// InGOFLAGS returns whether GOFLAGS contains the given flag, such as "-mod".
+func InGOFLAGS(flag string) bool {
+ for _, goflag := range GOFLAGS() {
+ name := goflag
+ if strings.HasPrefix(name, "--") {
+ name = name[1:]
+ }
+ if i := strings.Index(name, "="); i >= 0 {
+ name = name[:i]
+ }
+ if name == flag {
+ return true
+ }
+ }
+ return false
+}
diff --git a/src/cmd/go/internal/base/limit.go b/src/cmd/go/internal/base/limit.go
new file mode 100644
index 0000000..b4160bd
--- /dev/null
+++ b/src/cmd/go/internal/base/limit.go
@@ -0,0 +1,84 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "fmt"
+ "internal/godebug"
+ "runtime"
+ "strconv"
+ "sync"
+)
+
+var NetLimitGodebug = godebug.New("#cmdgonetlimit")
+
+// NetLimit returns the limit on concurrent network operations
+// configured by GODEBUG=cmdgonetlimit, if any.
+//
+// A limit of 0 (indicated by 0, true) means that network operations should not
+// be allowed.
+func NetLimit() (int, bool) {
+ netLimitOnce.Do(func() {
+ s := NetLimitGodebug.Value()
+ if s == "" {
+ return
+ }
+
+ n, err := strconv.Atoi(s)
+ if err != nil {
+ Fatalf("invalid %s: %v", NetLimitGodebug.Name(), err)
+ }
+ if n < 0 {
+ // Treat negative values as unlimited.
+ return
+ }
+ netLimitSem = make(chan struct{}, n)
+ })
+
+ return cap(netLimitSem), netLimitSem != nil
+}
+
+// AcquireNet acquires a semaphore token for a network operation.
+func AcquireNet() (release func(), err error) {
+ hasToken := false
+ if n, ok := NetLimit(); ok {
+ if n == 0 {
+ return nil, fmt.Errorf("network disabled by %v=%v", NetLimitGodebug.Name(), NetLimitGodebug.Value())
+ }
+ netLimitSem <- struct{}{}
+ hasToken = true
+ }
+
+ checker := new(netTokenChecker)
+ runtime.SetFinalizer(checker, (*netTokenChecker).panicUnreleased)
+
+ return func() {
+ if checker.released {
+ panic("internal error: net token released twice")
+ }
+ checker.released = true
+ if hasToken {
+ <-netLimitSem
+ }
+ runtime.SetFinalizer(checker, nil)
+ }, nil
+}
+
+var (
+ netLimitOnce sync.Once
+ netLimitSem chan struct{}
+)
+
+type netTokenChecker struct {
+ released bool
+ // We want to use a finalizer to check that all acquired tokens are returned,
+ // so we arbitrarily pad the tokens with a string to defeat the runtime's
+ // “tiny allocator”.
+ unusedAvoidTinyAllocator string
+}
+
+func (c *netTokenChecker) panicUnreleased() {
+ panic("internal error: net token acquired but not released")
+}
diff --git a/src/cmd/go/internal/base/path.go b/src/cmd/go/internal/base/path.go
new file mode 100644
index 0000000..64f213b
--- /dev/null
+++ b/src/cmd/go/internal/base/path.go
@@ -0,0 +1,79 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+)
+
+var cwd string
+var cwdOnce sync.Once
+
+// UncachedCwd returns the current working directory.
+// Most callers should use Cwd, which caches the result for future use.
+// UncachedCwd is appropriate to call early in program startup before flag parsing,
+// because the -C flag may change the current directory.
+func UncachedCwd() string {
+ wd, err := os.Getwd()
+ if err != nil {
+ Fatalf("cannot determine current directory: %v", err)
+ }
+ return wd
+}
+
+// Cwd returns the current working directory at the time of the first call.
+func Cwd() string {
+ cwdOnce.Do(func() {
+ cwd = UncachedCwd()
+ })
+ return cwd
+}
+
+// ShortPath returns an absolute or relative name for path, whatever is shorter.
+func ShortPath(path string) string {
+ if rel, err := filepath.Rel(Cwd(), path); err == nil && len(rel) < len(path) {
+ return rel
+ }
+ return path
+}
+
+// RelPaths returns a copy of paths with absolute paths
+// made relative to the current directory if they would be shorter.
+func RelPaths(paths []string) []string {
+ var out []string
+ for _, p := range paths {
+ rel, err := filepath.Rel(Cwd(), p)
+ if err == nil && len(rel) < len(p) {
+ p = rel
+ }
+ out = append(out, p)
+ }
+ return out
+}
+
+// IsTestFile reports whether the source file is a set of tests and should therefore
+// be excluded from coverage analysis.
+func IsTestFile(file string) bool {
+ // We don't cover tests, only the code they test.
+ return strings.HasSuffix(file, "_test.go")
+}
+
+// IsNull reports whether the path is a common name for the null device.
+// It returns true for /dev/null on Unix, or NUL (case-insensitive) on Windows.
+func IsNull(path string) bool {
+ if path == os.DevNull {
+ return true
+ }
+ if runtime.GOOS == "windows" {
+ if strings.EqualFold(path, "NUL") {
+ return true
+ }
+ }
+ return false
+}
diff --git a/src/cmd/go/internal/base/signal.go b/src/cmd/go/internal/base/signal.go
new file mode 100644
index 0000000..05befcf
--- /dev/null
+++ b/src/cmd/go/internal/base/signal.go
@@ -0,0 +1,31 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "os"
+ "os/signal"
+ "sync"
+)
+
+// Interrupted is closed when the go command receives an interrupt signal.
+var Interrupted = make(chan struct{})
+
+// processSignals setups signal handler.
+func processSignals() {
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, signalsToIgnore...)
+ go func() {
+ <-sig
+ close(Interrupted)
+ }()
+}
+
+var onceProcessSignals sync.Once
+
+// StartSigHandlers starts the signal handlers.
+func StartSigHandlers() {
+ onceProcessSignals.Do(processSignals)
+}
diff --git a/src/cmd/go/internal/base/signal_notunix.go b/src/cmd/go/internal/base/signal_notunix.go
new file mode 100644
index 0000000..682705f
--- /dev/null
+++ b/src/cmd/go/internal/base/signal_notunix.go
@@ -0,0 +1,17 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build plan9 || windows
+
+package base
+
+import (
+ "os"
+)
+
+var signalsToIgnore = []os.Signal{os.Interrupt}
+
+// SignalTrace is the signal to send to make a Go program
+// crash with a stack trace (no such signal in this case).
+var SignalTrace os.Signal = nil
diff --git a/src/cmd/go/internal/base/signal_unix.go b/src/cmd/go/internal/base/signal_unix.go
new file mode 100644
index 0000000..0905971
--- /dev/null
+++ b/src/cmd/go/internal/base/signal_unix.go
@@ -0,0 +1,18 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || js || wasip1
+
+package base
+
+import (
+ "os"
+ "syscall"
+)
+
+var signalsToIgnore = []os.Signal{os.Interrupt, syscall.SIGQUIT}
+
+// SignalTrace is the signal to send to make a Go program
+// crash with a stack trace.
+var SignalTrace os.Signal = syscall.SIGQUIT
diff --git a/src/cmd/go/internal/base/tool.go b/src/cmd/go/internal/base/tool.go
new file mode 100644
index 0000000..ab623da
--- /dev/null
+++ b/src/cmd/go/internal/base/tool.go
@@ -0,0 +1,41 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package base
+
+import (
+ "fmt"
+ "go/build"
+ "os"
+ "path/filepath"
+
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/par"
+)
+
+// Tool returns the path to the named tool (for example, "vet").
+// If the tool cannot be found, Tool exits the process.
+func Tool(toolName string) string {
+ toolPath, err := ToolPath(toolName)
+ if err != nil && len(cfg.BuildToolexec) == 0 {
+ // Give a nice message if there is no tool with that name.
+ fmt.Fprintf(os.Stderr, "go: no such tool %q\n", toolName)
+ SetExitStatus(2)
+ Exit()
+ }
+ return toolPath
+}
+
+// Tool returns the path at which we expect to find the named tool
+// (for example, "vet"), and the error (if any) from statting that path.
+func ToolPath(toolName string) (string, error) {
+ toolPath := filepath.Join(build.ToolDir, toolName) + cfg.ToolExeSuffix()
+ err := toolStatCache.Do(toolPath, func() error {
+ _, err := os.Stat(toolPath)
+ return err
+ })
+ return toolPath, err
+}
+
+var toolStatCache par.Cache[string, error]