| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // A command for building and maintaining the module cache |
| // a.out <flags> <command> <args> |
| // The commands are: |
| // 'update', which attempts to update an existing index, |
| // 'query', which looks up things in the index. |
| // 'clean', which remove obsolete index files. |
| // If the command is invoked with no arguments, it defaults to 'update'. |
| package main |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "log" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "strings" |
| "time" |
| |
| "golang.org/x/tools/internal/modindex" |
| ) |
| |
| var verbose = flag.Int("v", 0, "how much information to print") |
| |
| type cmd struct { |
| name string |
| f func(string) |
| doc string |
| } |
| |
| var cmds = []cmd{ |
| {"update", update, "if there is an existing index of GOMODCACHE, update it. Otherwise create one."}, |
| {"clean", clean, "removed unreferenced indexes more than an hour old"}, |
| {"query", query, "not yet implemented"}, |
| } |
| |
| func goEnv(s string) string { |
| out, err := exec.Command("go", "env", s).Output() |
| if err != nil { |
| return "" |
| } |
| out = bytes.TrimSpace(out) |
| return string(out) |
| } |
| |
| func main() { |
| flag.Parse() |
| log.SetFlags(log.Lshortfile) |
| gomodcache := goEnv("GOMODCACHE") |
| if gomodcache == "" { |
| log.Fatal("can't find GOMODCACHE") |
| } |
| if flag.NArg() == 0 { |
| update(gomodcache) |
| return |
| } |
| for _, c := range cmds { |
| if flag.Arg(0) == c.name { |
| c.f(gomodcache) |
| return |
| } |
| } |
| flag.Usage() |
| } |
| |
| func init() { |
| var sb strings.Builder |
| fmt.Fprintf(&sb, "usage:\n") |
| for _, c := range cmds { |
| fmt.Fprintf(&sb, "'%s': %s\n", c.name, c.doc) |
| } |
| msg := sb.String() |
| flag.Usage = func() { |
| fmt.Fprint(os.Stderr, msg) |
| } |
| } |
| |
| func update(dir string) { |
| if _, err := modindex.Update(dir); err != nil { |
| log.Print(err) |
| } |
| } |
| |
| func query(dir string) { |
| panic("implement") |
| } |
| |
| func clean(_ string) { |
| des := modindex.IndexDir |
| // look at the files starting with 'index' |
| // the current ones of each version are pointed to by |
| // index-name-%d files. Any others more than an hour old |
| // are deleted. |
| dis, err := os.ReadDir(des) |
| if err != nil { |
| log.Fatal(err) |
| } |
| cutoff := time.Now().Add(-time.Hour) |
| var inames []string // older files named index* |
| curnames := make(map[string]bool) // current versions of index (different CurrentVersion) |
| for _, de := range dis { |
| if !strings.HasPrefix(de.Name(), "index") { |
| continue |
| } |
| if strings.HasPrefix(de.Name(), "index-name-") { |
| buf, err := os.ReadFile(filepath.Join(des, de.Name())) |
| if err != nil { |
| log.Print(err) |
| continue |
| } |
| curnames[string(buf)] = true |
| if *verbose > 1 { |
| log.Printf("latest index is %s", string(buf)) |
| } |
| } |
| info, err := de.Info() |
| if err != nil { |
| log.Print(err) |
| continue |
| } |
| if info.ModTime().Before(cutoff) && !strings.HasPrefix(de.Name(), "index-name-") { |
| // add to the list of files to be removed. index-name-%d files are never removed |
| inames = append(inames, de.Name()) |
| if *verbose > 0 { |
| log.Printf("%s:%s", de.Name(), cutoff.Sub(info.ModTime())) |
| } |
| } |
| } |
| for _, nm := range inames { |
| if curnames[nm] { |
| continue |
| } |
| err := os.Remove(filepath.Join(des, nm)) |
| if err != nil && *verbose > 0 { |
| log.Printf("%s not removed (%v)", nm, err) |
| } |
| } |
| } |