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  	"cmd/go/internal/toolchain"
    11  	"cmd/go/internal/workcmd"
    12  	"context"
    13  	"flag"
    14  	"fmt"
    15  	"internal/buildcfg"
    16  	"log"
    17  	"os"
    18  	"path/filepath"
    19  	rtrace "runtime/trace"
    20  	"slices"
    21  	"strings"
    22  
    23  	"cmd/go/internal/base"
    24  	"cmd/go/internal/bug"
    25  	"cmd/go/internal/cfg"
    26  	"cmd/go/internal/clean"
    27  	"cmd/go/internal/doc"
    28  	"cmd/go/internal/envcmd"
    29  	"cmd/go/internal/fix"
    30  	"cmd/go/internal/fmtcmd"
    31  	"cmd/go/internal/generate"
    32  	"cmd/go/internal/help"
    33  	"cmd/go/internal/list"
    34  	"cmd/go/internal/modcmd"
    35  	"cmd/go/internal/modfetch"
    36  	"cmd/go/internal/modget"
    37  	"cmd/go/internal/modload"
    38  	"cmd/go/internal/run"
    39  	"cmd/go/internal/test"
    40  	"cmd/go/internal/tool"
    41  	"cmd/go/internal/trace"
    42  	"cmd/go/internal/version"
    43  	"cmd/go/internal/vet"
    44  	"cmd/go/internal/work"
    45  )
    46  
    47  func init() {
    48  	base.Go.Commands = []*base.Command{
    49  		bug.CmdBug,
    50  		work.CmdBuild,
    51  		clean.CmdClean,
    52  		doc.CmdDoc,
    53  		envcmd.CmdEnv,
    54  		fix.CmdFix,
    55  		fmtcmd.CmdFmt,
    56  		generate.CmdGenerate,
    57  		modget.CmdGet,
    58  		work.CmdInstall,
    59  		list.CmdList,
    60  		modcmd.CmdMod,
    61  		workcmd.CmdWork,
    62  		run.CmdRun,
    63  		test.CmdTest,
    64  		tool.CmdTool,
    65  		version.CmdVersion,
    66  		vet.CmdVet,
    67  
    68  		help.HelpBuildConstraint,
    69  		help.HelpBuildmode,
    70  		help.HelpC,
    71  		help.HelpCache,
    72  		help.HelpEnvironment,
    73  		help.HelpFileType,
    74  		modload.HelpGoMod,
    75  		help.HelpGopath,
    76  		modfetch.HelpGoproxy,
    77  		help.HelpImportPath,
    78  		modload.HelpModules,
    79  		modfetch.HelpModuleAuth,
    80  		help.HelpPackages,
    81  		modfetch.HelpPrivate,
    82  		test.HelpTestflag,
    83  		test.HelpTestfunc,
    84  		modget.HelpVCS,
    85  	}
    86  }
    87  
    88  var _ = go11tag
    89  
    90  func main() {
    91  	log.SetFlags(0)
    92  	handleChdirFlag()
    93  	toolchain.Select()
    94  
    95  	flag.Usage = base.Usage
    96  	flag.Parse()
    97  
    98  	args := flag.Args()
    99  	if len(args) < 1 {
   100  		base.Usage()
   101  	}
   102  
   103  	cfg.CmdName = args[0] // for error messages
   104  	if args[0] == "help" {
   105  		help.Help(os.Stdout, args[1:])
   106  		return
   107  	}
   108  
   109  	if cfg.GOROOT == "" {
   110  		fmt.Fprintf(os.Stderr, "go: cannot find GOROOT directory: 'go' binary is trimmed and GOROOT is not set\n")
   111  		os.Exit(2)
   112  	}
   113  	if fi, err := os.Stat(cfg.GOROOT); err != nil || !fi.IsDir() {
   114  		fmt.Fprintf(os.Stderr, "go: cannot find GOROOT directory: %v\n", cfg.GOROOT)
   115  		os.Exit(2)
   116  	}
   117  
   118  	// Diagnose common mistake: GOPATH==GOROOT.
   119  	// This setting is equivalent to not setting GOPATH at all,
   120  	// which is not what most people want when they do it.
   121  	if gopath := cfg.BuildContext.GOPATH; filepath.Clean(gopath) == filepath.Clean(cfg.GOROOT) {
   122  		fmt.Fprintf(os.Stderr, "warning: GOPATH set to GOROOT (%s) has no effect\n", gopath)
   123  	} else {
   124  		for _, p := range filepath.SplitList(gopath) {
   125  			// Some GOPATHs have empty directory elements - ignore them.
   126  			// See issue 21928 for details.
   127  			if p == "" {
   128  				continue
   129  			}
   130  			// Note: using HasPrefix instead of Contains because a ~ can appear
   131  			// in the middle of directory elements, such as /tmp/git-1.8.2~rc3
   132  			// or C:\PROGRA~1. Only ~ as a path prefix has meaning to the shell.
   133  			if strings.HasPrefix(p, "~") {
   134  				fmt.Fprintf(os.Stderr, "go: GOPATH entry cannot start with shell metacharacter '~': %q\n", p)
   135  				os.Exit(2)
   136  			}
   137  			if !filepath.IsAbs(p) {
   138  				if cfg.Getenv("GOPATH") == "" {
   139  					// We inferred $GOPATH from $HOME and did a bad job at it.
   140  					// Instead of dying, uninfer it.
   141  					cfg.BuildContext.GOPATH = ""
   142  				} else {
   143  					fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\nFor more details see: 'go help gopath'\n", p)
   144  					os.Exit(2)
   145  				}
   146  			}
   147  		}
   148  	}
   149  
   150  	cmd, used := lookupCmd(args)
   151  	cfg.CmdName = strings.Join(args[:used], " ")
   152  	if len(cmd.Commands) > 0 {
   153  		if used >= len(args) {
   154  			help.PrintUsage(os.Stderr, cmd)
   155  			base.SetExitStatus(2)
   156  			base.Exit()
   157  		}
   158  		if args[used] == "help" {
   159  			// Accept 'go mod help' and 'go mod help foo' for 'go help mod' and 'go help mod foo'.
   160  			help.Help(os.Stdout, append(slices.Clip(args[:used]), args[used+1:]...))
   161  			base.Exit()
   162  		}
   163  		helpArg := ""
   164  		if used > 0 {
   165  			helpArg += " " + strings.Join(args[:used], " ")
   166  		}
   167  		cmdName := cfg.CmdName
   168  		if cmdName == "" {
   169  			cmdName = args[0]
   170  		}
   171  		fmt.Fprintf(os.Stderr, "go %s: unknown command\nRun 'go help%s' for usage.\n", cmdName, helpArg)
   172  		base.SetExitStatus(2)
   173  		base.Exit()
   174  	}
   175  	invoke(cmd, args[used-1:])
   176  	base.Exit()
   177  }
   178  
   179  // lookupCmd interprets the initial elements of args
   180  // to find a command to run (cmd.Runnable() == true)
   181  // or else a command group that ran out of arguments
   182  // or had an unknown subcommand (len(cmd.Commands) > 0).
   183  // It returns that command and the number of elements of args
   184  // that it took to arrive at that command.
   185  func lookupCmd(args []string) (cmd *base.Command, used int) {
   186  	cmd = base.Go
   187  	for used < len(args) {
   188  		c := cmd.Lookup(args[used])
   189  		if c == nil {
   190  			break
   191  		}
   192  		if c.Runnable() {
   193  			cmd = c
   194  			used++
   195  			break
   196  		}
   197  		if len(c.Commands) > 0 {
   198  			cmd = c
   199  			used++
   200  			if used >= len(args) || args[0] == "help" {
   201  				break
   202  			}
   203  			continue
   204  		}
   205  		// len(c.Commands) == 0 && !c.Runnable() => help text; stop at "help"
   206  		break
   207  	}
   208  	return cmd, used
   209  }
   210  
   211  func invoke(cmd *base.Command, args []string) {
   212  	// 'go env' handles checking the build config
   213  	if cmd != envcmd.CmdEnv {
   214  		buildcfg.Check()
   215  		if cfg.ExperimentErr != nil {
   216  			base.Fatal(cfg.ExperimentErr)
   217  		}
   218  	}
   219  
   220  	// Set environment (GOOS, GOARCH, etc) explicitly.
   221  	// In theory all the commands we invoke should have
   222  	// the same default computation of these as we do,
   223  	// but in practice there might be skew
   224  	// This makes sure we all agree.
   225  	cfg.OrigEnv = toolchain.FilterEnv(os.Environ())
   226  	cfg.CmdEnv = envcmd.MkEnv()
   227  	for _, env := range cfg.CmdEnv {
   228  		if os.Getenv(env.Name) != env.Value {
   229  			os.Setenv(env.Name, env.Value)
   230  		}
   231  	}
   232  
   233  	cmd.Flag.Usage = func() { cmd.Usage() }
   234  	if cmd.CustomFlags {
   235  		args = args[1:]
   236  	} else {
   237  		base.SetFromGOFLAGS(&cmd.Flag)
   238  		cmd.Flag.Parse(args[1:])
   239  		args = cmd.Flag.Args()
   240  	}
   241  
   242  	if cfg.DebugRuntimeTrace != "" {
   243  		f, err := os.Create(cfg.DebugRuntimeTrace)
   244  		if err != nil {
   245  			base.Fatalf("creating trace file: %v", err)
   246  		}
   247  		if err := rtrace.Start(f); err != nil {
   248  			base.Fatalf("starting event trace: %v", err)
   249  		}
   250  		defer func() {
   251  			rtrace.Stop()
   252  		}()
   253  	}
   254  
   255  	ctx := maybeStartTrace(context.Background())
   256  	ctx, span := trace.StartSpan(ctx, fmt.Sprint("Running ", cmd.Name(), " command"))
   257  	cmd.Run(ctx, cmd, args)
   258  	span.Done()
   259  }
   260  
   261  func init() {
   262  	base.Usage = mainUsage
   263  }
   264  
   265  func mainUsage() {
   266  	help.PrintUsage(os.Stderr, base.Go)
   267  	os.Exit(2)
   268  }
   269  
   270  func maybeStartTrace(pctx context.Context) context.Context {
   271  	if cfg.DebugTrace == "" {
   272  		return pctx
   273  	}
   274  
   275  	ctx, close, err := trace.Start(pctx, cfg.DebugTrace)
   276  	if err != nil {
   277  		base.Fatalf("failed to start trace: %v", err)
   278  	}
   279  	base.AtExit(func() {
   280  		if err := close(); err != nil {
   281  			base.Fatalf("failed to stop trace: %v", err)
   282  		}
   283  	})
   284  
   285  	return ctx
   286  }
   287  
   288  // handleChdirFlag handles the -C flag before doing anything else.
   289  // The -C flag must be the first flag on the command line, to make it easy to find
   290  // even with commands that have custom flag parsing.
   291  // handleChdirFlag handles the flag by chdir'ing to the directory
   292  // and then removing that flag from the command line entirely.
   293  //
   294  // We have to handle the -C flag this way for two reasons:
   295  //
   296  //  1. Toolchain selection needs to be in the right directory to look for go.mod and go.work.
   297  //
   298  //  2. A toolchain switch later on reinvokes the new go command with the same arguments.
   299  //     The parent toolchain has already done the chdir; the child must not try to do it again.
   300  func handleChdirFlag() {
   301  	_, used := lookupCmd(os.Args[1:])
   302  	used++ // because of [1:]
   303  	if used >= len(os.Args) {
   304  		return
   305  	}
   306  
   307  	var dir string
   308  	switch a := os.Args[used]; {
   309  	default:
   310  		return
   311  
   312  	case a == "-C", a == "--C":
   313  		if used+1 >= len(os.Args) {
   314  			return
   315  		}
   316  		dir = os.Args[used+1]
   317  		os.Args = slices.Delete(os.Args, used, used+2)
   318  
   319  	case strings.HasPrefix(a, "-C="), strings.HasPrefix(a, "--C="):
   320  		_, dir, _ = strings.Cut(a, "=")
   321  		os.Args = slices.Delete(os.Args, used, used+1)
   322  	}
   323  
   324  	if err := os.Chdir(dir); err != nil {
   325  		base.Fatalf("go: %v", err)
   326  	}
   327  }
   328  

View as plain text