1// Copyright 2017 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// Package base defines shared basic pieces of the go command,
6// in particular logging and the Command structure.
7package base
8
9import (
10	"context"
11	"flag"
12	"fmt"
13	"log"
14	"os"
15	"os/exec"
16	"reflect"
17	"strings"
18	"sync"
19
20	"cmd/go/internal/cfg"
21	"cmd/go/internal/str"
22)
23
24// A Command is an implementation of a go command
25// like go build or go fix.
26type Command struct {
27	// Run runs the command.
28	// The args are the arguments after the command name.
29	Run func(ctx context.Context, cmd *Command, args []string)
30
31	// UsageLine is the one-line usage message.
32	// The words between "go" and the first flag or argument in the line are taken to be the command name.
33	UsageLine string
34
35	// Short is the short description shown in the 'go help' output.
36	Short string
37
38	// Long is the long message shown in the 'go help <this-command>' output.
39	Long string
40
41	// Flag is a set of flags specific to this command.
42	Flag flag.FlagSet
43
44	// CustomFlags indicates that the command will do its own
45	// flag parsing.
46	CustomFlags bool
47
48	// Commands lists the available commands and help topics.
49	// The order here is the order in which they are printed by 'go help'.
50	// Note that subcommands are in general best avoided.
51	Commands []*Command
52}
53
54var Go = &Command{
55	UsageLine: "go",
56	Long:      `Go is a tool for managing Go source code.`,
57	// Commands initialized in package main
58}
59
60// Lookup returns the subcommand with the given name, if any.
61// Otherwise it returns nil.
62//
63// Lookup ignores subcommands that have len(c.Commands) == 0 and c.Run == nil.
64// Such subcommands are only for use as arguments to "help".
65func (c *Command) Lookup(name string) *Command {
66	for _, sub := range c.Commands {
67		if sub.Name() == name && (len(c.Commands) > 0 || c.Runnable()) {
68			return sub
69		}
70	}
71	return nil
72}
73
74// hasFlag reports whether a command or any of its subcommands contain the given
75// flag.
76func hasFlag(c *Command, name string) bool {
77	if f := c.Flag.Lookup(name); f != nil {
78		return true
79	}
80	for _, sub := range c.Commands {
81		if hasFlag(sub, name) {
82			return true
83		}
84	}
85	return false
86}
87
88// LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument,
89func (c *Command) LongName() string {
90	name := c.UsageLine
91	if i := strings.Index(name, " ["); i >= 0 {
92		name = name[:i]
93	}
94	if name == "go" {
95		return ""
96	}
97	return strings.TrimPrefix(name, "go ")
98}
99
100// Name returns the command's short name: the last word in the usage line before a flag or argument.
101func (c *Command) Name() string {
102	name := c.LongName()
103	if i := strings.LastIndex(name, " "); i >= 0 {
104		name = name[i+1:]
105	}
106	return name
107}
108
109func (c *Command) Usage() {
110	fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
111	fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", c.LongName())
112	SetExitStatus(2)
113	Exit()
114}
115
116// Runnable reports whether the command can be run; otherwise
117// it is a documentation pseudo-command such as importpath.
118func (c *Command) Runnable() bool {
119	return c.Run != nil
120}
121
122var atExitFuncs []func()
123
124func AtExit(f func()) {
125	atExitFuncs = append(atExitFuncs, f)
126}
127
128func Exit() {
129	for _, f := range atExitFuncs {
130		f()
131	}
132	os.Exit(exitStatus)
133}
134
135func Fatalf(format string, args ...any) {
136	Errorf(format, args...)
137	Exit()
138}
139
140func Errorf(format string, args ...any) {
141	log.Printf(format, args...)
142	SetExitStatus(1)
143}
144
145func ExitIfErrors() {
146	if exitStatus != 0 {
147		Exit()
148	}
149}
150
151func Error(err error) {
152	// We use errors.Join to return multiple errors from various routines.
153	// If we receive multiple errors joined with a basic errors.Join,
154	// handle each one separately so that they all have the leading "go: " prefix.
155	// A plain interface check is not good enough because there might be
156	// other kinds of structured errors that are logically one unit and that
157	// add other context: only handling the wrapped errors would lose
158	// that context.
159	if err != nil && reflect.TypeOf(err).String() == "*errors.joinError" {
160		for _, e := range err.(interface{ Unwrap() []error }).Unwrap() {
161			Error(e)
162		}
163		return
164	}
165	Errorf("go: %v", err)
166}
167
168func Fatal(err error) {
169	Error(err)
170	Exit()
171}
172
173var exitStatus = 0
174var exitMu sync.Mutex
175
176func SetExitStatus(n int) {
177	exitMu.Lock()
178	if exitStatus < n {
179		exitStatus = n
180	}
181	exitMu.Unlock()
182}
183
184func GetExitStatus() int {
185	return exitStatus
186}
187
188// Run runs the command, with stdout and stderr
189// connected to the go command's own stdout and stderr.
190// If the command fails, Run reports the error using Errorf.
191func Run(cmdargs ...any) {
192	cmdline := str.StringList(cmdargs...)
193	if cfg.BuildN || cfg.BuildX {
194		fmt.Printf("%s\n", strings.Join(cmdline, " "))
195		if cfg.BuildN {
196			return
197		}
198	}
199
200	cmd := exec.Command(cmdline[0], cmdline[1:]...)
201	cmd.Stdout = os.Stdout
202	cmd.Stderr = os.Stderr
203	if err := cmd.Run(); err != nil {
204		Errorf("%v", err)
205	}
206}
207
208// RunStdin is like run but connects Stdin.
209func RunStdin(cmdline []string) {
210	cmd := exec.Command(cmdline[0], cmdline[1:]...)
211	cmd.Stdin = os.Stdin
212	cmd.Stdout = os.Stdout
213	cmd.Stderr = os.Stderr
214	cmd.Env = cfg.OrigEnv
215	StartSigHandlers()
216	if err := cmd.Run(); err != nil {
217		Errorf("%v", err)
218	}
219}
220
221// Usage is the usage-reporting function, filled in by package main
222// but here for reference by other packages.
223var Usage func()
224