]>
Commit | Line | Data |
---|---|---|
dd931d9b ILT |
1 | // Copyright 2018 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 modfetch | |
6 | ||
7 | import ( | |
8 | "bytes" | |
9 | "encoding/json" | |
d79a22ed | 10 | "errors" |
dd931d9b | 11 | "fmt" |
4f4a855d | 12 | "io" |
dd931d9b ILT |
13 | "io/ioutil" |
14 | "os" | |
15 | "path/filepath" | |
16 | "strings" | |
17 | ||
18 | "cmd/go/internal/base" | |
aa8901e9 | 19 | "cmd/go/internal/cfg" |
4f4a855d | 20 | "cmd/go/internal/lockedfile" |
dd931d9b | 21 | "cmd/go/internal/modfetch/codehost" |
dd931d9b | 22 | "cmd/go/internal/par" |
4f4a855d | 23 | "cmd/go/internal/renameio" |
dd931d9b | 24 | |
5a8ea165 ILT |
25 | "golang.org/x/mod/module" |
26 | "golang.org/x/mod/semver" | |
27 | ) | |
dd931d9b ILT |
28 | |
29 | var PkgMod string // $GOPATH/pkg/mod; set by package modload | |
30 | ||
31 | func cacheDir(path string) (string, error) { | |
32 | if PkgMod == "" { | |
33 | return "", fmt.Errorf("internal error: modfetch.PkgMod not set") | |
34 | } | |
5a8ea165 | 35 | enc, err := module.EscapePath(path) |
dd931d9b ILT |
36 | if err != nil { |
37 | return "", err | |
38 | } | |
39 | return filepath.Join(PkgMod, "cache/download", enc, "/@v"), nil | |
40 | } | |
41 | ||
42 | func CachePath(m module.Version, suffix string) (string, error) { | |
43 | dir, err := cacheDir(m.Path) | |
44 | if err != nil { | |
45 | return "", err | |
46 | } | |
47 | if !semver.IsValid(m.Version) { | |
48 | return "", fmt.Errorf("non-semver module version %q", m.Version) | |
49 | } | |
50 | if module.CanonicalVersion(m.Version) != m.Version { | |
51 | return "", fmt.Errorf("non-canonical module version %q", m.Version) | |
52 | } | |
5a8ea165 | 53 | encVer, err := module.EscapeVersion(m.Version) |
dd931d9b ILT |
54 | if err != nil { |
55 | return "", err | |
56 | } | |
57 | return filepath.Join(dir, encVer+"."+suffix), nil | |
58 | } | |
59 | ||
d79a22ed ILT |
60 | // DownloadDir returns the directory to which m should have been downloaded. |
61 | // An error will be returned if the module path or version cannot be escaped. | |
62 | // An error satisfying errors.Is(err, os.ErrNotExist) will be returned | |
63 | // along with the directory if the directory does not exist or if the directory | |
64 | // is not completely populated. | |
dd931d9b ILT |
65 | func DownloadDir(m module.Version) (string, error) { |
66 | if PkgMod == "" { | |
67 | return "", fmt.Errorf("internal error: modfetch.PkgMod not set") | |
68 | } | |
5a8ea165 | 69 | enc, err := module.EscapePath(m.Path) |
dd931d9b ILT |
70 | if err != nil { |
71 | return "", err | |
72 | } | |
73 | if !semver.IsValid(m.Version) { | |
74 | return "", fmt.Errorf("non-semver module version %q", m.Version) | |
75 | } | |
76 | if module.CanonicalVersion(m.Version) != m.Version { | |
77 | return "", fmt.Errorf("non-canonical module version %q", m.Version) | |
78 | } | |
5a8ea165 | 79 | encVer, err := module.EscapeVersion(m.Version) |
dd931d9b ILT |
80 | if err != nil { |
81 | return "", err | |
82 | } | |
d79a22ed ILT |
83 | |
84 | dir := filepath.Join(PkgMod, enc+"@"+encVer) | |
85 | if fi, err := os.Stat(dir); os.IsNotExist(err) { | |
86 | return dir, err | |
87 | } else if err != nil { | |
88 | return dir, &DownloadDirPartialError{dir, err} | |
89 | } else if !fi.IsDir() { | |
90 | return dir, &DownloadDirPartialError{dir, errors.New("not a directory")} | |
91 | } | |
92 | partialPath, err := CachePath(m, "partial") | |
93 | if err != nil { | |
94 | return dir, err | |
95 | } | |
96 | if _, err := os.Stat(partialPath); err == nil { | |
97 | return dir, &DownloadDirPartialError{dir, errors.New("not completely extracted")} | |
98 | } else if !os.IsNotExist(err) { | |
99 | return dir, err | |
100 | } | |
101 | return dir, nil | |
102 | } | |
103 | ||
104 | // DownloadDirPartialError is returned by DownloadDir if a module directory | |
105 | // exists but was not completely populated. | |
106 | // | |
107 | // DownloadDirPartialError is equivalent to os.ErrNotExist. | |
108 | type DownloadDirPartialError struct { | |
109 | Dir string | |
110 | Err error | |
dd931d9b ILT |
111 | } |
112 | ||
d79a22ed ILT |
113 | func (e *DownloadDirPartialError) Error() string { return fmt.Sprintf("%s: %v", e.Dir, e.Err) } |
114 | func (e *DownloadDirPartialError) Is(err error) bool { return err == os.ErrNotExist } | |
115 | ||
4f4a855d ILT |
116 | // lockVersion locks a file within the module cache that guards the downloading |
117 | // and extraction of the zipfile for the given module version. | |
118 | func lockVersion(mod module.Version) (unlock func(), err error) { | |
119 | path, err := CachePath(mod, "lock") | |
120 | if err != nil { | |
121 | return nil, err | |
122 | } | |
123 | if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { | |
124 | return nil, err | |
125 | } | |
126 | return lockedfile.MutexAt(path).Lock() | |
127 | } | |
128 | ||
5a8ea165 ILT |
129 | // SideLock locks a file within the module cache that that previously guarded |
130 | // edits to files outside the cache, such as go.sum and go.mod files in the | |
131 | // user's working directory. | |
132 | // If err is nil, the caller MUST eventually call the unlock function. | |
133 | func SideLock() (unlock func(), err error) { | |
4f4a855d ILT |
134 | if PkgMod == "" { |
135 | base.Fatalf("go: internal error: modfetch.PkgMod not set") | |
136 | } | |
5a8ea165 | 137 | |
4f4a855d ILT |
138 | path := filepath.Join(PkgMod, "cache", "lock") |
139 | if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { | |
5a8ea165 | 140 | return nil, fmt.Errorf("failed to create cache directory: %w", err) |
4f4a855d | 141 | } |
5a8ea165 ILT |
142 | |
143 | return lockedfile.MutexAt(path).Lock() | |
4f4a855d ILT |
144 | } |
145 | ||
dd931d9b ILT |
146 | // A cachingRepo is a cache around an underlying Repo, |
147 | // avoiding redundant calls to ModulePath, Versions, Stat, Latest, and GoMod (but not Zip). | |
148 | // It is also safe for simultaneous use by multiple goroutines | |
149 | // (so that it can be returned from Lookup multiple times). | |
150 | // It serializes calls to the underlying Repo. | |
151 | type cachingRepo struct { | |
152 | path string | |
153 | cache par.Cache // cache for all operations | |
154 | r Repo | |
155 | } | |
156 | ||
157 | func newCachingRepo(r Repo) *cachingRepo { | |
158 | return &cachingRepo{ | |
159 | r: r, | |
160 | path: r.ModulePath(), | |
161 | } | |
162 | } | |
163 | ||
164 | func (r *cachingRepo) ModulePath() string { | |
165 | return r.path | |
166 | } | |
167 | ||
168 | func (r *cachingRepo) Versions(prefix string) ([]string, error) { | |
169 | type cached struct { | |
170 | list []string | |
171 | err error | |
172 | } | |
173 | c := r.cache.Do("versions:"+prefix, func() interface{} { | |
174 | list, err := r.r.Versions(prefix) | |
175 | return cached{list, err} | |
176 | }).(cached) | |
177 | ||
178 | if c.err != nil { | |
179 | return nil, c.err | |
180 | } | |
181 | return append([]string(nil), c.list...), nil | |
182 | } | |
183 | ||
184 | type cachedInfo struct { | |
185 | info *RevInfo | |
186 | err error | |
187 | } | |
188 | ||
189 | func (r *cachingRepo) Stat(rev string) (*RevInfo, error) { | |
190 | c := r.cache.Do("stat:"+rev, func() interface{} { | |
191 | file, info, err := readDiskStat(r.path, rev) | |
192 | if err == nil { | |
193 | return cachedInfo{info, nil} | |
194 | } | |
195 | ||
dd931d9b ILT |
196 | info, err = r.r.Stat(rev) |
197 | if err == nil { | |
dd931d9b ILT |
198 | // If we resolved, say, 1234abcde to v0.0.0-20180604122334-1234abcdef78, |
199 | // then save the information under the proper version, for future use. | |
200 | if info.Version != rev { | |
4f4a855d | 201 | file, _ = CachePath(module.Version{Path: r.path, Version: info.Version}, "info") |
dd931d9b ILT |
202 | r.cache.Do("stat:"+info.Version, func() interface{} { |
203 | return cachedInfo{info, err} | |
204 | }) | |
205 | } | |
4f4a855d ILT |
206 | |
207 | if err := writeDiskStat(file, info); err != nil { | |
208 | fmt.Fprintf(os.Stderr, "go: writing stat cache: %v\n", err) | |
209 | } | |
dd931d9b ILT |
210 | } |
211 | return cachedInfo{info, err} | |
212 | }).(cachedInfo) | |
213 | ||
214 | if c.err != nil { | |
215 | return nil, c.err | |
216 | } | |
217 | info := *c.info | |
218 | return &info, nil | |
219 | } | |
220 | ||
221 | func (r *cachingRepo) Latest() (*RevInfo, error) { | |
222 | c := r.cache.Do("latest:", func() interface{} { | |
dd931d9b ILT |
223 | info, err := r.r.Latest() |
224 | ||
225 | // Save info for likely future Stat call. | |
226 | if err == nil { | |
227 | r.cache.Do("stat:"+info.Version, func() interface{} { | |
228 | return cachedInfo{info, err} | |
229 | }) | |
230 | if file, _, err := readDiskStat(r.path, info.Version); err != nil { | |
231 | writeDiskStat(file, info) | |
232 | } | |
233 | } | |
234 | ||
235 | return cachedInfo{info, err} | |
236 | }).(cachedInfo) | |
237 | ||
238 | if c.err != nil { | |
239 | return nil, c.err | |
240 | } | |
241 | info := *c.info | |
242 | return &info, nil | |
243 | } | |
244 | ||
aa8901e9 | 245 | func (r *cachingRepo) GoMod(version string) ([]byte, error) { |
dd931d9b ILT |
246 | type cached struct { |
247 | text []byte | |
248 | err error | |
249 | } | |
aa8901e9 ILT |
250 | c := r.cache.Do("gomod:"+version, func() interface{} { |
251 | file, text, err := readDiskGoMod(r.path, version) | |
dd931d9b ILT |
252 | if err == nil { |
253 | // Note: readDiskGoMod already called checkGoMod. | |
254 | return cached{text, nil} | |
255 | } | |
256 | ||
aa8901e9 | 257 | text, err = r.r.GoMod(version) |
dd931d9b | 258 | if err == nil { |
5a8ea165 ILT |
259 | if err := checkGoMod(r.path, version, text); err != nil { |
260 | return cached{text, err} | |
261 | } | |
dd931d9b ILT |
262 | if err := writeDiskGoMod(file, text); err != nil { |
263 | fmt.Fprintf(os.Stderr, "go: writing go.mod cache: %v\n", err) | |
264 | } | |
265 | } | |
266 | return cached{text, err} | |
267 | }).(cached) | |
268 | ||
269 | if c.err != nil { | |
270 | return nil, c.err | |
271 | } | |
272 | return append([]byte(nil), c.text...), nil | |
273 | } | |
274 | ||
4f4a855d ILT |
275 | func (r *cachingRepo) Zip(dst io.Writer, version string) error { |
276 | return r.r.Zip(dst, version) | |
dd931d9b ILT |
277 | } |
278 | ||
279 | // Stat is like Lookup(path).Stat(rev) but avoids the | |
280 | // repository path resolution in Lookup if the result is | |
281 | // already cached on local disk. | |
aa8901e9 | 282 | func Stat(proxy, path, rev string) (*RevInfo, error) { |
dd931d9b ILT |
283 | _, info, err := readDiskStat(path, rev) |
284 | if err == nil { | |
285 | return info, nil | |
286 | } | |
aa8901e9 | 287 | repo, err := Lookup(proxy, path) |
dd931d9b ILT |
288 | if err != nil { |
289 | return nil, err | |
290 | } | |
291 | return repo.Stat(rev) | |
292 | } | |
293 | ||
294 | // InfoFile is like Stat but returns the name of the file containing | |
295 | // the cached information. | |
296 | func InfoFile(path, version string) (string, error) { | |
297 | if !semver.IsValid(version) { | |
298 | return "", fmt.Errorf("invalid version %q", version) | |
299 | } | |
aa8901e9 ILT |
300 | |
301 | if file, _, err := readDiskStat(path, version); err == nil { | |
302 | return file, nil | |
303 | } | |
304 | ||
305 | err := TryProxies(func(proxy string) error { | |
306 | repo, err := Lookup(proxy, path) | |
307 | if err == nil { | |
308 | _, err = repo.Stat(version) | |
309 | } | |
310 | return err | |
311 | }) | |
312 | if err != nil { | |
dd931d9b ILT |
313 | return "", err |
314 | } | |
aa8901e9 | 315 | |
dd931d9b ILT |
316 | // Stat should have populated the disk cache for us. |
317 | file, _, err := readDiskStat(path, version) | |
318 | if err != nil { | |
319 | return "", err | |
320 | } | |
321 | return file, nil | |
322 | } | |
323 | ||
324 | // GoMod is like Lookup(path).GoMod(rev) but avoids the | |
325 | // repository path resolution in Lookup if the result is | |
326 | // already cached on local disk. | |
327 | func GoMod(path, rev string) ([]byte, error) { | |
328 | // Convert commit hash to pseudo-version | |
329 | // to increase cache hit rate. | |
330 | if !semver.IsValid(rev) { | |
aa8901e9 ILT |
331 | if _, info, err := readDiskStat(path, rev); err == nil { |
332 | rev = info.Version | |
333 | } else { | |
334 | err := TryProxies(func(proxy string) error { | |
335 | repo, err := Lookup(proxy, path) | |
336 | if err != nil { | |
337 | return err | |
338 | } | |
339 | info, err := repo.Stat(rev) | |
340 | if err == nil { | |
341 | rev = info.Version | |
342 | } | |
343 | return err | |
344 | }) | |
345 | if err != nil { | |
346 | return nil, err | |
347 | } | |
dd931d9b | 348 | } |
dd931d9b | 349 | } |
aa8901e9 | 350 | |
dd931d9b ILT |
351 | _, data, err := readDiskGoMod(path, rev) |
352 | if err == nil { | |
353 | return data, nil | |
354 | } | |
aa8901e9 ILT |
355 | |
356 | err = TryProxies(func(proxy string) error { | |
357 | repo, err := Lookup(proxy, path) | |
358 | if err == nil { | |
359 | data, err = repo.GoMod(rev) | |
360 | } | |
361 | return err | |
362 | }) | |
363 | return data, err | |
dd931d9b ILT |
364 | } |
365 | ||
366 | // GoModFile is like GoMod but returns the name of the file containing | |
367 | // the cached information. | |
368 | func GoModFile(path, version string) (string, error) { | |
369 | if !semver.IsValid(version) { | |
370 | return "", fmt.Errorf("invalid version %q", version) | |
371 | } | |
372 | if _, err := GoMod(path, version); err != nil { | |
373 | return "", err | |
374 | } | |
375 | // GoMod should have populated the disk cache for us. | |
376 | file, _, err := readDiskGoMod(path, version) | |
377 | if err != nil { | |
378 | return "", err | |
379 | } | |
380 | return file, nil | |
381 | } | |
382 | ||
383 | // GoModSum returns the go.sum entry for the module version's go.mod file. | |
384 | // (That is, it returns the entry listed in go.sum as "path version/go.mod".) | |
385 | func GoModSum(path, version string) (string, error) { | |
386 | if !semver.IsValid(version) { | |
387 | return "", fmt.Errorf("invalid version %q", version) | |
388 | } | |
389 | data, err := GoMod(path, version) | |
390 | if err != nil { | |
391 | return "", err | |
392 | } | |
393 | sum, err := goModSum(data) | |
394 | if err != nil { | |
395 | return "", err | |
396 | } | |
397 | return sum, nil | |
398 | } | |
399 | ||
400 | var errNotCached = fmt.Errorf("not in cache") | |
401 | ||
402 | // readDiskStat reads a cached stat result from disk, | |
403 | // returning the name of the cache file and the result. | |
404 | // If the read fails, the caller can use | |
405 | // writeDiskStat(file, info) to write a new cache entry. | |
406 | func readDiskStat(path, rev string) (file string, info *RevInfo, err error) { | |
407 | file, data, err := readDiskCache(path, rev, "info") | |
408 | if err != nil { | |
aa8901e9 ILT |
409 | // If the cache already contains a pseudo-version with the given hash, we |
410 | // would previously return that pseudo-version without checking upstream. | |
411 | // However, that produced an unfortunate side-effect: if the author added a | |
412 | // tag to the repository, 'go get' would not pick up the effect of that new | |
413 | // tag on the existing commits, and 'go' commands that referred to those | |
414 | // commits would use the previous name instead of the new one. | |
415 | // | |
416 | // That's especially problematic if the original pseudo-version starts with | |
417 | // v0.0.0-, as was the case for all pseudo-versions during vgo development, | |
418 | // since a v0.0.0- pseudo-version has lower precedence than pretty much any | |
419 | // tagged version. | |
420 | // | |
421 | // In practice, we're only looking up by hash during initial conversion of a | |
422 | // legacy config and during an explicit 'go get', and a little extra latency | |
423 | // for those operations seems worth the benefit of picking up more accurate | |
424 | // versions. | |
425 | // | |
426 | // Fall back to this resolution scheme only if the GOPROXY setting prohibits | |
427 | // us from resolving upstream tags. | |
428 | if cfg.GOPROXY == "off" { | |
429 | if file, info, err := readDiskStatByHash(path, rev); err == nil { | |
430 | return file, info, nil | |
431 | } | |
dd931d9b ILT |
432 | } |
433 | return file, nil, err | |
434 | } | |
435 | info = new(RevInfo) | |
436 | if err := json.Unmarshal(data, info); err != nil { | |
437 | return file, nil, errNotCached | |
438 | } | |
439 | // The disk might have stale .info files that have Name and Short fields set. | |
440 | // We want to canonicalize to .info files with those fields omitted. | |
441 | // Remarshal and update the cache file if needed. | |
442 | data2, err := json.Marshal(info) | |
443 | if err == nil && !bytes.Equal(data2, data) { | |
444 | writeDiskCache(file, data) | |
445 | } | |
446 | return file, info, nil | |
447 | } | |
448 | ||
449 | // readDiskStatByHash is a fallback for readDiskStat for the case | |
450 | // where rev is a commit hash instead of a proper semantic version. | |
451 | // In that case, we look for a cached pseudo-version that matches | |
452 | // the commit hash. If we find one, we use it. | |
453 | // This matters most for converting legacy package management | |
454 | // configs, when we are often looking up commits by full hash. | |
455 | // Without this check we'd be doing network I/O to the remote repo | |
456 | // just to find out about a commit we already know about | |
457 | // (and have cached under its pseudo-version). | |
458 | func readDiskStatByHash(path, rev string) (file string, info *RevInfo, err error) { | |
459 | if PkgMod == "" { | |
460 | // Do not download to current directory. | |
461 | return "", nil, errNotCached | |
462 | } | |
463 | ||
464 | if !codehost.AllHex(rev) || len(rev) < 12 { | |
465 | return "", nil, errNotCached | |
466 | } | |
467 | rev = rev[:12] | |
468 | cdir, err := cacheDir(path) | |
469 | if err != nil { | |
470 | return "", nil, errNotCached | |
471 | } | |
472 | dir, err := os.Open(cdir) | |
473 | if err != nil { | |
474 | return "", nil, errNotCached | |
475 | } | |
476 | names, err := dir.Readdirnames(-1) | |
477 | dir.Close() | |
478 | if err != nil { | |
479 | return "", nil, errNotCached | |
480 | } | |
aa8901e9 ILT |
481 | |
482 | // A given commit hash may map to more than one pseudo-version, | |
483 | // depending on which tags are present on the repository. | |
484 | // Take the highest such version. | |
485 | var maxVersion string | |
dd931d9b | 486 | suffix := "-" + rev + ".info" |
aa8901e9 | 487 | err = errNotCached |
dd931d9b | 488 | for _, name := range names { |
aa8901e9 ILT |
489 | if strings.HasSuffix(name, suffix) { |
490 | v := strings.TrimSuffix(name, ".info") | |
491 | if IsPseudoVersion(v) && semver.Max(maxVersion, v) == v { | |
492 | maxVersion = v | |
493 | file, info, err = readDiskStat(path, strings.TrimSuffix(name, ".info")) | |
494 | } | |
dd931d9b ILT |
495 | } |
496 | } | |
aa8901e9 | 497 | return file, info, err |
dd931d9b ILT |
498 | } |
499 | ||
500 | // oldVgoPrefix is the prefix in the old auto-generated cached go.mod files. | |
501 | // We stopped trying to auto-generate the go.mod files. Now we use a trivial | |
502 | // go.mod with only a module line, and we've dropped the version prefix | |
503 | // entirely. If we see a version prefix, that means we're looking at an old copy | |
504 | // and should ignore it. | |
505 | var oldVgoPrefix = []byte("//vgo 0.0.") | |
506 | ||
4f4a855d | 507 | // readDiskGoMod reads a cached go.mod file from disk, |
dd931d9b ILT |
508 | // returning the name of the cache file and the result. |
509 | // If the read fails, the caller can use | |
510 | // writeDiskGoMod(file, data) to write a new cache entry. | |
511 | func readDiskGoMod(path, rev string) (file string, data []byte, err error) { | |
512 | file, data, err = readDiskCache(path, rev, "mod") | |
513 | ||
514 | // If the file has an old auto-conversion prefix, pretend it's not there. | |
515 | if bytes.HasPrefix(data, oldVgoPrefix) { | |
516 | err = errNotCached | |
517 | data = nil | |
518 | } | |
519 | ||
520 | if err == nil { | |
5a8ea165 ILT |
521 | if err := checkGoMod(path, rev, data); err != nil { |
522 | return "", nil, err | |
523 | } | |
dd931d9b ILT |
524 | } |
525 | ||
526 | return file, data, err | |
527 | } | |
528 | ||
529 | // readDiskCache is the generic "read from a cache file" implementation. | |
530 | // It takes the revision and an identifying suffix for the kind of data being cached. | |
531 | // It returns the name of the cache file and the content of the file. | |
532 | // If the read fails, the caller can use | |
533 | // writeDiskCache(file, data) to write a new cache entry. | |
534 | func readDiskCache(path, rev, suffix string) (file string, data []byte, err error) { | |
535 | file, err = CachePath(module.Version{Path: path, Version: rev}, suffix) | |
536 | if err != nil { | |
537 | return "", nil, errNotCached | |
538 | } | |
aa8901e9 | 539 | data, err = renameio.ReadFile(file) |
dd931d9b ILT |
540 | if err != nil { |
541 | return file, nil, errNotCached | |
542 | } | |
543 | return file, data, nil | |
544 | } | |
545 | ||
546 | // writeDiskStat writes a stat result cache entry. | |
547 | // The file name must have been returned by a previous call to readDiskStat. | |
548 | func writeDiskStat(file string, info *RevInfo) error { | |
549 | if file == "" { | |
550 | return nil | |
551 | } | |
552 | js, err := json.Marshal(info) | |
553 | if err != nil { | |
554 | return err | |
555 | } | |
556 | return writeDiskCache(file, js) | |
557 | } | |
558 | ||
559 | // writeDiskGoMod writes a go.mod cache entry. | |
560 | // The file name must have been returned by a previous call to readDiskGoMod. | |
561 | func writeDiskGoMod(file string, text []byte) error { | |
562 | return writeDiskCache(file, text) | |
563 | } | |
564 | ||
565 | // writeDiskCache is the generic "write to a cache file" implementation. | |
566 | // The file must have been returned by a previous call to readDiskCache. | |
567 | func writeDiskCache(file string, data []byte) error { | |
568 | if file == "" { | |
569 | return nil | |
570 | } | |
571 | // Make sure directory for file exists. | |
572 | if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil { | |
573 | return err | |
574 | } | |
4f4a855d | 575 | |
aa8901e9 | 576 | if err := renameio.WriteFile(file, data, 0666); err != nil { |
dd931d9b ILT |
577 | return err |
578 | } | |
579 | ||
580 | if strings.HasSuffix(file, ".mod") { | |
581 | rewriteVersionList(filepath.Dir(file)) | |
582 | } | |
583 | return nil | |
584 | } | |
585 | ||
586 | // rewriteVersionList rewrites the version list in dir | |
587 | // after a new *.mod file has been written. | |
588 | func rewriteVersionList(dir string) { | |
589 | if filepath.Base(dir) != "@v" { | |
590 | base.Fatalf("go: internal error: misuse of rewriteVersionList") | |
591 | } | |
592 | ||
4f4a855d ILT |
593 | listFile := filepath.Join(dir, "list") |
594 | ||
595 | // We use a separate lockfile here instead of locking listFile itself because | |
596 | // we want to use Rename to write the file atomically. The list may be read by | |
597 | // a GOPROXY HTTP server, and if we crash midway through a rewrite (or if the | |
598 | // HTTP server ignores our locking and serves the file midway through a | |
599 | // rewrite) it's better to serve a stale list than a truncated one. | |
600 | unlock, err := lockedfile.MutexAt(listFile + ".lock").Lock() | |
601 | if err != nil { | |
602 | base.Fatalf("go: can't lock version list lockfile: %v", err) | |
603 | } | |
604 | defer unlock() | |
dd931d9b ILT |
605 | |
606 | infos, err := ioutil.ReadDir(dir) | |
607 | if err != nil { | |
608 | return | |
609 | } | |
610 | var list []string | |
611 | for _, info := range infos { | |
612 | // We look for *.mod files on the theory that if we can't supply | |
613 | // the .mod file then there's no point in listing that version, | |
614 | // since it's unusable. (We can have *.info without *.mod.) | |
615 | // We don't require *.zip files on the theory that for code only | |
616 | // involved in module graph construction, many *.zip files | |
617 | // will never be requested. | |
618 | name := info.Name() | |
619 | if strings.HasSuffix(name, ".mod") { | |
620 | v := strings.TrimSuffix(name, ".mod") | |
621 | if v != "" && module.CanonicalVersion(v) == v { | |
622 | list = append(list, v) | |
623 | } | |
624 | } | |
625 | } | |
626 | SortVersions(list) | |
627 | ||
628 | var buf bytes.Buffer | |
629 | for _, v := range list { | |
630 | buf.WriteString(v) | |
631 | buf.WriteString("\n") | |
632 | } | |
aa8901e9 | 633 | old, _ := renameio.ReadFile(listFile) |
dd931d9b ILT |
634 | if bytes.Equal(buf.Bytes(), old) { |
635 | return | |
636 | } | |
4f4a855d | 637 | |
aa8901e9 | 638 | if err := renameio.WriteFile(listFile, buf.Bytes(), 0666); err != nil { |
4f4a855d ILT |
639 | base.Fatalf("go: failed to write version list: %v", err) |
640 | } | |
dd931d9b | 641 | } |