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 }