github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/cmd/fetch_repo/module.go (about) 1 /* Copyright 2019 The Bazel Authors. All rights reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package main 17 18 import ( 19 "fmt" 20 "os" 21 22 "golang.org/x/mod/sumdb/dirhash" 23 ) 24 25 func fetchModule(dest, importpath, version, sum string) error { 26 // Check that version is a complete semantic version or pseudo-version. 27 if _, ok := parse(version); !ok { 28 return fmt.Errorf("%q is not a valid semantic version", version) 29 } else if isSemverPrefix(version) { 30 return fmt.Errorf("-version must be a complete semantic version. %q is a prefix.", version) 31 } 32 33 // Download the module. In Go 1.11, this command must be run in a module, 34 // so we create a dummy module in the current directory (which should be 35 // empty). 36 w, err := os.OpenFile("go.mod", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o666) 37 if err != nil { 38 return fmt.Errorf("error creating temporary go.mod: %v", err) 39 } 40 _, err = fmt.Fprintln(w, "module example.com/temporary/module/for/fetch_repo/download") 41 if err != nil { 42 w.Close() 43 return fmt.Errorf("error writing temporary go.mod: %v", err) 44 } 45 if err := w.Close(); err != nil { 46 return fmt.Errorf("error closing temporary go.mod: %v", err) 47 } 48 49 dl := GoModDownloadResult{} 50 err = runGoModDownload(&dl, dest, importpath, version) 51 os.Remove("go.mod") 52 if err != nil { 53 return err 54 } 55 56 // Copy the module to the destination. 57 if err := copyTree(dest, dl.Dir); err != nil { 58 return fmt.Errorf("failed copying repo: %w", err) 59 } 60 61 // Verify sum of the directory itself against the go.sum. 62 repoSum, err := dirhash.HashDir(dest, importpath+"@"+version, dirhash.Hash1) 63 if err != nil { 64 return fmt.Errorf("failed computing sum: %w", err) 65 } 66 67 if repoSum != sum { 68 if goModCache := os.Getenv("GOMODCACHE"); goModCache != "" { 69 return fmt.Errorf("resulting module with sum %s; expected sum %s, Please try clearing your module cache directory %q", repoSum, sum, goModCache) 70 } 71 return fmt.Errorf("resulting module with sum %s; expected sum %s", repoSum, sum) 72 } 73 74 return nil 75 } 76 77 // semantic version parsing functions below this point were copied from 78 // cmd/go/internal/semver and cmd/go/internal/modload at go1.12beta2. 79 80 // parsed returns the parsed form of a semantic version string. 81 type parsed struct { 82 major string 83 minor string 84 patch string 85 short string 86 prerelease string 87 build string 88 err string 89 } 90 91 func parse(v string) (p parsed, ok bool) { 92 if v == "" || v[0] != 'v' { 93 p.err = "missing v prefix" 94 return 95 } 96 p.major, v, ok = parseInt(v[1:]) 97 if !ok { 98 p.err = "bad major version" 99 return 100 } 101 if v == "" { 102 p.minor = "0" 103 p.patch = "0" 104 p.short = ".0.0" 105 return 106 } 107 if v[0] != '.' { 108 p.err = "bad minor prefix" 109 ok = false 110 return 111 } 112 p.minor, v, ok = parseInt(v[1:]) 113 if !ok { 114 p.err = "bad minor version" 115 return 116 } 117 if v == "" { 118 p.patch = "0" 119 p.short = ".0" 120 return 121 } 122 if v[0] != '.' { 123 p.err = "bad patch prefix" 124 ok = false 125 return 126 } 127 p.patch, v, ok = parseInt(v[1:]) 128 if !ok { 129 p.err = "bad patch version" 130 return 131 } 132 if len(v) > 0 && v[0] == '-' { 133 p.prerelease, v, ok = parsePrerelease(v) 134 if !ok { 135 p.err = "bad prerelease" 136 return 137 } 138 } 139 if len(v) > 0 && v[0] == '+' { 140 p.build, v, ok = parseBuild(v) 141 if !ok { 142 p.err = "bad build" 143 return 144 } 145 } 146 if v != "" { 147 p.err = "junk on end" 148 ok = false 149 return 150 } 151 ok = true 152 return 153 } 154 155 func parseInt(v string) (t, rest string, ok bool) { 156 if v == "" { 157 return 158 } 159 if v[0] < '0' || '9' < v[0] { 160 return 161 } 162 i := 1 163 for i < len(v) && '0' <= v[i] && v[i] <= '9' { 164 i++ 165 } 166 if v[0] == '0' && i != 1 { 167 return 168 } 169 return v[:i], v[i:], true 170 } 171 172 func parsePrerelease(v string) (t, rest string, ok bool) { 173 // "A pre-release version MAY be denoted by appending a hyphen and 174 // a series of dot separated identifiers immediately following the patch version. 175 // Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. 176 // Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes." 177 if v == "" || v[0] != '-' { 178 return 179 } 180 i := 1 181 start := 1 182 for i < len(v) && v[i] != '+' { 183 if !isIdentChar(v[i]) && v[i] != '.' { 184 return 185 } 186 if v[i] == '.' { 187 if start == i || isBadNum(v[start:i]) { 188 return 189 } 190 start = i + 1 191 } 192 i++ 193 } 194 if start == i || isBadNum(v[start:i]) { 195 return 196 } 197 return v[:i], v[i:], true 198 } 199 200 func parseBuild(v string) (t, rest string, ok bool) { 201 if v == "" || v[0] != '+' { 202 return 203 } 204 i := 1 205 start := 1 206 for i < len(v) { 207 if !isIdentChar(v[i]) && v[i] != '.' { 208 return 209 } 210 if v[i] == '.' { 211 if start == i { 212 return 213 } 214 start = i + 1 215 } 216 i++ 217 } 218 if start == i { 219 return 220 } 221 return v[:i], v[i:], true 222 } 223 224 func isIdentChar(c byte) bool { 225 return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' 226 } 227 228 func isBadNum(v string) bool { 229 i := 0 230 for i < len(v) && '0' <= v[i] && v[i] <= '9' { 231 i++ 232 } 233 return i == len(v) && i > 1 && v[0] == '0' 234 } 235 236 // isSemverPrefix reports whether v is a semantic version prefix: v1 or v1.2 (not v1.2.3). 237 // The caller is assumed to have checked that semver.IsValid(v) is true. 238 func isSemverPrefix(v string) bool { 239 dots := 0 240 for i := 0; i < len(v); i++ { 241 switch v[i] { 242 case '-', '+': 243 return false 244 case '.': 245 dots++ 246 if dots >= 2 { 247 return false 248 } 249 } 250 } 251 return true 252 }