...

Source file src/cuelang.org/go/mod/modcache/cache.go

Documentation: cuelang.org/go/mod/modcache

     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(ctx context.Context, mv module.Version) (file string, data []byte, err error) {
    28  	return c.readDiskCache(ctx, 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(ctx context.Context, mv module.Version, suffix string) (file string, data []byte, err error) {
    43  	file, err = c.cachePath(ctx, 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(ctx context.Context, 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, 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(ctx, 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(ctx context.Context, 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, "cache/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(ctx context.Context, mod module.Version) (unlock func(), err error) {
   165  	path, err := c.cachePath(ctx, 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  }
   174  

View as plain text