github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/version/version.go (about) 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. 6 package version 7 8 import ( 9 "context" 10 "debug/buildinfo" 11 "errors" 12 "fmt" 13 "io/fs" 14 "os" 15 "path/filepath" 16 "runtime" 17 "strings" 18 19 "github.com/go-asm/go/cmd/go/base" 20 "github.com/go-asm/go/cmd/go/gover" 21 ) 22 23 var 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 28 Go version reports the Go version used to build each of the named files. 29 30 If no files are named on the command line, go version prints its own 31 version information. 32 33 If a directory is named, go version walks that directory, recursively, 34 looking for recognized Go binaries and reporting their versions. 35 By default, go version does not report unrecognized files found 36 during a directory scan. The -v flag causes it to report unrecognized files. 37 38 The -m flag causes go version to print each file's embedded 39 module version information, when available. In the output, the module 40 information consists of multiple lines following the version line, each 41 indented by a leading tab character. 42 43 See also: go doc runtime/debug.BuildInfo. 44 `, 45 } 46 47 func init() { 48 base.AddChdirFlag(&CmdVersion.Flag) 49 CmdVersion.Run = runVersion // break init cycle 50 } 51 52 var ( 53 versionM = CmdVersion.Flag.Bool("m", false, "") 54 versionV = CmdVersion.Flag.Bool("v", false, "") 55 ) 56 57 func 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. 101 func 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. 118 func 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. 135 func 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 }