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