github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/goversion/go_version.go (about)

     1  package goversion
     2  
     3  import (
     4  	"fmt"
     5  	"os/exec"
     6  	"strconv"
     7  	"strings"
     8  )
     9  
    10  // GoVersion represents the Go version of
    11  // the Go compiler version used to compile
    12  // the target binary.
    13  type GoVersion struct {
    14  	Major     int
    15  	Minor     int
    16  	Rev       int // revision number or negative number for beta and rc releases
    17  	Proposal  string
    18  	Toolchain string
    19  }
    20  
    21  const (
    22  	betaStart = -1000
    23  	betaEnd   = -2000
    24  )
    25  
    26  func betaRev(beta int) int {
    27  	return beta + betaEnd
    28  }
    29  
    30  func rcRev(rc int) int {
    31  	return rc + betaStart
    32  }
    33  
    34  var (
    35  	GoVer18Beta = GoVersion{1, 8, betaRev(0), "", ""}
    36  )
    37  
    38  // Parse parses a go version string
    39  func Parse(ver string) (GoVersion, bool) {
    40  	var r GoVersion
    41  	var err1, err2, err3 error
    42  
    43  	if strings.HasPrefix(ver, "devel") {
    44  		return GoVersion{-1, 0, 0, "", ""}, true
    45  	}
    46  
    47  	if strings.HasPrefix(ver, "go") {
    48  		ver := strings.Split(ver, " ")[0]
    49  		v := strings.SplitN(ver[2:], ".", 4)
    50  		switch len(v) {
    51  		case 2:
    52  			r.Major, err1 = strconv.Atoi(v[0])
    53  			var vr []string
    54  
    55  			if vr = strings.SplitN(v[1], "beta", 2); len(vr) == 2 {
    56  				// old beta releases goX.YbetaZ
    57  				var beta int
    58  				beta, err3 = strconv.Atoi(vr[1])
    59  				r.Rev = betaRev(beta)
    60  			} else if vr = strings.SplitN(v[1], "b", 2); len(vr) == 2 {
    61  				// old boringcrypto version goX.YbZ
    62  				if _, err := strconv.Atoi(vr[1]); err != nil {
    63  					return GoVersion{}, false
    64  				}
    65  			} else {
    66  				vr = strings.SplitN(v[1], "rc", 2)
    67  				if len(vr) == 2 {
    68  					// rc release goX.YrcZ
    69  					var rc int
    70  					rc, err3 = strconv.Atoi(vr[1])
    71  					r.Rev = rcRev(rc)
    72  				} else {
    73  					r.Minor, err2 = strconv.Atoi(v[1])
    74  					if err2 != nil {
    75  						return GoVersion{}, false
    76  					}
    77  					return r, true
    78  				}
    79  			}
    80  
    81  			// old major release (if none of the options above apply) goX.Y
    82  
    83  			r.Minor, err2 = strconv.Atoi(vr[0])
    84  			r.Proposal = ""
    85  
    86  			if err1 != nil || err2 != nil || err3 != nil {
    87  				return GoVersion{}, false
    88  			}
    89  
    90  			return r, true
    91  
    92  		case 3:
    93  
    94  			r.Major, err1 = strconv.Atoi(v[0])
    95  			r.Minor, err2 = strconv.Atoi(v[1])
    96  
    97  			if vr := strings.SplitN(v[2], "-", 2); len(vr) == 2 {
    98  				// minor version with toolchain modifier goX.Y.Z-anything
    99  				r.Rev, err3 = strconv.Atoi(vr[0])
   100  				r.Toolchain = vr[1]
   101  			} else if vr := strings.SplitN(v[2], "b", 2); len(vr) == 2 {
   102  				// old boringcrypto version goX.Y.ZbW
   103  				r.Rev, err3 = strconv.Atoi(vr[0])
   104  			} else {
   105  				// minor version goX.Y.Z
   106  				r.Rev, err3 = strconv.Atoi(v[2])
   107  			}
   108  
   109  			r.Proposal = ""
   110  			if err1 != nil || err2 != nil || err3 != nil {
   111  				return GoVersion{}, false
   112  			}
   113  
   114  			return r, true
   115  
   116  		case 4:
   117  
   118  			// old proposal release goX.Y.Z.anything
   119  
   120  			r.Major, err1 = strconv.Atoi(v[0])
   121  			r.Minor, err2 = strconv.Atoi(v[1])
   122  			r.Rev, err3 = strconv.Atoi(v[2])
   123  			r.Proposal = v[3]
   124  			if err1 != nil || err2 != nil || err3 != nil || r.Proposal == "" {
   125  				return GoVersion{}, false
   126  			}
   127  
   128  			return r, true
   129  
   130  		default:
   131  			return GoVersion{}, false
   132  		}
   133  	}
   134  
   135  	return GoVersion{}, false
   136  }
   137  
   138  // AfterOrEqual returns whether one GoVersion is after or
   139  // equal to the other.
   140  func (v *GoVersion) AfterOrEqual(b GoVersion) bool {
   141  	if v.Major < b.Major {
   142  		return false
   143  	} else if v.Major > b.Major {
   144  		return true
   145  	}
   146  
   147  	if v.Minor < b.Minor {
   148  		return false
   149  	} else if v.Minor > b.Minor {
   150  		return true
   151  	}
   152  
   153  	if v.Rev < b.Rev {
   154  		return false
   155  	} else if v.Rev > b.Rev {
   156  		return true
   157  	}
   158  
   159  	return true
   160  }
   161  
   162  // IsDevel returns whether the GoVersion
   163  // is a development version.
   164  func (v *GoVersion) IsDevel() bool {
   165  	return v.Major < 0
   166  }
   167  
   168  func (v *GoVersion) String() string {
   169  	switch {
   170  	case v.Rev < betaStart:
   171  		// beta version
   172  		return fmt.Sprintf("go%d.%dbeta%d", v.Major, v.Minor, v.Rev-betaEnd)
   173  	case v.Rev < 0:
   174  		// rc version
   175  		return fmt.Sprintf("go%d.%drc%d", v.Major, v.Minor, v.Rev-betaStart)
   176  	case v.Proposal != "":
   177  		// with proposal
   178  		return fmt.Sprintf("go%d.%d.%d.%s", v.Major, v.Minor, v.Rev, v.Proposal)
   179  	case v.Rev == 0 && v.Minor < 21:
   180  		// old major version
   181  		return fmt.Sprintf("go%d.%d", v.Major, v.Minor)
   182  	case v.Toolchain != "":
   183  		return fmt.Sprintf("go%d.%d.%d-%s", v.Major, v.Minor, v.Rev, v.Toolchain)
   184  	default:
   185  		// post go1.21 major version or minor version
   186  		return fmt.Sprintf("go%d.%d.%d", v.Major, v.Minor, v.Rev)
   187  	}
   188  }
   189  
   190  const goVersionPrefix = "go version "
   191  
   192  // Installed runs "go version" and parses the output
   193  func Installed() (GoVersion, bool) {
   194  	out, err := exec.Command("go", "version").CombinedOutput()
   195  	if err != nil {
   196  		return GoVersion{}, false
   197  	}
   198  
   199  	s := string(out)
   200  
   201  	if !strings.HasPrefix(s, goVersionPrefix) {
   202  		return GoVersion{}, false
   203  	}
   204  
   205  	return Parse(s[len(goVersionPrefix):])
   206  }
   207  
   208  // VersionAfterOrEqual checks that version (as returned by runtime.Version()
   209  // or go version) is major.minor or a later version, or a development
   210  // version.
   211  func VersionAfterOrEqual(version string, major, minor int) bool {
   212  	return VersionAfterOrEqualRev(version, major, minor, -1)
   213  }
   214  
   215  // VersionAfterOrEqualRev checks that version (as returned by runtime.Version()
   216  // or go version) is major.minor or a later version, or a development
   217  // version.
   218  func VersionAfterOrEqualRev(version string, major, minor, rev int) bool {
   219  	ver, _ := Parse(version)
   220  	if ver.IsDevel() {
   221  		return true
   222  	}
   223  	return ver.AfterOrEqual(GoVersion{major, minor, rev, "", ""})
   224  }
   225  
   226  const producerVersionPrefix = "Go cmd/compile "
   227  
   228  // ProducerAfterOrEqual checks that the DW_AT_producer version is
   229  // major.minor or a later version, or a development version.
   230  func ProducerAfterOrEqual(producer string, major, minor int) bool {
   231  	ver := ParseProducer(producer)
   232  	if ver.IsDevel() {
   233  		return true
   234  	}
   235  	return ver.AfterOrEqual(GoVersion{major, minor, 0, "", ""})
   236  }
   237  
   238  func ParseProducer(producer string) GoVersion {
   239  	producer = strings.TrimPrefix(producer, producerVersionPrefix)
   240  	ver, _ := Parse(producer)
   241  	return ver
   242  }