version.go (2489B)
1 // Copyright 2020 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 gocommand 6 7 import ( 8 "context" 9 "fmt" 10 "regexp" 11 "slices" 12 "strings" 13 ) 14 15 // GoVersion reports the minor version number of the highest release 16 // tag built into the go command on the PATH. 17 // 18 // Note that this may be higher than the version of the go tool used 19 // to build this application, and thus the versions of the standard 20 // go/{scanner,parser,ast,types} packages that are linked into it. 21 // In that case, callers should either downgrade to the version of 22 // go used to build the application, or report an error that the 23 // application is too old to use the go command on the PATH. 24 func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) { 25 inv.Verb = "list" 26 inv.Args = []string{"-e", "-f", `{{context.ReleaseTags}}`, `--`, `unsafe`} 27 inv.BuildFlags = nil // This is not a build command. 28 inv.ModFlag = "" 29 inv.ModFile = "" 30 // Set GO111MODULE=off so that we are immune to errors in go.{work,mod}. 31 // Unfortunately, this breaks the Go 1.21+ toolchain directive and 32 // may affect the set of ReleaseTags; see #68495. 33 inv.Env = append(inv.Env[:len(inv.Env):len(inv.Env)], "GO111MODULE=off") 34 35 stdoutBytes, err := r.Run(ctx, inv) 36 if err != nil { 37 return 0, err 38 } 39 stdout := stdoutBytes.String() 40 if len(stdout) < 3 { 41 return 0, fmt.Errorf("bad ReleaseTags output: %q", stdout) 42 } 43 // Split up "[go1.1 go1.15]" and return highest go1.X value. 44 tags := strings.Fields(stdout[1 : len(stdout)-2]) 45 for _, tag := range slices.Backward(tags) { 46 var version int 47 if _, err := fmt.Sscanf(tag, "go1.%d", &version); err != nil { 48 continue 49 } 50 return version, nil 51 } 52 return 0, fmt.Errorf("no parseable ReleaseTags in %v", tags) 53 } 54 55 // GoVersionOutput returns the complete output of the go version command. 56 func GoVersionOutput(ctx context.Context, inv Invocation, r *Runner) (string, error) { 57 inv.Verb = "version" 58 goVersion, err := r.Run(ctx, inv) 59 if err != nil { 60 return "", err 61 } 62 return goVersion.String(), nil 63 } 64 65 // ParseGoVersionOutput extracts the Go version string 66 // from the output of the "go version" command. 67 // Given an unrecognized form, it returns an empty string. 68 func ParseGoVersionOutput(data string) string { 69 re := regexp.MustCompile(`^go version (go\S+|devel \S+)`) 70 m := re.FindStringSubmatch(data) 71 if len(m) != 2 { 72 return "" // unrecognized version 73 } 74 return m[1] 75 }