cuelang.org/go@v0.10.1/mod/modcache/cache.go (about) 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. 5 package modcache 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "io/fs" 12 "os" 13 "path/filepath" 14 15 "github.com/rogpeppe/go-internal/lockedfile" 16 "github.com/rogpeppe/go-internal/robustio" 17 18 "cuelang.org/go/mod/module" 19 ) 20 21 var 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. 27 func (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. 33 func (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. 42 func (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. 56 func (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. 98 func (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 135 func (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. 154 type downloadDirPartialError struct { 155 Dir string 156 Err error 157 } 158 159 func (e *downloadDirPartialError) Error() string { return fmt.Sprintf("%s: %v", e.Dir, e.Err) } 160 func (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. 164 func (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 }