diff options
| author | 2023-04-28 01:36:44 +0800 | |
|---|---|---|
| committer | 2023-04-28 01:36:44 +0800 | |
| commit | dd84b9d64fb98746a230cd24233ff50a562c39c9 (patch) | |
| tree | b583261ef00b3afe72ec4d6dacb31e57779a6faf /cli/internal/cacheitem/create.go | |
| parent | 0b46fcd72ac34382387b2bcf9095233efbcc52f4 (diff) | |
| download | HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.tar.gz HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.zip | |
Diffstat (limited to 'cli/internal/cacheitem/create.go')
| -rw-r--r-- | cli/internal/cacheitem/create.go | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/cli/internal/cacheitem/create.go b/cli/internal/cacheitem/create.go new file mode 100644 index 0000000..ce5b1c8 --- /dev/null +++ b/cli/internal/cacheitem/create.go @@ -0,0 +1,119 @@ +package cacheitem + +import ( + "archive/tar" + "bufio" + "io" + "os" + "strings" + "time" + + "github.com/DataDog/zstd" + + "github.com/moby/sys/sequential" + "github.com/vercel/turbo/cli/internal/tarpatch" + "github.com/vercel/turbo/cli/internal/turbopath" +) + +// Create makes a new CacheItem at the specified path. +func Create(path turbopath.AbsoluteSystemPath) (*CacheItem, error) { + handle, err := path.OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0644) + if err != nil { + return nil, err + } + + cacheItem := &CacheItem{ + Path: path, + handle: handle, + compressed: strings.HasSuffix(path.ToString(), ".zst"), + } + + cacheItem.init() + return cacheItem, nil +} + +// init prepares the CacheItem for writing. +// Wires all the writers end-to-end: +// tar.Writer -> zstd.Writer -> fileBuffer -> file +func (ci *CacheItem) init() { + fileBuffer := bufio.NewWriterSize(ci.handle, 2^20) // Flush to disk in 1mb chunks. + + var tw *tar.Writer + if ci.compressed { + zw := zstd.NewWriter(fileBuffer) + tw = tar.NewWriter(zw) + ci.zw = zw + } else { + tw = tar.NewWriter(fileBuffer) + } + + ci.tw = tw + ci.fileBuffer = fileBuffer +} + +// AddFile adds a user-cached item to the tar. +func (ci *CacheItem) AddFile(fsAnchor turbopath.AbsoluteSystemPath, filePath turbopath.AnchoredSystemPath) error { + // Calculate the fully-qualified path to the file to read it. + sourcePath := filePath.RestoreAnchor(fsAnchor) + + // We grab the FileInfo which tar.FileInfoHeader accepts. + fileInfo, lstatErr := sourcePath.Lstat() + if lstatErr != nil { + return lstatErr + } + + // Determine if we need to populate the additional link argument to tar.FileInfoHeader. + var link string + if fileInfo.Mode()&os.ModeSymlink != 0 { + linkTarget, readlinkErr := sourcePath.Readlink() + if readlinkErr != nil { + return readlinkErr + } + link = linkTarget + } + + // Normalize the path within the cache. + cacheDestinationName := filePath.ToUnixPath() + + // Generate the the header. + // We do not use header generation from stdlib because it can throw an error. + header, headerErr := tarpatch.FileInfoHeader(cacheDestinationName, fileInfo, link) + if headerErr != nil { + return headerErr + } + + // Throw an error if trying to create a cache that contains a type we don't support. + if (header.Typeflag != tar.TypeReg) && (header.Typeflag != tar.TypeDir) && (header.Typeflag != tar.TypeSymlink) { + return errUnsupportedFileType + } + + // Consistent creation. + header.Uid = 0 + header.Gid = 0 + header.AccessTime = time.Unix(0, 0) + header.ModTime = time.Unix(0, 0) + header.ChangeTime = time.Unix(0, 0) + + // Always write the header. + if err := ci.tw.WriteHeader(header); err != nil { + return err + } + + // If there is a body to be written, do so. + if header.Typeflag == tar.TypeReg && header.Size > 0 { + // Windows has a distinct "sequential read" opening mode. + // We use a library that will switch to this mode for Windows. + sourceFile, sourceErr := sequential.OpenFile(sourcePath.ToString(), os.O_RDONLY, 0777) + if sourceErr != nil { + return sourceErr + } + + if _, err := io.Copy(ci.tw, sourceFile); err != nil { + return err + } + + return sourceFile.Close() + } + + return nil +} |
