
This is a large commit that moves all code directly related to compiling/linking into a new builder package. This has a number of advantages: * It cleanly separates the API between the command line and the full compilation (with a very small API surface). * When the compiler finally compiles one package at a time (instead of everything at once as it does now), something will have to invoke it once per package. This builder package will be the natural place to do that, and also be the place where the whole process can be parallelized. * It allows the TinyGo compiler to be used as a package. A client can simply import the builder package and compile code using it. As part of this refactor, the following additional things changed: * Exported symbols have been made unexported when they weren't needed. * The compilation target has been moved into the compileopts.Options struct. This is done because the target really is just another compiler option, and the API is simplified by moving it in there. * The moveFile function has been duplicated. It does not really belong in the builder API but is used both by the builder and the command line. Moving it into a separate package didn't seem useful either for what is essentially an utility function. * Some doc strings have been improved. Some future changes/refactors I'd like to make after this commit: * Clean up the API between the builder and the compiler package. * Perhaps move the test files (in testdata/) into the builder package. * Perhaps move the loader package into the builder package.
118 строки
2,8 КиБ
Go
118 строки
2,8 КиБ
Go
package builder
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/tinygo-org/tinygo/goenv"
|
|
)
|
|
|
|
// 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) {
|
|
cachepath := filepath.Join(goenv.Get("GOCACHE"), 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 := goenv.Get("GOCACHE")
|
|
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 = outf.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Rename(dst+".tmp", dst)
|
|
}
|