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 version implements the “go version” command. 6package version 7 8import ( 9 "context" 10 "debug/buildinfo" 11 "errors" 12 "fmt" 13 "io/fs" 14 "os" 15 "path/filepath" 16 "runtime" 17 "strings" 18 19 "cmd/go/internal/base" 20 "cmd/go/internal/gover" 21) 22 23var CmdVersion = &base.Command{ 24 UsageLine: "go version [-m] [-v] [file ...]", 25 Short: "print Go version", 26 Long: `Version prints the build information for Go binary files. 27 28Go version reports the Go version used to build each of the named files. 29 30If no files are named on the command line, go version prints its own 31version information. 32 33If a directory is named, go version walks that directory, recursively, 34looking for recognized Go binaries and reporting their versions. 35By default, go version does not report unrecognized files found 36during a directory scan. The -v flag causes it to report unrecognized files. 37 38The -m flag causes go version to print each file's embedded 39module version information, when available. In the output, the module 40information consists of multiple lines following the version line, each 41indented by a leading tab character. 42 43See also: go doc runtime/debug.BuildInfo. 44`, 45} 46 47func init() { 48 base.AddChdirFlag(&CmdVersion.Flag) 49 CmdVersion.Run = runVersion // break init cycle 50} 51 52var ( 53 versionM = CmdVersion.Flag.Bool("m", false, "") 54 versionV = CmdVersion.Flag.Bool("v", false, "") 55) 56 57func runVersion(ctx context.Context, cmd *base.Command, args []string) { 58 if len(args) == 0 { 59 // If any of this command's flags were passed explicitly, error 60 // out, because they only make sense with arguments. 61 // 62 // Don't error if the flags came from GOFLAGS, since that can be 63 // a reasonable use case. For example, imagine GOFLAGS=-v to 64 // turn "verbose mode" on for all Go commands, which should not 65 // break "go version". 66 var argOnlyFlag string 67 if !base.InGOFLAGS("-m") && *versionM { 68 argOnlyFlag = "-m" 69 } else if !base.InGOFLAGS("-v") && *versionV { 70 argOnlyFlag = "-v" 71 } 72 if argOnlyFlag != "" { 73 fmt.Fprintf(os.Stderr, "go: 'go version' only accepts %s flag with arguments\n", argOnlyFlag) 74 base.SetExitStatus(2) 75 return 76 } 77 v := runtime.Version() 78 if gover.TestVersion != "" { 79 v = gover.TestVersion + " (TESTGO_VERSION)" 80 } 81 fmt.Printf("go version %s %s/%s\n", v, runtime.GOOS, runtime.GOARCH) 82 return 83 } 84 85 for _, arg := range args { 86 info, err := os.Stat(arg) 87 if err != nil { 88 fmt.Fprintf(os.Stderr, "%v\n", err) 89 base.SetExitStatus(1) 90 continue 91 } 92 if info.IsDir() { 93 scanDir(arg) 94 } else { 95 scanFile(arg, info, true) 96 } 97 } 98} 99 100// scanDir scans a directory for binary to run scanFile on. 101func scanDir(dir string) { 102 filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { 103 if d.Type().IsRegular() || d.Type()&fs.ModeSymlink != 0 { 104 info, err := d.Info() 105 if err != nil { 106 if *versionV { 107 fmt.Fprintf(os.Stderr, "%s: %v\n", path, err) 108 } 109 return nil 110 } 111 scanFile(path, info, *versionV) 112 } 113 return nil 114 }) 115} 116 117// isGoBinaryCandidate reports whether the file is a candidate to be a Go binary. 118func isGoBinaryCandidate(file string, info fs.FileInfo) bool { 119 if info.Mode().IsRegular() && info.Mode()&0111 != 0 { 120 return true 121 } 122 name := strings.ToLower(file) 123 switch filepath.Ext(name) { 124 case ".so", ".exe", ".dll": 125 return true 126 default: 127 return strings.Contains(name, ".so.") 128 } 129} 130 131// scanFile scans file to try to report the Go and module versions. 132// If mustPrint is true, scanFile will report any error reading file. 133// Otherwise (mustPrint is false, because scanFile is being called 134// by scanDir) scanFile prints nothing for non-Go binaries. 135func scanFile(file string, info fs.FileInfo, mustPrint bool) { 136 if info.Mode()&fs.ModeSymlink != 0 { 137 // Accept file symlinks only. 138 i, err := os.Stat(file) 139 if err != nil || !i.Mode().IsRegular() { 140 if mustPrint { 141 fmt.Fprintf(os.Stderr, "%s: symlink\n", file) 142 } 143 return 144 } 145 info = i 146 } 147 148 bi, err := buildinfo.ReadFile(file) 149 if err != nil { 150 if mustPrint { 151 if pathErr := (*os.PathError)(nil); errors.As(err, &pathErr) && filepath.Clean(pathErr.Path) == filepath.Clean(file) { 152 fmt.Fprintf(os.Stderr, "%v\n", file) 153 } else { 154 155 // Skip errors for non-Go binaries. 156 // buildinfo.ReadFile errors are not fine-grained enough 157 // to know if the file is a Go binary or not, 158 // so try to infer it from the file mode and extension. 159 if isGoBinaryCandidate(file, info) { 160 fmt.Fprintf(os.Stderr, "%s: %v\n", file, err) 161 } 162 } 163 } 164 return 165 } 166 167 fmt.Printf("%s: %s\n", file, bi.GoVersion) 168 bi.GoVersion = "" // suppress printing go version again 169 mod := bi.String() 170 if *versionM && len(mod) > 0 { 171 fmt.Printf("\t%s\n", strings.ReplaceAll(mod[:len(mod)-1], "\n", "\n\t")) 172 } 173} 174