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