cmd/go/internal/modfetch: maintain @v/list files After this change, the $GOPATH/src/mod/cache/download file tree is exactly the format that needs to be served from a Go package proxy. The tree can be copied to static hosting or even accessed by setting GOPROXY to a file:/// URL. Test that. Fixes golang/go#26185. Change-Id: I81f10fa42835a5d1909ab348fd1dfb7449089f5e Reviewed-on: https://go-review.googlesource.com/122406 Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/modfetch/cache.go b/vendor/cmd/go/internal/modfetch/cache.go index be0adc0..5503df1 100644 --- a/vendor/cmd/go/internal/modfetch/cache.go +++ b/vendor/cmd/go/internal/modfetch/cache.go
@@ -13,6 +13,7 @@ "path/filepath" "strings" + "cmd/go/internal/base" "cmd/go/internal/modfetch/codehost" "cmd/go/internal/par" "cmd/go/internal/semver" @@ -345,5 +346,59 @@ } // Rename temp file onto cache file, // so that the cache file is always a complete file. - return os.Rename(f.Name(), file) + if err := os.Rename(f.Name(), file); err != nil { + return err + } + + if strings.HasSuffix(file, ".mod") { + rewriteVersionList(filepath.Dir(file)) + } + return nil +} + +// rewriteVersionList rewrites the version list in dir +// after a new *.mod file has been written. +func rewriteVersionList(dir string) { + if filepath.Base(dir) != "@v" { + base.Fatalf("go: internal error: misuse of rewriteVersionList") + } + + // TODO(rsc): We should do some kind of directory locking here, + // to avoid lost updates. + + infos, err := ioutil.ReadDir(dir) + if err != nil { + return + } + var list []string + for _, info := range infos { + // We look for *.mod files on the theory that if we can't supply + // the .mod file then there's no point in listing that version, + // since it's unusable. (We can have *.info without *.mod.) + // We don't require *.zip files on the theory that for code only + // involved in module graph construction, many *.zip files + // will never be requested. + name := info.Name() + if strings.HasSuffix(name, ".mod") { + v := strings.TrimSuffix(name, ".mod") + if semver.IsValid(v) && semver.Canonical(v) == v { + list = append(list, v) + } + } + } + SortVersions(list) + + var buf bytes.Buffer + for _, v := range list { + buf.WriteString(v) + buf.WriteString("\n") + } + listFile := filepath.Join(dir, "list") + old, _ := ioutil.ReadFile(listFile) + if bytes.Equal(buf.Bytes(), old) { + return + } + // TODO: Use rename to install file, + // so that readers never see an incomplete file. + ioutil.WriteFile(listFile, buf.Bytes(), 0666) }
diff --git a/vendor/cmd/go/mod_test.go b/vendor/cmd/go/mod_test.go index 258066f..01e07d0 100644 --- a/vendor/cmd/go/mod_test.go +++ b/vendor/cmd/go/mod_test.go
@@ -1155,6 +1155,45 @@ } } +func TestModProxy(t *testing.T) { + tg := testgo(t) + tg.setenv("GO111MODULE", "on") + defer tg.cleanup() + tg.makeTempdir() + + tg.setenv("GOPATH", tg.path("gp1")) + + tg.must(os.MkdirAll(tg.path("x"), 0777)) + tg.must(ioutil.WriteFile(tg.path("x/main.go"), []byte(`package x; import _ "rsc.io/quote"`), 0666)) + tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`module x + require rsc.io/quote v1.5.1`), 0666)) + tg.cd(tg.path("x")) + tg.run("list", "all") + tg.run("list", "-getmode=local", "all") + tg.mustExist(tg.path("gp1/src/mod/cache/download/rsc.io/quote/@v/list")) + + // @v/list should contain version list. + data, err := ioutil.ReadFile(tg.path("gp1/src/mod/cache/download/rsc.io/quote/@v/list")) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(data), "v1.5.1\n") { + t.Fatalf("cannot find v1.5.1 in @v/list:\n%s", data) + } + + tg.setenv("GOPROXY", "file:///nonexist") + tg.run("list", "-getmode=local", "all") + + tg.setenv("GOPATH", tg.path("gp2")) + tg.runFail("list", "-getmode=local", "all") + tg.runFail("list", "all") // because GOPROXY is bogus + + tg.setenv("GOPROXY", "file://"+filepath.ToSlash(tg.path("gp1/src/mod/cache/download"))) + tg.runFail("list", "-getmode=local", "all") + tg.run("list", "all") + tg.mustExist(tg.path("gp2/src/mod/cache/download/rsc.io/quote/@v/list")) +} + func TestModVendorNoDeps(t *testing.T) { tg := testgo(t) tg.setenv("GO111MODULE", "on")