this repo has no description
at master 173 lines 5.4 kB view raw
1// Package modcache provides a file-based cache for modules. 2// 3// WARNING: THIS PACKAGE IS EXPERIMENTAL. 4// ITS API MAY CHANGE AT ANY TIME. 5package modcache 6 7import ( 8 "context" 9 "errors" 10 "fmt" 11 "io/fs" 12 "os" 13 "path/filepath" 14 15 "github.com/rogpeppe/go-internal/lockedfile" 16 17 "cuelang.org/go/internal/robustio" 18 "cuelang.org/go/mod/module" 19) 20 21var errNotCached = fmt.Errorf("not in cache") 22 23// readDiskModFile reads a cached go.mod file from disk, 24// returning the name of the cache file and the result. 25// If the read fails, the caller can use 26// writeDiskModFile(file, data) to write a new cache entry. 27func (c *Cache) readDiskModFile(mv module.Version) (file string, data []byte, err error) { 28 return c.readDiskCache(mv, "mod") 29} 30 31// writeDiskModFile writes a cue.mod/module.cue cache entry. 32// The file name must have been returned by a previous call to readDiskModFile. 33func (c *Cache) writeDiskModFile(ctx context.Context, file string, text []byte) error { 34 return c.writeDiskCache(ctx, file, text) 35} 36 37// readDiskCache is the generic "read from a cache file" implementation. 38// It takes the revision and an identifying suffix for the kind of data being cached. 39// It returns the name of the cache file and the content of the file. 40// If the read fails, the caller can use 41// writeDiskCache(file, data) to write a new cache entry. 42func (c *Cache) readDiskCache(mv module.Version, suffix string) (file string, data []byte, err error) { 43 file, err = c.cachePath(mv, suffix) 44 if err != nil { 45 return "", nil, errNotCached 46 } 47 data, err = robustio.ReadFile(file) 48 if err != nil { 49 return file, nil, errNotCached 50 } 51 return file, data, nil 52} 53 54// writeDiskCache is the generic "write to a cache file" implementation. 55// The file must have been returned by a previous call to readDiskCache. 56func (c *Cache) writeDiskCache(ctx context.Context, file string, data []byte) error { 57 if file == "" { 58 return nil 59 } 60 // Make sure directory for file exists. 61 if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil { 62 return err 63 } 64 65 // Write the file to a temporary location, and then rename it to its final 66 // path to reduce the likelihood of a corrupt file existing at that final path. 67 f, err := tempFile(ctx, filepath.Dir(file), filepath.Base(file), 0666) 68 if err != nil { 69 return err 70 } 71 defer func() { 72 // Only call os.Remove on f.Name() if we failed to rename it: otherwise, 73 // some other process may have created a new file with the same name after 74 // the rename completed. 75 if err != nil { 76 f.Close() 77 os.Remove(f.Name()) 78 } 79 }() 80 81 if _, err := f.Write(data); err != nil { 82 return err 83 } 84 if err := f.Close(); err != nil { 85 return err 86 } 87 if err := robustio.Rename(f.Name(), file); err != nil { 88 return err 89 } 90 return nil 91} 92 93// downloadDir returns the directory for storing. 94// An error will be returned if the module path or version cannot be escaped. 95// An error satisfying [errors.Is](err, [fs.ErrNotExist]) will be returned 96// along with the directory if the directory does not exist or if the directory 97// is not completely populated. 98func (c *Cache) downloadDir(m module.Version) (string, error) { 99 if !m.IsCanonical() { 100 return "", fmt.Errorf("non-semver module version %q", m.Version()) 101 } 102 enc, err := module.EscapePath(m.BasePath()) 103 if err != nil { 104 return "", err 105 } 106 encVer, err := module.EscapeVersion(m.Version()) 107 if err != nil { 108 return "", err 109 } 110 111 // Check whether the directory itself exists. 112 dir := filepath.Join(c.dir, "extract", enc+"@"+encVer) 113 if fi, err := os.Stat(dir); os.IsNotExist(err) { 114 return dir, err 115 } else if err != nil { 116 return dir, &downloadDirPartialError{dir, err} 117 } else if !fi.IsDir() { 118 return dir, &downloadDirPartialError{dir, errors.New("not a directory")} 119 } 120 121 // Check if a .partial file exists. This is created at the beginning of 122 // a download and removed after the zip is extracted. 123 partialPath, err := c.cachePath(m, "partial") 124 if err != nil { 125 return dir, err 126 } 127 if _, err := os.Stat(partialPath); err == nil { 128 return dir, &downloadDirPartialError{dir, errors.New("not completely extracted")} 129 } else if !os.IsNotExist(err) { 130 return dir, err 131 } 132 return dir, nil 133} 134 135func (c *Cache) cachePath(m module.Version, suffix string) (string, error) { 136 if !m.IsValid() || m.Version() == "" { 137 return "", fmt.Errorf("non-semver module version %q", m) 138 } 139 esc, err := module.EscapePath(m.BasePath()) 140 if err != nil { 141 return "", err 142 } 143 encVer, err := module.EscapeVersion(m.Version()) 144 if err != nil { 145 return "", err 146 } 147 return filepath.Join(c.dir, "download", esc, "/@v", encVer+"."+suffix), nil 148} 149 150// downloadDirPartialError is returned by DownloadDir if a module directory 151// exists but was not completely populated. 152// 153// downloadDirPartialError is equivalent to fs.ErrNotExist. 154type downloadDirPartialError struct { 155 Dir string 156 Err error 157} 158 159func (e *downloadDirPartialError) Error() string { return fmt.Sprintf("%s: %v", e.Dir, e.Err) } 160func (e *downloadDirPartialError) Is(err error) bool { return err == fs.ErrNotExist } 161 162// lockVersion locks a file within the module cache that guards the downloading 163// and extraction of module data for the given module version. 164func (c *Cache) lockVersion(mod module.Version) (unlock func(), err error) { 165 path, err := c.cachePath(mod, "lock") 166 if err != nil { 167 return nil, err 168 } 169 if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { 170 return nil, err 171 } 172 return lockedfile.MutexAt(path).Lock() 173}