1
2
3
4
5
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 "cmd/go/internal/base"
20 )
21
22 var CmdVersion = &base.Command{
23 UsageLine: "go version [-m] [-v] [file ...]",
24 Short: "print Go version",
25 Long: `Version prints the build information for Go executables.
26
27 Go version reports the Go version used to build each of the named
28 executable 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 executable'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 CmdVersion.Run = runVersion
49 }
50
51 var (
52 versionM = CmdVersion.Flag.Bool("m", false, "")
53 versionV = CmdVersion.Flag.Bool("v", false, "")
54 )
55
56 func runVersion(ctx context.Context, cmd *base.Command, args []string) {
57 if len(args) == 0 {
58
59
60
61
62
63
64
65 var argOnlyFlag string
66 if !base.InGOFLAGS("-m") && *versionM {
67 argOnlyFlag = "-m"
68 } else if !base.InGOFLAGS("-v") && *versionV {
69 argOnlyFlag = "-v"
70 }
71 if argOnlyFlag != "" {
72 fmt.Fprintf(os.Stderr, "go: 'go version' only accepts %s flag with arguments\n", argOnlyFlag)
73 base.SetExitStatus(2)
74 return
75 }
76 fmt.Printf("go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
77 return
78 }
79
80 for _, arg := range args {
81 info, err := os.Stat(arg)
82 if err != nil {
83 fmt.Fprintf(os.Stderr, "%v\n", err)
84 base.SetExitStatus(1)
85 continue
86 }
87 if info.IsDir() {
88 scanDir(arg)
89 } else {
90 scanFile(arg, info, true)
91 }
92 }
93 }
94
95
96 func scanDir(dir string) {
97 filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
98 if d.Type().IsRegular() || d.Type()&fs.ModeSymlink != 0 {
99 info, err := d.Info()
100 if err != nil {
101 if *versionV {
102 fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
103 }
104 return nil
105 }
106 scanFile(path, info, *versionV)
107 }
108 return nil
109 })
110 }
111
112
113 func isExe(file string, info fs.FileInfo) bool {
114 if runtime.GOOS == "windows" {
115 return strings.HasSuffix(strings.ToLower(file), ".exe")
116 }
117 return info.Mode().IsRegular() && info.Mode()&0111 != 0
118 }
119
120
121
122
123
124 func scanFile(file string, info fs.FileInfo, mustPrint bool) {
125 if info.Mode()&fs.ModeSymlink != 0 {
126
127 i, err := os.Stat(file)
128 if err != nil || !i.Mode().IsRegular() {
129 if mustPrint {
130 fmt.Fprintf(os.Stderr, "%s: symlink\n", file)
131 }
132 return
133 }
134 info = i
135 }
136
137 if !isExe(file, info) {
138 if mustPrint {
139 fmt.Fprintf(os.Stderr, "%s: not executable file\n", file)
140 }
141 return
142 }
143
144 bi, err := buildinfo.ReadFile(file)
145 if err != nil {
146 if mustPrint {
147 if pathErr := (*os.PathError)(nil); errors.As(err, &pathErr) && filepath.Clean(pathErr.Path) == filepath.Clean(file) {
148 fmt.Fprintf(os.Stderr, "%v\n", file)
149 } else {
150 fmt.Fprintf(os.Stderr, "%s: %v\n", file, err)
151 }
152 }
153 return
154 }
155
156 fmt.Printf("%s: %s\n", file, bi.GoVersion)
157 bi.GoVersion = ""
158 mod := bi.String()
159 if *versionM && len(mod) > 0 {
160 fmt.Printf("\t%s\n", strings.ReplaceAll(mod[:len(mod)-1], "\n", "\n\t"))
161 }
162 }
163
View as plain text