// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "bytes" "flag" "fmt" "go/build" "io" "log" "os" "os/exec" "path" "path/filepath" "regexp" "runtime" "strings" "sync" "text/template" "unicode" "unicode/utf8" ) // A Command is an implementation of a go command // like go build or go fix. type Command struct { // Run runs the command. // The args are the arguments after the command name. Run func(cmd *Command, args []string) // UsageLine is the one-line usage message. // The first word in the line is taken to be the command name. UsageLine string // Short is the short description shown in the 'go help' output. Short string // Long is the long message shown in the 'go help ' output. Long string // Flag is a set of flags specific to this command. Flag flag.FlagSet // CustomFlags indicates that the command will do its own // flag parsing. CustomFlags bool } // Name returns the command's name: the first word in the usage line. func (c *Command) Name() string { name := c.UsageLine i := strings.Index(name, " ") if i >= 0 { name = name[:i] } return name } func (c *Command) Usage() { fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long)) os.Exit(2) } // Runnable reports whether the command can be run; otherwise // it is a documentation pseudo-command such as importpath. func (c *Command) Runnable() bool { return c.Run != nil } // Commands lists the available commands and help topics. // The order here is the order in which they are printed by 'go help'. var commands = []*Command{ cmdBuild, cmdClean, cmdEnv, cmdFix, cmdFmt, cmdGenerate, cmdGet, cmdInstall, cmdList, cmdRun, cmdTest, cmdTool, cmdVersion, cmdVet, helpC, helpFileType, helpGopath, helpImportPath, helpPackages, helpTestflag, helpTestfunc, } var exitStatus = 0 var exitMu sync.Mutex func setExitStatus(n int) { exitMu.Lock() if exitStatus < n { exitStatus = n } exitMu.Unlock() } func main() { _ = go11tag flag.Usage = usage flag.Parse() log.SetFlags(0) args := flag.Args() if len(args) < 1 { usage() } if args[0] == "help" { help(args[1:]) return } // Diagnose common mistake: GOPATH==GOROOT. // This setting is equivalent to not setting GOPATH at all, // which is not what most people want when they do it. if gopath := os.Getenv("GOPATH"); gopath == runtime.GOROOT() { fmt.Fprintf(os.Stderr, "warning: GOPATH set to GOROOT (%s) has no effect\n", gopath) } else { for _, p := range filepath.SplitList(gopath) { // Note: using HasPrefix instead of Contains because a ~ can appear // in the middle of directory elements, such as /tmp/git-1.8.2~rc3 // or C:\PROGRA~1. Only ~ as a path prefix has meaning to the shell. if strings.HasPrefix(p, "~") { fmt.Fprintf(os.Stderr, "go: GOPATH entry cannot start with shell metacharacter '~': %q\n", p) os.Exit(2) } if build.IsLocalImport(p) { fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\nRun 'go help gopath' for usage.\n", p) os.Exit(2) } } } if fi, err := os.Stat(goroot); err != nil || !fi.IsDir() { fmt.Fprintf(os.Stderr, "go: cannot find GOROOT directory: %v\n", goroot) os.Exit(2) } for _, cmd := range commands { if cmd.Name() == args[0] && cmd.Run != nil { cmd.Flag.Usage = func() { cmd.Usage() } if cmd.CustomFlags { args = args[1:] } else { cmd.Flag.Parse(args[1:]) args = cmd.Flag.Args() } cmd.Run(cmd, args) exit() return } } fmt.Fprintf(os.Stderr, "go: unknown subcommand %q\nRun 'go help' for usage.\n", args[0]) setExitStatus(2) exit() } var usageTemplate = `Go is a tool for managing Go source code. Usage: go command [arguments] The commands are: {{range .}}{{if .Runnable}} {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} Use "go help [command]" for more information about a command. Additional help topics: {{range .}}{{if not .Runnable}} {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} Use "go help [topic]" for more information about that topic. ` var helpTemplate = `{{if .Runnable}}usage: go {{.UsageLine}} {{end}}{{.Long | trim}} ` var documentationTemplate = `// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // DO NOT EDIT THIS FILE. GENERATED BY mkdoc.sh. // Edit the documentation in other files and rerun mkdoc.sh to generate this one. /* {{range .}}{{if .Short}}{{.Short | capitalize}} {{end}}{{if .Runnable}}Usage: go {{.UsageLine}} {{end}}{{.Long | trim}} {{end}}*/ package main ` // tmpl executes the given template text on data, writing the result to w. func tmpl(w io.Writer, text string, data interface{}) { t := template.New("top") t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize}) template.Must(t.Parse(text)) if err := t.Execute(w, data); err != nil { panic(err) } } func capitalize(s string) string { if s == "" { return s } r, n := utf8.DecodeRuneInString(s) return string(unicode.ToTitle(r)) + s[n:] } func printUsage(w io.Writer) { tmpl(w, usageTemplate, commands) } func usage() { // special case "go test -h" if len(os.Args) > 1 && os.Args[1] == "test" { help([]string{"testflag"}) os.Exit(2) } printUsage(os.Stderr) os.Exit(2) } // help implements the 'help' command. func help(args []string) { if len(args) == 0 { printUsage(os.Stdout) // not exit 2: succeeded at 'go help'. return } if len(args) != 1 { fmt.Fprintf(os.Stderr, "usage: go help command\n\nToo many arguments given.\n") os.Exit(2) // failed at 'go help' } arg := args[0] // 'go help documentation' generates doc.go. if arg == "documentation" { buf := new(bytes.Buffer) printUsage(buf) usage := &Command{Long: buf.String()} tmpl(os.Stdout, documentationTemplate, append([]*Command{usage}, commands...)) return } for _, cmd := range commands { if cmd.Name() == arg { tmpl(os.Stdout, helpTemplate, cmd) // not exit 2: succeeded at 'go help cmd'. return } } fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'go help'.\n", arg) os.Exit(2) // failed at 'go help cmd' } // importPathsNoDotExpansion returns the import paths to use for the given // command line, but it does no ... expansion. func importPathsNoDotExpansion(args []string) []string { if len(args) == 0 { return []string{"."} } var out []string for _, a := range args { // Arguments are supposed to be import paths, but // as a courtesy to Windows developers, rewrite \ to / // in command-line arguments. Handles .\... and so on. if filepath.Separator == '\\' { a = strings.Replace(a, `\`, `/`, -1) } // Put argument in canonical form, but preserve leading ./. if strings.HasPrefix(a, "./") { a = "./" + path.Clean(a) if a == "./." { a = "." } } else { a = path.Clean(a) } if a == "all" || a == "std" { out = append(out, allPackages(a)...) continue } out = append(out, a) } return out } // importPaths returns the import paths to use for the given command line. func importPaths(args []string) []string { args = importPathsNoDotExpansion(args) var out []string for _, a := range args { if strings.Contains(a, "...") { if build.IsLocalImport(a) { out = append(out, allPackagesInFS(a)...) } else { out = append(out, allPackages(a)...) } continue } out = append(out, a) } return out } var atexitFuncs []func() func atexit(f func()) { atexitFuncs = append(atexitFuncs, f) } func exit() { for _, f := range atexitFuncs { f() } os.Exit(exitStatus) } func fatalf(format string, args ...interface{}) { errorf(format, args...) exit() } func errorf(format string, args ...interface{}) { log.Printf(format, args...) setExitStatus(1) } var logf = log.Printf func exitIfErrors() { if exitStatus != 0 { exit() } } func run(cmdargs ...interface{}) { cmdline := stringList(cmdargs...) if buildN || buildX { fmt.Printf("%s\n", strings.Join(cmdline, " ")) if buildN { return } } cmd := exec.Command(cmdline[0], cmdline[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { errorf("%v", err) } } func runOut(dir string, cmdargs ...interface{}) []byte { cmdline := stringList(cmdargs...) cmd := exec.Command(cmdline[0], cmdline[1:]...) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { os.Stderr.Write(out) errorf("%v", err) out = nil } return out } // envForDir returns a copy of the environment // suitable for running in the given directory. // The environment is the current process's environment // but with an updated $PWD, so that an os.Getwd in the // child will be faster. func envForDir(dir string) []string { env := os.Environ() // Internally we only use rooted paths, so dir is rooted. // Even if dir is not rooted, no harm done. return mergeEnvLists([]string{"PWD=" + dir}, env) } // mergeEnvLists merges the two environment lists such that // variables with the same name in "in" replace those in "out". func mergeEnvLists(in, out []string) []string { NextVar: for _, inkv := range in { k := strings.SplitAfterN(inkv, "=", 2)[0] for i, outkv := range out { if strings.HasPrefix(outkv, k) { out[i] = inkv continue NextVar } } out = append(out, inkv) } return out } // matchPattern(pattern)(name) reports whether // name matches pattern. Pattern is a limited glob // pattern in which '...' means 'any string' and there // is no other special syntax. func matchPattern(pattern string) func(name string) bool { re := regexp.QuoteMeta(pattern) re = strings.Replace(re, `\.\.\.`, `.*`, -1) // Special case: foo/... matches foo too. if strings.HasSuffix(re, `/.*`) { re = re[:len(re)-len(`/.*`)] + `(/.*)?` } reg := regexp.MustCompile(`^` + re + `$`) return func(name string) bool { return reg.MatchString(name) } } // hasPathPrefix reports whether the path s begins with the // elements in prefix. func hasPathPrefix(s, prefix string) bool { switch { default: return false case len(s) == len(prefix): return s == prefix case len(s) > len(prefix): if prefix != "" && prefix[len(prefix)-1] == '/' { return strings.HasPrefix(s, prefix) } return s[len(prefix)] == '/' && s[:len(prefix)] == prefix } } // treeCanMatchPattern(pattern)(name) reports whether // name or children of name can possibly match pattern. // Pattern is the same limited glob accepted by matchPattern. func treeCanMatchPattern(pattern string) func(name string) bool { wildCard := false if i := strings.Index(pattern, "..."); i >= 0 { wildCard = true pattern = pattern[:i] } return func(name string) bool { return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || wildCard && strings.HasPrefix(name, pattern) } } // allPackages returns all the packages that can be found // under the $GOPATH directories and $GOROOT matching pattern. // The pattern is either "all" (all packages), "std" (standard packages) // or a path including "...". func allPackages(pattern string) []string { pkgs := matchPackages(pattern) if len(pkgs) == 0 { fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) } return pkgs } func matchPackages(pattern string) []string { match := func(string) bool { return true } treeCanMatch := func(string) bool { return true } if pattern != "all" && pattern != "std" { match = matchPattern(pattern) treeCanMatch = treeCanMatchPattern(pattern) } have := map[string]bool{ "builtin": true, // ignore pseudo-package that exists only for documentation } if !buildContext.CgoEnabled { have["runtime/cgo"] = true // ignore during walk } var pkgs []string // Commands cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator) filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error { if err != nil || !fi.IsDir() || path == cmd { return nil } name := path[len(cmd):] if !treeCanMatch(name) { return filepath.SkipDir } // Commands are all in cmd/, not in subdirectories. if strings.Contains(name, string(filepath.Separator)) { return filepath.SkipDir } // We use, e.g., cmd/gofmt as the pseudo import path for gofmt. name = "cmd/" + name if have[name] { return nil } have[name] = true if !match(name) { return nil } _, err = buildContext.ImportDir(path, 0) if err != nil { if _, noGo := err.(*build.NoGoError); !noGo { log.Print(err) } return nil } pkgs = append(pkgs, name) return nil }) for _, src := range buildContext.SrcDirs() { if pattern == "std" && src != gorootSrc { continue } src = filepath.Clean(src) + string(filepath.Separator) filepath.Walk(src, func(path string, fi os.FileInfo, err error) error { if err != nil || !fi.IsDir() || path == src { return nil } // Avoid .foo, _foo, and testdata directory trees. _, elem := filepath.Split(path) if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { return filepath.SkipDir } name := filepath.ToSlash(path[len(src):]) if pattern == "std" && strings.Contains(name, ".") { return filepath.SkipDir } if !treeCanMatch(name) { return filepath.SkipDir } if have[name] { return nil } have[name] = true if !match(name) { return nil } _, err = buildContext.ImportDir(path, 0) if err != nil { if _, noGo := err.(*build.NoGoError); noGo { return nil } } pkgs = append(pkgs, name) return nil }) } return pkgs } // allPackagesInFS is like allPackages but is passed a pattern // beginning ./ or ../, meaning it should scan the tree rooted // at the given directory. There are ... in the pattern too. func allPackagesInFS(pattern string) []string { pkgs := matchPackagesInFS(pattern) if len(pkgs) == 0 { fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) } return pkgs } func matchPackagesInFS(pattern string) []string { // Find directory to begin the scan. // Could be smarter but this one optimization // is enough for now, since ... is usually at the // end of a path. i := strings.Index(pattern, "...") dir, _ := path.Split(pattern[:i]) // pattern begins with ./ or ../. // path.Clean will discard the ./ but not the ../. // We need to preserve the ./ for pattern matching // and in the returned import paths. prefix := "" if strings.HasPrefix(pattern, "./") { prefix = "./" } match := matchPattern(pattern) var pkgs []string filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { if err != nil || !fi.IsDir() { return nil } if path == dir { // filepath.Walk starts at dir and recurses. For the recursive case, // the path is the result of filepath.Join, which calls filepath.Clean. // The initial case is not Cleaned, though, so we do this explicitly. // // This converts a path like "./io/" to "io". Without this step, running // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io // package, because prepending the prefix "./" to the unclean path would // result in "././io", and match("././io") returns false. path = filepath.Clean(path) } // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". _, elem := filepath.Split(path) dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { return filepath.SkipDir } name := prefix + filepath.ToSlash(path) if !match(name) { return nil } if _, err = build.ImportDir(path, 0); err != nil { if _, noGo := err.(*build.NoGoError); !noGo { log.Print(err) } return nil } pkgs = append(pkgs, name) return nil }) return pkgs } // stringList's arguments should be a sequence of string or []string values. // stringList flattens them into a single []string. func stringList(args ...interface{}) []string { var x []string for _, arg := range args { switch arg := arg.(type) { case []string: x = append(x, arg...) case string: x = append(x, arg) default: panic("stringList: invalid argument") } } return x } // toFold returns a string with the property that // strings.EqualFold(s, t) iff toFold(s) == toFold(t) // This lets us test a large set of strings for fold-equivalent // duplicates without making a quadratic number of calls // to EqualFold. Note that strings.ToUpper and strings.ToLower // have the desired property in some corner cases. func toFold(s string) string { // Fast path: all ASCII, no upper case. // Most paths look like this already. for i := 0; i < len(s); i++ { c := s[i] if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' { goto Slow } } return s Slow: var buf bytes.Buffer for _, r := range s { // SimpleFold(x) cycles to the next equivalent rune > x // or wraps around to smaller values. Iterate until it wraps, // and we've found the minimum value. for { r0 := r r = unicode.SimpleFold(r0) if r <= r0 { break } } // Exception to allow fast path above: A-Z => a-z if 'A' <= r && r <= 'Z' { r += 'a' - 'A' } buf.WriteRune(r) } return buf.String() } // foldDup reports a pair of strings from the list that are // equal according to strings.EqualFold. // It returns "", "" if there are no such strings. func foldDup(list []string) (string, string) { clash := map[string]string{} for _, s := range list { fold := toFold(s) if t := clash[fold]; t != "" { if s > t { s, t = t, s } return s, t } clash[fold] = s } return "", "" }