diff options
Diffstat (limited to 'src/cmd/go/internal/base')
-rw-r--r-- | src/cmd/go/internal/base/base.go | 223 | ||||
-rw-r--r-- | src/cmd/go/internal/base/env.go | 46 | ||||
-rw-r--r-- | src/cmd/go/internal/base/flag.go | 85 | ||||
-rw-r--r-- | src/cmd/go/internal/base/goflags.go | 162 | ||||
-rw-r--r-- | src/cmd/go/internal/base/limit.go | 84 | ||||
-rw-r--r-- | src/cmd/go/internal/base/path.go | 79 | ||||
-rw-r--r-- | src/cmd/go/internal/base/signal.go | 31 | ||||
-rw-r--r-- | src/cmd/go/internal/base/signal_notunix.go | 17 | ||||
-rw-r--r-- | src/cmd/go/internal/base/signal_unix.go | 18 | ||||
-rw-r--r-- | src/cmd/go/internal/base/tool.go | 41 |
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] |