golang.org/x/build@v0.0.0-20240506185731-218518f32b70/maintner/maintnerd/maintapi/version/version.go (about)

     1  // Copyright 2019 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 logic to parse version
     6  // of Go tags and release branches.
     7  package version
     8  
     9  import (
    10  	"strings"
    11  )
    12  
    13  // ParseTag parses the major-minor-patch version triplet
    14  // from goX, goX.Y, or goX.Y.Z tag names,
    15  // and reports whether the tag name is valid.
    16  //
    17  // Tags with suffixes like "go1.2beta3" or "go1.2rc1"
    18  // are currently not supported, and get rejected.
    19  //
    20  // For example, "go1" is parsed as version 1.0.0,
    21  // "go1.2" is parsed as version 1.2.0,
    22  // and "go1.2.3" is parsed as version 1.2.3.
    23  func ParseTag(tagName string) (major, minor, patch int, ok bool) {
    24  	const prefix = "go"
    25  	if !strings.HasPrefix(tagName, prefix) {
    26  		return 0, 0, 0, false
    27  	}
    28  	v := strings.SplitN(tagName[len(prefix):], ".", 4)
    29  	if len(v) > 3 {
    30  		return 0, 0, 0, false
    31  	}
    32  	major, ok = parse0To999(v[0])
    33  	if !ok || major == 0 {
    34  		return 0, 0, 0, false
    35  	}
    36  	if len(v) == 2 || len(v) == 3 {
    37  		minor, ok = parse0To999(v[1])
    38  		if !ok {
    39  			return 0, 0, 0, false
    40  		}
    41  	}
    42  	if len(v) == 3 {
    43  		patch, ok = parse0To999(v[2])
    44  		if !ok {
    45  			return 0, 0, 0, false
    46  		}
    47  	}
    48  	return major, minor, patch, true
    49  }
    50  
    51  // ParseReleaseBranch parses the major-minor version pair
    52  // from release-branch.goX or release-branch.goX.Y release branch names,
    53  // and reports whether the release branch name is valid.
    54  //
    55  // For example, "release-branch.go1" is parsed as version 1.0,
    56  // and "release-branch.go1.2" is parsed as version 1.2.
    57  func ParseReleaseBranch(branchName string) (major, minor int, ok bool) {
    58  	const prefix = "release-branch.go"
    59  	if !strings.HasPrefix(branchName, prefix) {
    60  		return 0, 0, false
    61  	}
    62  	if strings.HasSuffix(branchName, ".0") {
    63  		// Trailing zero version components must be omitted in Go release branches,
    64  		// so reject if we see one.
    65  		return 0, 0, false
    66  	}
    67  	dottedNum := branchName[len(prefix):] // "1", "1.1", "2", "2.5"
    68  	numDot := strings.Count(dottedNum, ".")
    69  	if numDot > 1 {
    70  		return 0, 0, false
    71  	}
    72  	majorStr, minorStr := dottedNum, ""
    73  	if numDot == 1 {
    74  		dot := strings.Index(dottedNum, ".")
    75  		majorStr, minorStr = dottedNum[:dot], dottedNum[dot+1:]
    76  	}
    77  	major, ok = parse0To999(majorStr)
    78  	if !ok || major == 0 {
    79  		return 0, 0, false
    80  	}
    81  	if numDot > 0 {
    82  		minor, ok = parse0To999(minorStr)
    83  		if !ok {
    84  			return 0, 0, false
    85  		}
    86  	}
    87  	return major, minor, true
    88  }
    89  
    90  // parse0To999 converts the canonical ASCII string representation
    91  // of a number in the range [0, 999] to its integer form.
    92  // strconv.Itoa(n) will equal to s if and only if ok is true.
    93  //
    94  // It's similar to strconv.Atoi, except it doesn't permit
    95  // negative numbers, leading '+'/'-' signs, leading zeros,
    96  // or other potential valid but non-canonical string
    97  // representations of numbers.
    98  func parse0To999(s string) (n int, ok bool) {
    99  	if len(s) < 1 || 3 < len(s) {
   100  		return 0, false
   101  	}
   102  	if len(s) > 1 && s[0] == '0' {
   103  		// Leading zeros are rejected.
   104  		return 0, false
   105  	}
   106  	for _, c := range []byte(s) {
   107  		if c < '0' || '9' < c {
   108  			return 0, false
   109  		}
   110  		n = n*10 + int(c-'0')
   111  	}
   112  	return n, true
   113  }
   114  
   115  // Go1PointX returns the second number in a string that looks like a Go
   116  // version, i.e. X in anything that starts with "go1.X".
   117  func Go1PointX(version string) (int, bool) {
   118  	const prefix = "go1."
   119  	if !strings.HasPrefix(version, prefix) {
   120  		return 0, false
   121  	}
   122  	numberEnd := len(prefix)
   123  	for ; numberEnd < len(version) && version[numberEnd] >= '0' && version[numberEnd] <= '9'; numberEnd++ {
   124  	}
   125  	x, ok := parse0To999(version[len(prefix):numberEnd])
   126  	if !ok {
   127  		return 0, false
   128  	}
   129  	return x, true
   130  }