Skip to content

Commit

Permalink
Serializing Archieves
Browse files Browse the repository at this point in the history
  • Loading branch information
grantnelson-wf committed Dec 5, 2024
1 parent ff99ecc commit c2773f3
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 93 deletions.
9 changes: 3 additions & 6 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -977,11 +977,8 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
}

if !s.options.NoCache {
archive := s.buildCache.LoadArchive(pkg.ImportPath, s.Types)
if archive != nil && !pkg.SrcModTime.After(archive.BuildTime) {
if err := archive.RegisterTypes(s.Types); err != nil {
panic(fmt.Errorf("failed to load type information from %v: %w", archive, err))
}
archive := s.buildCache.LoadArchive(pkg.ImportPath, pkg.SrcModTime, s.Types)
if archive != nil {
s.UpToDateArchives[pkg.ImportPath] = archive
// Existing archive is up to date, no need to build it from scratch.
return archive, nil
Expand Down Expand Up @@ -1021,7 +1018,7 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
fmt.Println(pkg.ImportPath)
}

s.buildCache.StoreArchive(archive)
s.buildCache.StoreArchive(archive, time.Now())
s.UpToDateArchives[pkg.ImportPath] = archive

return archive, nil
Expand Down
18 changes: 13 additions & 5 deletions build/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"path"
"path/filepath"
"time"

"github.com/gopherjs/gopherjs/compiler"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -91,7 +92,7 @@ func (bc BuildCache) String() string {

// StoreArchive compiled archive in the cache. Any error inside this method
// will cause the cache not to be persisted.
func (bc *BuildCache) StoreArchive(a *compiler.Archive) {
func (bc *BuildCache) StoreArchive(a *compiler.Archive, buildTime time.Time) {
if bc == nil {
return // Caching is disabled.
}
Expand All @@ -107,7 +108,7 @@ func (bc *BuildCache) StoreArchive(a *compiler.Archive) {
return
}
defer f.Close()
if err := compiler.WriteArchive(a, f); err != nil {
if err := compiler.WriteArchive(a, buildTime, f); err != nil {
log.Warningf("Failed to write build cache archive %q: %v", a, err)
// Make sure we don't leave a half-written archive behind.
os.Remove(f.Name())
Expand All @@ -126,7 +127,10 @@ func (bc *BuildCache) StoreArchive(a *compiler.Archive) {
//
// The returned archive would have been built with the same configuration as
// the build cache was.
func (bc *BuildCache) LoadArchive(importPath string, packages map[string]*types.Package) *compiler.Archive {
//
// The imports map is used to resolve package dependencies and may modify the
// map to include the package from the read archive. See [gcexportdata.Read].
func (bc *BuildCache) LoadArchive(importPath string, srcModTime time.Time, imports map[string]*types.Package) *compiler.Archive {
if bc == nil {
return nil // Caching is disabled.
}
Expand All @@ -141,12 +145,16 @@ func (bc *BuildCache) LoadArchive(importPath string, packages map[string]*types.
return nil // Cache miss.
}
defer f.Close()
a, err := compiler.ReadArchive(importPath, f, packages)
a, buildTime, err := compiler.ReadArchive(importPath, f, srcModTime, imports)
if err != nil {
log.Warningf("Failed to read cached package archive for %q: %v", importPath, err)
return nil // Invalid/corrupted archive, cache miss.
}
log.Infof("Found cached package archive for %q, built at %v.", importPath, a.BuildTime)
if a == nil {
log.Infof("Found out-of-date package archive for %q, built at %v.", importPath, buildTime)
return nil // Archive is out-of-date, cache miss.
}
log.Infof("Found cached package archive for %q, built at %v.", importPath, buildTime)
return a
}

Expand Down
54 changes: 45 additions & 9 deletions build/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cache
import (
"go/types"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/gopherjs/gopherjs/compiler"
Expand All @@ -16,22 +17,24 @@ func TestStore(t *testing.T) {
Imports: []string{"fake/dep"},
}

deps := map[string]*types.Package{}
srcModTime := newTime(0.0)
buildTime := newTime(5.0)
imports := map[string]*types.Package{}
bc := BuildCache{}
if got := bc.LoadArchive(want.ImportPath, deps); got != nil {
if got := bc.LoadArchive(want.ImportPath, srcModTime, imports); got != nil {
t.Errorf("Got: %s was found in the cache. Want: empty cache.", got.ImportPath)
}
bc.StoreArchive(want)
got := bc.LoadArchive(want.ImportPath, deps)
bc.StoreArchive(want, buildTime)
got := bc.LoadArchive(want.ImportPath, srcModTime, imports)
if got == nil {
t.Errorf("Got: %s wan not found in the cache. Want: archive is can be loaded after store.", want.ImportPath)
t.Errorf("Got: %s was not found in the cache. Want: archive is can be loaded after store.", want.ImportPath)
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("Loaded archive is different from stored (-want,+got):\n%s", diff)
}

// Make sure the package names are a part of the cache key.
if got := bc.LoadArchive("fake/other", deps); got != nil {
if got := bc.LoadArchive("fake/other", srcModTime, imports); got != nil {
t.Errorf("Got: fake/other was found in cache: %#v. Want: nil for packages that weren't cached.", got)
}
}
Expand Down Expand Up @@ -61,21 +64,54 @@ func TestInvalidation(t *testing.T) {
},
}

deps := map[string]*types.Package{}
srcModTime := newTime(0.0)
buildTime := newTime(5.0)
imports := map[string]*types.Package{}
for _, test := range tests {
a := &compiler.Archive{ImportPath: "package/fake"}
test.cache1.StoreArchive(a)
test.cache1.StoreArchive(a, buildTime)

if got := test.cache2.LoadArchive(a.ImportPath, deps); got != nil {
if got := test.cache2.LoadArchive(a.ImportPath, srcModTime, imports); got != nil {
t.Logf("-cache1,+cache2:\n%s", cmp.Diff(test.cache1, test.cache2))
t.Errorf("Got: %v loaded from cache. Want: build parameter change invalidates cache.", got)
}
}
}

func TestOldArchive(t *testing.T) {
cacheForTest(t)

want := &compiler.Archive{
ImportPath: "fake/package",
Imports: []string{"fake/dep"},
}

buildTime := newTime(5.0)
imports := map[string]*types.Package{}
bc := BuildCache{}
bc.StoreArchive(want, buildTime)

oldSrcModTime := newTime(2.0) // older than archive build time, so archive is up-to-date
got := bc.LoadArchive(want.ImportPath, oldSrcModTime, imports)
if got == nil {
t.Errorf("Got: %s was nil. Want: up-to-date archive to be loaded.", want.ImportPath)
}

newerSrcModTime := newTime(7.0) // newer than archive build time, so archive is stale
got = bc.LoadArchive(want.ImportPath, newerSrcModTime, imports)
if got != nil {
t.Errorf("Got: %s was not nil. Want: stale archive to not be loaded.", want.ImportPath)
}
}

func cacheForTest(t *testing.T) {
t.Helper()
originalRoot := cacheRoot
t.Cleanup(func() { cacheRoot = originalRoot })
cacheRoot = t.TempDir()
}

func newTime(seconds float64) time.Time {
return time.Date(1969, 7, 20, 20, 17, 0, 0, time.UTC).
Add(time.Duration(seconds * float64(time.Second)))
}
40 changes: 23 additions & 17 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,12 @@ type Archive struct {
Minified bool
// A list of go:linkname directives encountered in the package.
GoLinknames []GoLinkname
// Time when this archive was built.
BuildTime time.Time
}

func (a Archive) String() string {
return fmt.Sprintf("compiler.Archive{%s}", a.ImportPath)
}

// RegisterTypes adds package type information from the archive into the provided map.
func (a *Archive) RegisterTypes(packages map[string]*types.Package) error {
packages[a.ImportPath] = a.Package
return nil
}

type Dependency struct {
Pkg string
Type string
Expand Down Expand Up @@ -277,26 +269,38 @@ type serializableArchive struct {
}

// ReadArchive reads serialized compiled archive of the importPath package.
func ReadArchive(path string, r io.Reader, packages map[string]*types.Package) (*Archive, error) {
//
// The given srcModTime is used to determine if the archive is out-of-date.
// If the archive is out-of-date, the returned archive is nil.
// If there was not an error, the returned time is when the archive was built.
//
// The imports map is used to resolve package dependencies and may modify the
// map to include the package from the read archive. See [gcexportdata.Read].
func ReadArchive(importPath string, r io.Reader, srcModTime time.Time, imports map[string]*types.Package) (*Archive, time.Time, error) {
var sa serializableArchive
if err := gob.NewDecoder(r).Decode(&sa); err != nil {
return nil, err
return nil, time.Time{}, err
}

if srcModTime.After(sa.BuildTime) {
// Archive is out-of-date.
return nil, sa.BuildTime, nil
}

var a Archive
fset := token.NewFileSet()
if len(sa.ExportData) > 0 {
pkg, err := gcexportdata.Read(bytes.NewReader(sa.ExportData), fset, packages, a.ImportPath)
pkg, err := gcexportdata.Read(bytes.NewReader(sa.ExportData), fset, imports, importPath)
if err != nil {
return nil, err
return nil, sa.BuildTime, err
}
a.Package = pkg
}

if len(sa.FileSet) > 0 {
a.FileSet = token.NewFileSet()
if err := a.FileSet.Read(json.NewDecoder(bytes.NewReader(sa.FileSet)).Decode); err != nil {
return nil, err
return nil, sa.BuildTime, err
}
}

Expand All @@ -307,12 +311,14 @@ func ReadArchive(path string, r io.Reader, packages map[string]*types.Package) (
a.IncJSCode = sa.IncJSCode
a.Minified = sa.Minified
a.GoLinknames = sa.GoLinknames
a.BuildTime = sa.BuildTime
return &a, nil
return &a, sa.BuildTime, nil
}

// WriteArchive writes compiled package archive on disk for later reuse.
func WriteArchive(a *Archive, w io.Writer) error {
//
// The passed in buildTime is used to determine if the archive is out-of-date.
// It should be set to time.Now() typically but it exposed for testing purposes.
func WriteArchive(a *Archive, buildTime time.Time, w io.Writer) error {
exportData := new(bytes.Buffer)
if a.Package != nil {
if err := gcexportdata.Write(exportData, nil, a.Package); err != nil {
Expand All @@ -337,7 +343,7 @@ func WriteArchive(a *Archive, w io.Writer) error {
FileSet: encodedFileSet.Bytes(),
Minified: a.Minified,
GoLinknames: a.GoLinknames,
BuildTime: a.BuildTime,
BuildTime: buildTime,
}

return gob.NewEncoder(w).Encode(sa)
Expand Down
Loading

0 comments on commit c2773f3

Please sign in to comment.