Source file src/cmd/go/internal/version/version.go

     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  	"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 // break init cycle
    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  		// If any of this command's flags were passed explicitly, error
    59  		// out, because they only make sense with arguments.
    60  		//
    61  		// Don't error if the flags came from GOFLAGS, since that can be
    62  		// a reasonable use case. For example, imagine GOFLAGS=-v to
    63  		// turn "verbose mode" on for all Go commands, which should not
    64  		// break "go version".
    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  // scanDir scans a directory for executables to run scanFile on.
    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  // isExe reports whether the file should be considered executable.
   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  // scanFile scans file to try to report the Go and module versions.
   121  // If mustPrint is true, scanFile will report any error reading file.
   122  // Otherwise (mustPrint is false, because scanFile is being called
   123  // by scanDir) scanFile prints nothing for non-Go executables.
   124  func scanFile(file string, info fs.FileInfo, mustPrint bool) {
   125  	if info.Mode()&fs.ModeSymlink != 0 {
   126  		// Accept file symlinks only.
   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 = "" // suppress printing go version again
   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