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")