summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/version/version.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/internal/version/version.go')
-rw-r--r--src/cmd/go/internal/version/version.go168
1 files changed, 168 insertions, 0 deletions
diff --git a/src/cmd/go/internal/version/version.go b/src/cmd/go/internal/version/version.go
new file mode 100644
index 0000000..a0f6123
--- /dev/null
+++ b/src/cmd/go/internal/version/version.go
@@ -0,0 +1,168 @@
+// 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 version implements the “go version” command.
+package version
+
+import (
+ "context"
+ "debug/buildinfo"
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+
+ "cmd/go/internal/base"
+)
+
+var CmdVersion = &base.Command{
+ UsageLine: "go version [-m] [-v] [file ...]",
+ Short: "print Go version",
+ Long: `Version prints the build information for Go binary files.
+
+Go version reports the Go version used to build each of the named files.
+
+If no files are named on the command line, go version prints its own
+version information.
+
+If a directory is named, go version walks that directory, recursively,
+looking for recognized Go binaries and reporting their versions.
+By default, go version does not report unrecognized files found
+during a directory scan. The -v flag causes it to report unrecognized files.
+
+The -m flag causes go version to print each file's embedded
+module version information, when available. In the output, the module
+information consists of multiple lines following the version line, each
+indented by a leading tab character.
+
+See also: go doc runtime/debug.BuildInfo.
+`,
+}
+
+func init() {
+ base.AddChdirFlag(&CmdVersion.Flag)
+ CmdVersion.Run = runVersion // break init cycle
+}
+
+var (
+ versionM = CmdVersion.Flag.Bool("m", false, "")
+ versionV = CmdVersion.Flag.Bool("v", false, "")
+)
+
+func runVersion(ctx context.Context, cmd *base.Command, args []string) {
+ if len(args) == 0 {
+ // If any of this command's flags were passed explicitly, error
+ // out, because they only make sense with arguments.
+ //
+ // Don't error if the flags came from GOFLAGS, since that can be
+ // a reasonable use case. For example, imagine GOFLAGS=-v to
+ // turn "verbose mode" on for all Go commands, which should not
+ // break "go version".
+ var argOnlyFlag string
+ if !base.InGOFLAGS("-m") && *versionM {
+ argOnlyFlag = "-m"
+ } else if !base.InGOFLAGS("-v") && *versionV {
+ argOnlyFlag = "-v"
+ }
+ if argOnlyFlag != "" {
+ fmt.Fprintf(os.Stderr, "go: 'go version' only accepts %s flag with arguments\n", argOnlyFlag)
+ base.SetExitStatus(2)
+ return
+ }
+ fmt.Printf("go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
+ return
+ }
+
+ for _, arg := range args {
+ info, err := os.Stat(arg)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ base.SetExitStatus(1)
+ continue
+ }
+ if info.IsDir() {
+ scanDir(arg)
+ } else {
+ scanFile(arg, info, true)
+ }
+ }
+}
+
+// scanDir scans a directory for binary to run scanFile on.
+func scanDir(dir string) {
+ filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
+ if d.Type().IsRegular() || d.Type()&fs.ModeSymlink != 0 {
+ info, err := d.Info()
+ if err != nil {
+ if *versionV {
+ fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
+ }
+ return nil
+ }
+ scanFile(path, info, *versionV)
+ }
+ return nil
+ })
+}
+
+// isGoBinaryCandidate reports whether the file is a candidate to be a Go binary.
+func isGoBinaryCandidate(file string, info fs.FileInfo) bool {
+ if info.Mode().IsRegular() && info.Mode()&0111 != 0 {
+ return true
+ }
+ name := strings.ToLower(file)
+ switch filepath.Ext(name) {
+ case ".so", ".exe", ".dll":
+ return true
+ default:
+ return strings.Contains(name, ".so.")
+ }
+}
+
+// scanFile scans file to try to report the Go and module versions.
+// If mustPrint is true, scanFile will report any error reading file.
+// Otherwise (mustPrint is false, because scanFile is being called
+// by scanDir) scanFile prints nothing for non-Go binaries.
+func scanFile(file string, info fs.FileInfo, mustPrint bool) {
+ if info.Mode()&fs.ModeSymlink != 0 {
+ // Accept file symlinks only.
+ i, err := os.Stat(file)
+ if err != nil || !i.Mode().IsRegular() {
+ if mustPrint {
+ fmt.Fprintf(os.Stderr, "%s: symlink\n", file)
+ }
+ return
+ }
+ info = i
+ }
+
+ bi, err := buildinfo.ReadFile(file)
+ if err != nil {
+ if mustPrint {
+ if pathErr := (*os.PathError)(nil); errors.As(err, &pathErr) && filepath.Clean(pathErr.Path) == filepath.Clean(file) {
+ fmt.Fprintf(os.Stderr, "%v\n", file)
+ } else {
+
+ // Skip errors for non-Go binaries.
+ // buildinfo.ReadFile errors are not fine-grained enough
+ // to know if the file is a Go binary or not,
+ // so try to infer it from the file mode and extension.
+ if isGoBinaryCandidate(file, info) {
+ fmt.Fprintf(os.Stderr, "%s: %v\n", file, err)
+ }
+ }
+ }
+ return
+ }
+
+ fmt.Printf("%s: %s\n", file, bi.GoVersion)
+ bi.GoVersion = "" // suppress printing go version again
+ mod := bi.String()
+ if *versionM && len(mod) > 0 {
+ fmt.Printf("\t%s\n", strings.ReplaceAll(mod[:len(mod)-1], "\n", "\n\t"))
+ }
+}