Source file src/cmd/go/internal/tool/tool.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  // Package tool implements the ``go tool'' command.
     6  package tool
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	exec "internal/execabs"
    12  	"os"
    13  	"os/signal"
    14  	"sort"
    15  	"strings"
    16  
    17  	"cmd/go/internal/base"
    18  	"cmd/go/internal/cfg"
    19  )
    20  
    21  var CmdTool = &base.Command{
    22  	Run:       runTool,
    23  	UsageLine: "go tool [-n] command [args...]",
    24  	Short:     "run specified go tool",
    25  	Long: `
    26  Tool runs the go tool command identified by the arguments.
    27  With no arguments it prints the list of known tools.
    28  
    29  The -n flag causes tool to print the command that would be
    30  executed but not execute it.
    31  
    32  For more about each tool command, see 'go doc cmd/<command>'.
    33  `,
    34  }
    35  
    36  var toolN bool
    37  
    38  // Return whether tool can be expected in the gccgo tool directory.
    39  // Other binaries could be in the same directory so don't
    40  // show those with the 'go tool' command.
    41  func isGccgoTool(tool string) bool {
    42  	switch tool {
    43  	case "cgo", "fix", "cover", "godoc", "vet":
    44  		return true
    45  	}
    46  	return false
    47  }
    48  
    49  func init() {
    50  	CmdTool.Flag.BoolVar(&toolN, "n", false, "")
    51  }
    52  
    53  func runTool(ctx context.Context, cmd *base.Command, args []string) {
    54  	if len(args) == 0 {
    55  		listTools()
    56  		return
    57  	}
    58  	toolName := args[0]
    59  	// The tool name must be lower-case letters, numbers or underscores.
    60  	for _, c := range toolName {
    61  		switch {
    62  		case 'a' <= c && c <= 'z', '0' <= c && c <= '9', c == '_':
    63  		default:
    64  			fmt.Fprintf(os.Stderr, "go: bad tool name %q\n", toolName)
    65  			base.SetExitStatus(2)
    66  			return
    67  		}
    68  	}
    69  	toolPath := base.Tool(toolName)
    70  	if toolPath == "" {
    71  		return
    72  	}
    73  	if toolN {
    74  		cmd := toolPath
    75  		if len(args) > 1 {
    76  			cmd += " " + strings.Join(args[1:], " ")
    77  		}
    78  		fmt.Printf("%s\n", cmd)
    79  		return
    80  	}
    81  	args[0] = toolPath // in case the tool wants to re-exec itself, e.g. cmd/dist
    82  	toolCmd := &exec.Cmd{
    83  		Path:   toolPath,
    84  		Args:   args,
    85  		Stdin:  os.Stdin,
    86  		Stdout: os.Stdout,
    87  		Stderr: os.Stderr,
    88  	}
    89  	err := toolCmd.Start()
    90  	if err == nil {
    91  		c := make(chan os.Signal, 100)
    92  		signal.Notify(c)
    93  		go func() {
    94  			for sig := range c {
    95  				toolCmd.Process.Signal(sig)
    96  			}
    97  		}()
    98  		err = toolCmd.Wait()
    99  		signal.Stop(c)
   100  		close(c)
   101  	}
   102  	if err != nil {
   103  		// Only print about the exit status if the command
   104  		// didn't even run (not an ExitError) or it didn't exit cleanly
   105  		// or we're printing command lines too (-x mode).
   106  		// Assume if command exited cleanly (even with non-zero status)
   107  		// it printed any messages it wanted to print.
   108  		if e, ok := err.(*exec.ExitError); !ok || !e.Exited() || cfg.BuildX {
   109  			fmt.Fprintf(os.Stderr, "go tool %s: %s\n", toolName, err)
   110  		}
   111  		base.SetExitStatus(1)
   112  		return
   113  	}
   114  }
   115  
   116  // listTools prints a list of the available tools in the tools directory.
   117  func listTools() {
   118  	f, err := os.Open(base.ToolDir)
   119  	if err != nil {
   120  		fmt.Fprintf(os.Stderr, "go: no tool directory: %s\n", err)
   121  		base.SetExitStatus(2)
   122  		return
   123  	}
   124  	defer f.Close()
   125  	names, err := f.Readdirnames(-1)
   126  	if err != nil {
   127  		fmt.Fprintf(os.Stderr, "go: can't read tool directory: %s\n", err)
   128  		base.SetExitStatus(2)
   129  		return
   130  	}
   131  
   132  	sort.Strings(names)
   133  	for _, name := range names {
   134  		// Unify presentation by going to lower case.
   135  		name = strings.ToLower(name)
   136  		// If it's windows, don't show the .exe suffix.
   137  		if base.ToolIsWindows && strings.HasSuffix(name, base.ToolWindowsExtension) {
   138  			name = name[:len(name)-len(base.ToolWindowsExtension)]
   139  		}
   140  		// The tool directory used by gccgo will have other binaries
   141  		// in addition to go tools. Only display go tools here.
   142  		if cfg.BuildToolchainName == "gccgo" && !isGccgoTool(name) {
   143  			continue
   144  		}
   145  		fmt.Println(name)
   146  	}
   147  }
   148  

View as plain text