Source file src/cmd/go/main.go

     1  // Copyright 2011 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  //go:generate go test cmd/go -v -run=^TestDocsUpToDate$ -fixdocs
     6  
     7  package main
     8  
     9  import (
    10  	"context"
    11  	"flag"
    12  	"fmt"
    13  	"internal/buildcfg"
    14  	"log"
    15  	"os"
    16  	"path/filepath"
    17  	rtrace "runtime/trace"
    18  	"slices"
    19  	"strings"
    20  
    21  	"cmd/go/internal/base"
    22  	"cmd/go/internal/bug"
    23  	"cmd/go/internal/cfg"
    24  	"cmd/go/internal/clean"
    25  	"cmd/go/internal/doc"
    26  	"cmd/go/internal/envcmd"
    27  	"cmd/go/internal/fmtcmd"
    28  	"cmd/go/internal/generate"
    29  	"cmd/go/internal/help"
    30  	"cmd/go/internal/list"
    31  	"cmd/go/internal/modcmd"
    32  	"cmd/go/internal/modfetch"
    33  	"cmd/go/internal/modget"
    34  	"cmd/go/internal/modload"
    35  	"cmd/go/internal/run"
    36  	"cmd/go/internal/telemetrycmd"
    37  	"cmd/go/internal/telemetrystats"
    38  	"cmd/go/internal/test"
    39  	"cmd/go/internal/tool"
    40  	"cmd/go/internal/toolchain"
    41  	"cmd/go/internal/trace"
    42  	"cmd/go/internal/version"
    43  	"cmd/go/internal/vet"
    44  	"cmd/go/internal/work"
    45  	"cmd/go/internal/workcmd"
    46  	"cmd/internal/telemetry"
    47  	"cmd/internal/telemetry/counter"
    48  )
    49  
    50  func init() {
    51  	base.Go.Commands = []*base.Command{
    52  		bug.CmdBug,
    53  		work.CmdBuild,
    54  		clean.CmdClean,
    55  		doc.CmdDoc,
    56  		envcmd.CmdEnv,
    57  		vet.CmdFix,
    58  		fmtcmd.CmdFmt,
    59  		generate.CmdGenerate,
    60  		modget.CmdGet,
    61  		work.CmdInstall,
    62  		list.CmdList,
    63  		modcmd.CmdMod,
    64  		workcmd.CmdWork,
    65  		run.CmdRun,
    66  		telemetrycmd.CmdTelemetry,
    67  		test.CmdTest,
    68  		tool.CmdTool,
    69  		version.CmdVersion,
    70  		vet.CmdVet,
    71  
    72  		help.HelpBuildConstraint,
    73  		help.HelpBuildJSON,
    74  		help.HelpBuildmode,
    75  		help.HelpC,
    76  		help.HelpCache,
    77  		help.HelpEnvironment,
    78  		help.HelpFileType,
    79  		help.HelpGoAuth,
    80  		modload.HelpGoMod,
    81  		help.HelpGopath,
    82  		modfetch.HelpGoproxy,
    83  		help.HelpImportPath,
    84  		modload.HelpModules,
    85  		modfetch.HelpModuleAuth,
    86  		help.HelpPackages,
    87  		modfetch.HelpPrivate,
    88  		test.HelpTestflag,
    89  		test.HelpTestfunc,
    90  		modget.HelpVCS,
    91  	}
    92  }
    93  
    94  var _ = go11tag
    95  
    96  var counterErrorsGOPATHEntryRelative = counter.New("go/errors:gopath-entry-relative")
    97  
    98  func main() {
    99  	log.SetFlags(0)
   100  	telemetry.MaybeChild() // Run in child mode if this is the telemetry sidecar child process.
   101  	cmdIsGoTelemetryOff := cmdIsGoTelemetryOff()
   102  	if !cmdIsGoTelemetryOff {
   103  		counter.Open() // Open the telemetry counter file so counters can be written to it.
   104  	}
   105  	handleChdirFlag()
   106  	toolchain.Select()
   107  
   108  	if !cmdIsGoTelemetryOff {
   109  		telemetry.MaybeParent() // Run the upload process. Opening the counter file is idempotent.
   110  	}
   111  	flag.Usage = base.Usage
   112  	flag.Parse()
   113  	counter.Inc("go/invocations")
   114  	counter.CountFlags("go/flag:", *flag.CommandLine)
   115  
   116  	args := flag.Args()
   117  	if len(args) < 1 {
   118  		base.Usage()
   119  	}
   120  
   121  	cfg.CmdName = args[0] // for error messages
   122  	if args[0] == "help" {
   123  		counter.Inc("go/subcommand:" + strings.Join(append([]string{"help"}, args[1:]...), "-"))
   124  		help.Help(os.Stdout, args[1:])
   125  		return
   126  	}
   127  
   128  	if cfg.GOROOT == "" {
   129  		fmt.Fprintf(os.Stderr, "go: cannot find GOROOT directory: 'go' binary is trimmed and GOROOT is not set\n")
   130  		os.Exit(2)
   131  	}
   132  	if fi, err := os.Stat(cfg.GOROOT); err != nil || !fi.IsDir() {
   133  		fmt.Fprintf(os.Stderr, "go: cannot find GOROOT directory: %v\n", cfg.GOROOT)
   134  		os.Exit(2)
   135  	}
   136  	switch strings.ToLower(cfg.GOROOT) {
   137  	case "/usr/local/go": // Location recommended for installation on Linux and Darwin and used by Mac installer.
   138  		counter.Inc("go/goroot:usr-local-go")
   139  	case "/usr/lib/go": // A typical location used by Linux package managers.
   140  		counter.Inc("go/goroot:usr-lib-go")
   141  	case "/usr/lib/golang": // Another typical location used by Linux package managers.
   142  		counter.Inc("go/goroot:usr-lib-golang")
   143  	case `c:\program files\go`: // Location used by Windows installer.
   144  		counter.Inc("go/goroot:program-files-go")
   145  	case `c:\program files (x86)\go`: // Location used by 386 Windows installer on amd64 platform.
   146  		counter.Inc("go/goroot:program-files-x86-go")
   147  	default:
   148  		counter.Inc("go/goroot:other")
   149  	}
   150  
   151  	// Diagnose common mistake: GOPATH==GOROOT.
   152  	// This setting is equivalent to not setting GOPATH at all,
   153  	// which is not what most people want when they do it.
   154  	if gopath := cfg.BuildContext.GOPATH; filepath.Clean(gopath) == filepath.Clean(cfg.GOROOT) {
   155  		fmt.Fprintf(os.Stderr, "warning: both GOPATH and GOROOT are the same directory (%s); see https://go.dev/wiki/InstallTroubleshooting\n", gopath)
   156  	} else {
   157  		for _, p := range filepath.SplitList(gopath) {
   158  			// Some GOPATHs have empty directory elements - ignore them.
   159  			// See issue 21928 for details.
   160  			if p == "" {
   161  				continue
   162  			}
   163  			// Note: using HasPrefix instead of Contains because a ~ can appear
   164  			// in the middle of directory elements, such as /tmp/git-1.8.2~rc3
   165  			// or C:\PROGRA~1. Only ~ as a path prefix has meaning to the shell.
   166  			if strings.HasPrefix(p, "~") {
   167  				fmt.Fprintf(os.Stderr, "go: GOPATH entry cannot start with shell metacharacter '~': %q\n", p)
   168  				os.Exit(2)
   169  			}
   170  			if !filepath.IsAbs(p) {
   171  				if cfg.Getenv("GOPATH") == "" {
   172  					// We inferred $GOPATH from $HOME and did a bad job at it.
   173  					// Instead of dying, uninfer it.
   174  					cfg.BuildContext.GOPATH = ""
   175  				} else {
   176  					counterErrorsGOPATHEntryRelative.Inc()
   177  					fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\nFor more details see: 'go help gopath'\n", p)
   178  					os.Exit(2)
   179  				}
   180  			}
   181  		}
   182  	}
   183  
   184  	cmd, used := lookupCmd(args)
   185  	cfg.CmdName = strings.Join(args[:used], " ")
   186  	if len(cmd.Commands) > 0 {
   187  		if used >= len(args) {
   188  			help.PrintUsage(os.Stderr, cmd)
   189  			base.SetExitStatus(2)
   190  			base.Exit()
   191  		}
   192  		if args[used] == "help" {
   193  			// Accept 'go mod help' and 'go mod help foo' for 'go help mod' and 'go help mod foo'.
   194  			counter.Inc("go/subcommand:" + strings.ReplaceAll(cfg.CmdName, " ", "-") + "-" + strings.Join(args[used:], "-"))
   195  			help.Help(os.Stdout, append(slices.Clip(args[:used]), args[used+1:]...))
   196  			base.Exit()
   197  		}
   198  		helpArg := ""
   199  		if used > 0 {
   200  			helpArg += " " + strings.Join(args[:used], " ")
   201  		}
   202  		cmdName := cfg.CmdName
   203  		if cmdName == "" {
   204  			cmdName = args[0]
   205  		}
   206  		counter.Inc("go/subcommand:unknown")
   207  		fmt.Fprintf(os.Stderr, "go %s: unknown command\nRun 'go help%s' for usage.\n", cmdName, helpArg)
   208  		base.SetExitStatus(2)
   209  		base.Exit()
   210  	}
   211  	// Increment a subcommand counter for the subcommand we're running.
   212  	// Don't increment the counter for the tool subcommand here: we'll
   213  	// increment in the tool subcommand's Run function because we need
   214  	// to do the flag processing in invoke first.
   215  	if cfg.CmdName != "tool" {
   216  		counter.Inc("go/subcommand:" + strings.ReplaceAll(cfg.CmdName, " ", "-"))
   217  	}
   218  	telemetrystats.Increment()
   219  	invoke(cmd, args[used-1:])
   220  	base.Exit()
   221  }
   222  
   223  // cmdIsGoTelemetryOff reports whether the command is "go telemetry off". This
   224  // is used to decide whether to disable the opening of counter files. See #69269.
   225  func cmdIsGoTelemetryOff() bool {
   226  	restArgs := os.Args[1:]
   227  	// skipChdirFlag skips the -C flag, which is the only flag that can appear
   228  	// in a valid 'go telemetry off' command, and which hasn't been processed
   229  	// yet. We need to determine if the command is 'go telemetry off' before we open
   230  	// the counter file, but we want to process -C after we open counters so that
   231  	// we can increment the flag counter for it.
   232  	skipChdirFlag := func() {
   233  		if len(restArgs) == 0 {
   234  			return
   235  		}
   236  		switch a := restArgs[0]; {
   237  		case a == "-C", a == "--C":
   238  			if len(restArgs) < 2 {
   239  				restArgs = nil
   240  				return
   241  			}
   242  			restArgs = restArgs[2:]
   243  
   244  		case strings.HasPrefix(a, "-C="), strings.HasPrefix(a, "--C="):
   245  			restArgs = restArgs[1:]
   246  		}
   247  	}
   248  	skipChdirFlag()
   249  	cmd, used := lookupCmd(restArgs)
   250  	if cmd != telemetrycmd.CmdTelemetry {
   251  		return false
   252  	}
   253  	restArgs = restArgs[used:]
   254  	skipChdirFlag()
   255  	return len(restArgs) == 1 && restArgs[0] == "off"
   256  }
   257  
   258  // lookupCmd interprets the initial elements of args
   259  // to find a command to run (cmd.Runnable() == true)
   260  // or else a command group that ran out of arguments
   261  // or had an unknown subcommand (len(cmd.Commands) > 0).
   262  // It returns that command and the number of elements of args
   263  // that it took to arrive at that command.
   264  func lookupCmd(args []string) (cmd *base.Command, used int) {
   265  	cmd = base.Go
   266  	for used < len(args) {
   267  		c := cmd.Lookup(args[used])
   268  		if c == nil {
   269  			break
   270  		}
   271  		if c.Runnable() {
   272  			cmd = c
   273  			used++
   274  			break
   275  		}
   276  		if len(c.Commands) > 0 {
   277  			cmd = c
   278  			used++
   279  			if used >= len(args) || args[0] == "help" {
   280  				break
   281  			}
   282  			continue
   283  		}
   284  		// len(c.Commands) == 0 && !c.Runnable() => help text; stop at "help"
   285  		break
   286  	}
   287  	return cmd, used
   288  }
   289  
   290  func invoke(cmd *base.Command, args []string) {
   291  	// 'go env' handles checking the build config
   292  	if cmd != envcmd.CmdEnv {
   293  		buildcfg.Check()
   294  		if cfg.ExperimentErr != nil {
   295  			base.Fatal(cfg.ExperimentErr)
   296  		}
   297  	}
   298  
   299  	// Set environment (GOOS, GOARCH, etc) explicitly.
   300  	// In theory all the commands we invoke should have
   301  	// the same default computation of these as we do,
   302  	// but in practice there might be skew
   303  	// This makes sure we all agree.
   304  	cfg.OrigEnv = toolchain.FilterEnv(os.Environ())
   305  	cfg.CmdEnv = envcmd.MkEnv()
   306  	for _, env := range cfg.CmdEnv {
   307  		if os.Getenv(env.Name) != env.Value {
   308  			os.Setenv(env.Name, env.Value)
   309  		}
   310  	}
   311  
   312  	cmd.Flag.Usage = func() { cmd.Usage() }
   313  	if cmd.CustomFlags {
   314  		args = args[1:]
   315  	} else {
   316  		base.SetFromGOFLAGS(&cmd.Flag)
   317  		cmd.Flag.Parse(args[1:])
   318  		flagCounterPrefix := "go/" + strings.ReplaceAll(cfg.CmdName, " ", "-") + "/flag"
   319  		counter.CountFlags(flagCounterPrefix+":", cmd.Flag)
   320  		counter.CountFlagValue(flagCounterPrefix+"/", cmd.Flag, "buildmode")
   321  		args = cmd.Flag.Args()
   322  	}
   323  
   324  	if cfg.DebugRuntimeTrace != "" {
   325  		f, err := os.Create(cfg.DebugRuntimeTrace)
   326  		if err != nil {
   327  			base.Fatalf("creating trace file: %v", err)
   328  		}
   329  		if err := rtrace.Start(f); err != nil {
   330  			base.Fatalf("starting event trace: %v", err)
   331  		}
   332  		defer func() {
   333  			rtrace.Stop()
   334  			f.Close()
   335  		}()
   336  	}
   337  
   338  	ctx := maybeStartTrace(context.Background())
   339  	ctx, span := trace.StartSpan(ctx, fmt.Sprint("Running ", cmd.Name(), " command"))
   340  	cmd.Run(ctx, cmd, args)
   341  	span.Done()
   342  }
   343  
   344  func init() {
   345  	base.Usage = mainUsage
   346  }
   347  
   348  func mainUsage() {
   349  	help.PrintUsage(os.Stderr, base.Go)
   350  	os.Exit(2)
   351  }
   352  
   353  func maybeStartTrace(pctx context.Context) context.Context {
   354  	if cfg.DebugTrace == "" {
   355  		return pctx
   356  	}
   357  
   358  	ctx, close, err := trace.Start(pctx, cfg.DebugTrace)
   359  	if err != nil {
   360  		base.Fatalf("failed to start trace: %v", err)
   361  	}
   362  	base.AtExit(func() {
   363  		if err := close(); err != nil {
   364  			base.Fatalf("failed to stop trace: %v", err)
   365  		}
   366  	})
   367  
   368  	return ctx
   369  }
   370  
   371  // handleChdirFlag handles the -C flag before doing anything else.
   372  // The -C flag must be the first flag on the command line, to make it easy to find
   373  // even with commands that have custom flag parsing.
   374  // handleChdirFlag handles the flag by chdir'ing to the directory
   375  // and then removing that flag from the command line entirely.
   376  //
   377  // We have to handle the -C flag this way for two reasons:
   378  //
   379  //  1. Toolchain selection needs to be in the right directory to look for go.mod and go.work.
   380  //
   381  //  2. A toolchain switch later on reinvokes the new go command with the same arguments.
   382  //     The parent toolchain has already done the chdir; the child must not try to do it again.
   383  func handleChdirFlag() {
   384  	_, used := lookupCmd(os.Args[1:])
   385  	used++ // because of [1:]
   386  	if used >= len(os.Args) {
   387  		return
   388  	}
   389  
   390  	var dir string
   391  	switch a := os.Args[used]; {
   392  	default:
   393  		return
   394  
   395  	case a == "-C", a == "--C":
   396  		if used+1 >= len(os.Args) {
   397  			return
   398  		}
   399  		dir = os.Args[used+1]
   400  		os.Args = slices.Delete(os.Args, used, used+2)
   401  
   402  	case strings.HasPrefix(a, "-C="), strings.HasPrefix(a, "--C="):
   403  		_, dir, _ = strings.Cut(a, "=")
   404  		os.Args = slices.Delete(os.Args, used, used+1)
   405  	}
   406  	counter.Inc("go/flag:C")
   407  
   408  	if err := os.Chdir(dir); err != nil {
   409  		base.Fatalf("go: %v", err)
   410  	}
   411  }
   412  

View as plain text