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