mirror of
https://github.com/golang/go
synced 2024-11-22 09:04:42 -07:00
cmd/go: add support for mod tools
Running `go tool` with no arguments will now list built in tools followed by module defined tools. Running `go tool X` where X matches either the full package path, or the last segment of the package path, of a defined tool will build the tool to a known location and immediately execute it. For golang/go#48429 Change-Id: I02249df8dad12fb74aa244002f82a81af20e732f Reviewed-on: https://go-review.googlesource.com/c/go/+/534817 Reviewed-by: Michael Matloob <matloob@golang.org> Reviewed-by: Sam Thanawalla <samthanawalla@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
297081eb02
commit
5165f54167
@ -1949,12 +1949,16 @@
|
||||
// go tool [-n] command [args...]
|
||||
//
|
||||
// Tool runs the go tool command identified by the arguments.
|
||||
//
|
||||
// Go ships with a number of builtin tools, and additional tools
|
||||
// may be defined in the go.mod of the current module.
|
||||
//
|
||||
// With no arguments it prints the list of known tools.
|
||||
//
|
||||
// The -n flag causes tool to print the command that would be
|
||||
// executed but not execute it.
|
||||
//
|
||||
// For more about each tool command, see 'go doc cmd/<command>'.
|
||||
// For more about each builtin tool command, see 'go doc cmd/<command>'.
|
||||
//
|
||||
// # Print Go version
|
||||
//
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"cmd/internal/par"
|
||||
)
|
||||
|
||||
// Tool returns the path to the named tool (for example, "vet").
|
||||
// Tool returns the path to the named builtin tool (for example, "vet").
|
||||
// If the tool cannot be found, Tool exits the process.
|
||||
func Tool(toolName string) string {
|
||||
toolPath, err := ToolPath(toolName)
|
||||
@ -30,6 +30,9 @@ func Tool(toolName string) string {
|
||||
// ToolPath returns the path at which we expect to find the named tool
|
||||
// (for example, "vet"), and the error (if any) from statting that path.
|
||||
func ToolPath(toolName string) (string, error) {
|
||||
if !validToolName(toolName) {
|
||||
return "", fmt.Errorf("bad tool name: %q", toolName)
|
||||
}
|
||||
toolPath := filepath.Join(build.ToolDir, toolName) + cfg.ToolExeSuffix()
|
||||
err := toolStatCache.Do(toolPath, func() error {
|
||||
_, err := os.Stat(toolPath)
|
||||
@ -38,4 +41,15 @@ func ToolPath(toolName string) (string, error) {
|
||||
return toolPath, err
|
||||
}
|
||||
|
||||
func validToolName(toolName string) bool {
|
||||
for _, c := range toolName {
|
||||
switch {
|
||||
case 'a' <= c && c <= 'z', '0' <= c && c <= '9', c == '_':
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var toolStatCache par.Cache[string, error]
|
||||
|
@ -9,18 +9,27 @@ import (
|
||||
"cmd/internal/telemetry/counter"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"internal/platform"
|
||||
"maps"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"cmd/go/internal/base"
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/load"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/go/internal/str"
|
||||
"cmd/go/internal/work"
|
||||
)
|
||||
|
||||
var CmdTool = &base.Command{
|
||||
@ -29,12 +38,16 @@ var CmdTool = &base.Command{
|
||||
Short: "run specified go tool",
|
||||
Long: `
|
||||
Tool runs the go tool command identified by the arguments.
|
||||
|
||||
Go ships with a number of builtin tools, and additional tools
|
||||
may be defined in the go.mod of the current module.
|
||||
|
||||
With no arguments it prints the list of known tools.
|
||||
|
||||
The -n flag causes tool to print the command that would be
|
||||
executed but not execute it.
|
||||
|
||||
For more about each tool command, see 'go doc cmd/<command>'.
|
||||
For more about each builtin tool command, see 'go doc cmd/<command>'.
|
||||
`,
|
||||
}
|
||||
|
||||
@ -59,20 +72,10 @@ func init() {
|
||||
func runTool(ctx context.Context, cmd *base.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
counter.Inc("go/subcommand:tool")
|
||||
listTools()
|
||||
listTools(ctx)
|
||||
return
|
||||
}
|
||||
toolName := args[0]
|
||||
// The tool name must be lower-case letters, numbers or underscores.
|
||||
for _, c := range toolName {
|
||||
switch {
|
||||
case 'a' <= c && c <= 'z', '0' <= c && c <= '9', c == '_':
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "go: bad tool name %q\n", toolName)
|
||||
base.SetExitStatus(2)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
toolPath, err := base.ToolPath(toolName)
|
||||
if err != nil {
|
||||
@ -91,7 +94,14 @@ func runTool(ctx context.Context, cmd *base.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
tool := loadModTool(ctx, toolName)
|
||||
if tool != "" {
|
||||
buildAndRunModtool(ctx, tool, args[1:])
|
||||
return
|
||||
}
|
||||
|
||||
counter.Inc("go/subcommand:tool-unknown")
|
||||
|
||||
// Emit the usual error for the missing tool.
|
||||
_ = base.Tool(toolName)
|
||||
} else {
|
||||
@ -143,7 +153,7 @@ func runTool(ctx context.Context, cmd *base.Command, args []string) {
|
||||
}
|
||||
|
||||
// listTools prints a list of the available tools in the tools directory.
|
||||
func listTools() {
|
||||
func listTools(ctx context.Context) {
|
||||
f, err := os.Open(build.ToolDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "go: no tool directory: %s\n", err)
|
||||
@ -171,6 +181,13 @@ func listTools() {
|
||||
}
|
||||
fmt.Println(name)
|
||||
}
|
||||
|
||||
modload.InitWorkfile()
|
||||
modload.LoadModFile(ctx)
|
||||
modTools := slices.Sorted(maps.Keys(modload.MainModules.Tools()))
|
||||
for _, tool := range modTools {
|
||||
fmt.Println(tool)
|
||||
}
|
||||
}
|
||||
|
||||
func impersonateDistList(args []string) (handled bool) {
|
||||
@ -231,3 +248,91 @@ func impersonateDistList(args []string) (handled bool) {
|
||||
os.Stdout.Write(out)
|
||||
return true
|
||||
}
|
||||
|
||||
func loadModTool(ctx context.Context, name string) string {
|
||||
modload.InitWorkfile()
|
||||
modload.LoadModFile(ctx)
|
||||
|
||||
matches := []string{}
|
||||
for tool := range modload.MainModules.Tools() {
|
||||
if tool == name || path.Base(tool) == name {
|
||||
matches = append(matches, tool)
|
||||
}
|
||||
}
|
||||
|
||||
if len(matches) == 1 {
|
||||
return matches[0]
|
||||
}
|
||||
|
||||
if len(matches) > 1 {
|
||||
message := fmt.Sprintf("tool %q is ambiguous; choose one of:\n\t", name)
|
||||
for _, tool := range matches {
|
||||
message += tool + "\n\t"
|
||||
}
|
||||
base.Fatal(errors.New(message))
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func buildAndRunModtool(ctx context.Context, tool string, args []string) {
|
||||
work.BuildInit()
|
||||
b := work.NewBuilder("")
|
||||
defer func() {
|
||||
if err := b.Close(); err != nil {
|
||||
base.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
pkgOpts := load.PackageOpts{MainOnly: true}
|
||||
p := load.PackagesAndErrors(ctx, pkgOpts, []string{tool})[0]
|
||||
p.Internal.OmitDebug = true
|
||||
|
||||
a1 := b.LinkAction(work.ModeInstall, work.ModeBuild, p)
|
||||
a := &work.Action{Mode: "go tool", Actor: work.ActorFunc(runBuiltTool), Args: args, Deps: []*work.Action{a1}}
|
||||
b.Do(ctx, a)
|
||||
}
|
||||
|
||||
func runBuiltTool(b *work.Builder, ctx context.Context, a *work.Action) error {
|
||||
cmdline := str.StringList(work.FindExecCmd(), a.Deps[0].Target, a.Args)
|
||||
|
||||
if toolN {
|
||||
fmt.Println(strings.Join(cmdline, " "))
|
||||
return nil
|
||||
}
|
||||
|
||||
toolCmd := &exec.Cmd{
|
||||
Path: cmdline[0],
|
||||
Args: cmdline[1:],
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
err := toolCmd.Start()
|
||||
if err == nil {
|
||||
c := make(chan os.Signal, 100)
|
||||
signal.Notify(c)
|
||||
go func() {
|
||||
for sig := range c {
|
||||
toolCmd.Process.Signal(sig)
|
||||
}
|
||||
}()
|
||||
err = toolCmd.Wait()
|
||||
signal.Stop(c)
|
||||
close(c)
|
||||
}
|
||||
if err != nil {
|
||||
// Only print about the exit status if the command
|
||||
// didn't even run (not an ExitError)
|
||||
// Assume if command exited cleanly (even with non-zero status)
|
||||
// it printed any messages it wanted to print.
|
||||
if e, ok := err.(*exec.ExitError); ok {
|
||||
base.SetExitStatus(e.ExitCode())
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "go tool %s: %s\n", filepath.Base(a.Deps[0].Target), err)
|
||||
base.SetExitStatus(1)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user