18 "github.com/google/renameio"
19 "github.com/rogpeppe/go-internal/modfile"
20 "golang.org/x/mod/module"
24 Q: which versions of our module are being used
25 A: find the latest version of every Go module, find the dependency on our module
27 Q: what modules have stopped using our module
28 A: find every module where a version [0..N) uses us, but version N doesn't.
31 func Fetch(since time.Time) ([]module.Version, time.Time, error) {
32 var out []module.Version
34 out2, since2, err := fetch(since, out)
36 return nil, since, err
38 if len(out) == len(out2) {
44 return out, since, nil
47 func fetch(since time.Time, out []module.Version) ([]module.Version, time.Time, error) {
48 // +1µs because of bug in index.golang.org that returns results
49 // >=since instead of >since
50 ts := since.Add(1 * time.Microsecond)
51 u := `https://index.golang.org/index?since=` + ts.Format(time.RFC3339Nano)
52 resp, err := http.Get(u)
54 return nil, since, err
56 defer resp.Body.Close()
57 dec := json.NewDecoder(resp.Body)
64 if err := dec.Decode(&entry); err != nil {
68 return out, since, err
71 out = append(out, entry.Version)
72 since = entry.Timestamp
75 return out, since, nil
79 cache, err := os.UserCacheDir()
85 b, err := ioutil.ReadFile(filepath.Join(cache, "go-module-query", "last"))
87 t, err := time.Parse(time.RFC3339Nano, string(b))
92 log.Println("Resuming at", since)
93 } else if !os.IsNotExist(err) {
97 out, since, err := Fetch(since)
102 sem := make(chan struct{}, 8)
103 var wg sync.WaitGroup
105 for _, v := range out {
106 mpath, _ := module.EscapePath(v.Path)
107 p := filepath.Join(cache, "go-module-query", mpath, "@v", v.Version+".mod")
108 // XXX is this atomic?
109 if err := os.MkdirAll(filepath.Join(cache, "go-module-query", mpath, "@v"), 0777); err != nil {
113 if _, err := os.Stat(p); os.IsNotExist(err) {
114 fmt.Println("Fetching", v)
117 go func(p string, v module.Version) {
119 defer func() { <-sem }()
120 resp, err := http.Get("https://proxy.golang.org/" + path.Join(mpath, "@v", v.Version+".mod"))
122 atomic.AddUint64(&errs, 1)
126 defer resp.Body.Close()
127 // XXX handle response code
128 pf, err := renameio.TempFile("", p)
130 atomic.AddUint64(&errs, 1)
135 if _, err := io.Copy(pf, resp.Body); err != nil {
136 atomic.AddUint64(&errs, 1)
140 if err := pf.CloseAtomicallyReplace(); err != nil {
141 atomic.AddUint64(&errs, 1)
142 log.Println("Couldn't store go.mod:", err)
151 log.Println("Couldn't download all go.mod, not storing timestamp")
155 if err := renameio.WriteFile(filepath.Join(cache, "go-module-query", "last"), []byte(since.Format(time.RFC3339Nano)), 0666); err != nil {
156 log.Println("Couldn't store timestamp:", err)
161 cache, err := os.UserCacheDir()
165 filepath.Walk(filepath.Join(cache, "go-module-query"), func(path string, info os.FileInfo, err error) error {
169 if strings.HasSuffix(path, ".mod") {
170 name := filepath.Base(path)
171 name = name[:len(name)-4]
172 b, err := ioutil.ReadFile(path)
177 f, err := modfile.Parse(path, b, nil)
182 f.Module.Mod.Version = name
183 for _, dep := range f.Require {
184 fmt.Printf("%s@%s %s@%s\n", f.Module.Mod.Path, f.Module.Mod.Version, dep.Mod.Path, dep.Mod.Version)