package main import ( "io" "os" "path/filepath" "time" ) // Get the cache directory, usually ~/.cache/tinygo func cacheDir() string { dir, err := os.UserCacheDir() if err != nil { panic("could not find cache dir: " + err.Error()) } return filepath.Join(dir, "tinygo") } // Return the newest timestamp of all the file paths passed in. Used to check // for stale caches. func cacheTimestamp(paths []string) (time.Time, error) { var timestamp time.Time for _, path := range paths { st, err := os.Stat(path) if err != nil { return time.Time{}, err } if timestamp.IsZero() { timestamp = st.ModTime() } else if timestamp.Before(st.ModTime()) { timestamp = st.ModTime() } } return timestamp, nil } // Try to load a given file from the cache. Return "", nil if no cached file can // be found (or the file is stale), return the absolute path if there is a cache // and return an error on I/O errors. // // TODO: the configKey is currently ignored. It is supposed to be used as extra // data for the cache key, like the compiler version and arguments. func cacheLoad(name, configKey string, sourceFiles []string) (string, error) { dir := cacheDir() cachepath := filepath.Join(dir, name) cacheStat, err := os.Stat(cachepath) if os.IsNotExist(err) { return "", nil // does not exist } else if err != nil { return "", err // cannot stat cache file } sourceTimestamp, err := cacheTimestamp(sourceFiles) if err != nil { return "", err // cannot stat source files } if cacheStat.ModTime().After(sourceTimestamp) { return cachepath, nil } else { os.Remove(cachepath) // stale cache return "", nil } } // Store the file located at tmppath in the cache with the given name. The // tmppath may or may not be gone afterwards. // // Note: the configKey is ignored, see cacheLoad. func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string, error) { // get the last modified time if len(sourceFiles) == 0 { panic("cache: no source files") } // TODO: check the config key dir := cacheDir() err := os.MkdirAll(dir, 0777) if err != nil { return "", err } cachepath := filepath.Join(dir, name) err = moveFile(tmppath, cachepath) if err != nil { return "", err } return cachepath, nil } // moveFile renames the file from src to dst. If renaming doesn't work (for // example, the rename crosses a filesystem boundary), the file is copied and // the old file is removed. func moveFile(src, dst string) error { err := os.Rename(src, dst) if err == nil { // Success! return nil } // Failed to move, probably a different filesystem. // Do a copy + remove. inf, err := os.Open(src) if err != nil { return err } defer inf.Close() outpath := dst + ".tmp" outf, err := os.Create(outpath) if err != nil { return err } _, err = io.Copy(outf, inf) if err != nil { os.Remove(outpath) return err } err = os.Rename(dst+".tmp", dst) if err != nil { return err } return outf.Close() }