1
2
3
4
5
6 package version
7
8 import (
9 "context"
10 "debug/buildinfo"
11 "encoding/json"
12 "errors"
13 "fmt"
14 "io/fs"
15 "os"
16 "path/filepath"
17 "runtime"
18 "strings"
19
20 "cmd/go/internal/base"
21 "cmd/go/internal/gover"
22 )
23
24 var CmdVersion = &base.Command{
25 UsageLine: "go version [-m] [-v] [-json] [file ...]",
26 Short: "print Go version",
27 Long: `Version prints the build information for Go binary files.
28
29 Go version reports the Go version used to build each of the named files.
30
31 If no files are named on the command line, go version prints its own
32 version information.
33
34 If a directory is named, go version walks that directory, recursively,
35 looking for recognized Go binaries and reporting their versions.
36 By default, go version does not report unrecognized files found
37 during a directory scan. The -v flag causes it to report unrecognized files.
38
39 The -m flag causes go version to print each file's embedded
40 module version information, when available. In the output, the module
41 information consists of multiple lines following the version line, each
42 indented by a leading tab character.
43
44 The -json flag is similar to -m but outputs the runtime/debug.BuildInfo in JSON format.
45 If flag -json is specified without -m, go version reports an error.
46
47 See also: go doc runtime/debug.BuildInfo.
48 `,
49 }
50
51 func init() {
52 base.AddChdirFlag(&CmdVersion.Flag)
53 CmdVersion.Run = runVersion
54 }
55
56 var (
57 versionM = CmdVersion.Flag.Bool("m", false, "")
58 versionV = CmdVersion.Flag.Bool("v", false, "")
59 versionJson = CmdVersion.Flag.Bool("json", false, "")
60 )
61
62 func runVersion(ctx context.Context, cmd *base.Command, args []string) {
63 if len(args) == 0 {
64
65
66
67
68
69
70
71 var argOnlyFlag string
72 if !base.InGOFLAGS("-m") && *versionM {
73 argOnlyFlag = "-m"
74 } else if !base.InGOFLAGS("-v") && *versionV {
75 argOnlyFlag = "-v"
76 } else if !base.InGOFLAGS("-json") && *versionJson {
77
78
79
80 argOnlyFlag = "-json"
81 }
82 if argOnlyFlag != "" {
83 fmt.Fprintf(os.Stderr, "go: 'go version' only accepts %s flag with arguments\n", argOnlyFlag)
84 base.SetExitStatus(2)
85 return
86 }
87 v := runtime.Version()
88 if gover.TestVersion != "" {
89 v = gover.TestVersion + " (TESTGO_VERSION)"
90 }
91 fmt.Printf("go version %s %s/%s\n", v, runtime.GOOS, runtime.GOARCH)
92 return
93 }
94
95 if !*versionM && *versionJson {
96 fmt.Fprintf(os.Stderr, "go: 'go version' with -json flag requires -m flag\n")
97 base.SetExitStatus(2)
98 return
99 }
100
101 for _, arg := range args {
102 info, err := os.Stat(arg)
103 if err != nil {
104 fmt.Fprintf(os.Stderr, "%v\n", err)
105 base.SetExitStatus(1)
106 continue
107 }
108 if info.IsDir() {
109 scanDir(arg)
110 } else {
111 ok := scanFile(arg, info, true)
112 if !ok && *versionM {
113 base.SetExitStatus(1)
114 }
115 }
116 }
117 }
118
119
120 func scanDir(dir string) {
121 filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
122 if d.Type().IsRegular() || d.Type()&fs.ModeSymlink != 0 {
123 info, err := d.Info()
124 if err != nil {
125 if *versionV {
126 fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
127 }
128 return nil
129 }
130 scanFile(path, info, *versionV)
131 }
132 return nil
133 })
134 }
135
136
137 func isGoBinaryCandidate(file string, info fs.FileInfo) bool {
138 if info.Mode().IsRegular() && info.Mode()&0111 != 0 {
139 return true
140 }
141 name := strings.ToLower(file)
142 switch filepath.Ext(name) {
143 case ".so", ".exe", ".dll":
144 return true
145 default:
146 return strings.Contains(name, ".so.")
147 }
148 }
149
150
151
152
153
154
155 func scanFile(file string, info fs.FileInfo, mustPrint bool) bool {
156 if info.Mode()&fs.ModeSymlink != 0 {
157
158 i, err := os.Stat(file)
159 if err != nil || !i.Mode().IsRegular() {
160 if mustPrint {
161 fmt.Fprintf(os.Stderr, "%s: symlink\n", file)
162 }
163 return false
164 }
165 info = i
166 }
167
168 bi, err := buildinfo.ReadFile(file)
169 if err != nil {
170 if mustPrint {
171 if pathErr := (*os.PathError)(nil); errors.As(err, &pathErr) && filepath.Clean(pathErr.Path) == filepath.Clean(file) {
172 fmt.Fprintf(os.Stderr, "%v\n", file)
173 } else {
174
175
176
177
178 if isGoBinaryCandidate(file, info) {
179 fmt.Fprintf(os.Stderr, "%s: %v\n", file, err)
180 }
181 }
182 }
183 return false
184 }
185
186 if *versionM && *versionJson {
187 bs, err := json.MarshalIndent(bi, "", "\t")
188 if err != nil {
189 base.Fatal(err)
190 }
191 fmt.Printf("%s\n", bs)
192 return true
193 }
194
195 fmt.Printf("%s: %s\n", file, bi.GoVersion)
196 bi.GoVersion = ""
197 mod := bi.String()
198 if *versionM && len(mod) > 0 {
199 fmt.Printf("\t%s\n", strings.ReplaceAll(mod[:len(mod)-1], "\n", "\n\t"))
200 }
201 return true
202 }
203
View as plain text