Skip to content

Commit 3d82bcd

Browse files
committed
Support .inc.js files for standard library overlays.
This will simplify access to low-level JavaScript features from the overlay sources. In many ways, this will fill the role of assembly in the upstream standard library.
1 parent 1497816 commit 3d82bcd

File tree

3 files changed

+113
-57
lines changed

3 files changed

+113
-57
lines changed

build/build.go

Lines changed: 74 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"go/scanner"
1414
"go/token"
1515
"go/types"
16-
"io/ioutil"
1716
"os"
1817
"os/exec"
1918
"path"
@@ -25,10 +24,9 @@ import (
2524
"github.com/fsnotify/fsnotify"
2625
"github.com/gopherjs/gopherjs/compiler"
2726
"github.com/gopherjs/gopherjs/compiler/astutil"
28-
"github.com/gopherjs/gopherjs/compiler/gopherjspkg"
27+
log "github.com/sirupsen/logrus"
2928

3029
"github.com/neelance/sourcemap"
31-
"github.com/shurcooL/httpfs/vfsutil"
3230
"golang.org/x/tools/go/buildutil"
3331

3432
"github.com/gopherjs/gopherjs/build/cache"
@@ -64,20 +62,6 @@ func NewBuildContext(installSuffix string, buildTags []string) XContext {
6462
}
6563
}
6664

67-
// statFile returns an os.FileInfo describing the named file.
68-
// For files in "$GOROOT/src/github.com/gopherjs/gopherjs" directory,
69-
// gopherjspkg.FS is consulted first.
70-
func statFile(path string) (os.FileInfo, error) {
71-
gopherjsRoot := filepath.Join(DefaultGOROOT, "src", "github.com", "gopherjs", "gopherjs")
72-
if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) {
73-
path = filepath.ToSlash(path[len(gopherjsRoot):])
74-
if fi, err := vfsutil.Stat(gopherjspkg.FS, path); err == nil {
75-
return fi, nil
76-
}
77-
}
78-
return os.Stat(path)
79-
}
80-
8165
// Import returns details about the Go package named by the import path. If the
8266
// path is a local import path naming a package that can be imported using
8367
// a standard import path, the returned package will set p.ImportPath to
@@ -161,7 +145,7 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
161145
// as an existing file from the standard library). For all identifiers that exist
162146
// in the original AND the overrides, the original identifier in the AST gets
163147
// replaced by `_`. New identifiers that don't exist in original package get added.
164-
func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, error) {
148+
func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) {
165149
var files []*ast.File
166150
replacedDeclNames := make(map[string]bool)
167151
pruneOriginalFuncs := make(map[string]bool)
@@ -172,9 +156,12 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
172156
importPath = importPath[:len(importPath)-5]
173157
}
174158

159+
jsFiles := []JSFile{}
160+
175161
nativesContext := overlayCtx(xctx.Env())
176162

177163
if nativesPkg, err := nativesContext.Import(importPath, "", 0); err == nil {
164+
jsFiles = nativesPkg.JSFiles
178165
names := nativesPkg.GoFiles
179166
if isTest {
180167
names = append(names, nativesPkg.TestGoFiles...)
@@ -229,7 +216,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
229216
}
230217
r, err := buildutil.OpenFile(pkg.bctx, name)
231218
if err != nil {
232-
return nil, err
219+
return nil, nil, err
233220
}
234221
file, err := parser.ParseFile(fileSet, name, r, parser.ParseComments)
235222
r.Close()
@@ -298,9 +285,9 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
298285
}
299286

300287
if errList != nil {
301-
return nil, errList
288+
return nil, nil, errList
302289
}
303-
return files, nil
290+
return files, jsFiles, nil
304291
}
305292

306293
// Options controls build process behavior.
@@ -333,11 +320,18 @@ func (o *Options) PrintSuccess(format string, a ...interface{}) {
333320
fmt.Fprintf(os.Stderr, format, a...)
334321
}
335322

323+
// JSFile represents a *.inc.js file metadata and content.
324+
type JSFile struct {
325+
Path string // Full file path for the build context the file came from.
326+
ModTime time.Time
327+
Content []byte
328+
}
329+
336330
// PackageData is an extension of go/build.Package with additional metadata
337331
// GopherJS requires.
338332
type PackageData struct {
339333
*build.Package
340-
JSFiles []string
334+
JSFiles []JSFile
341335
// IsTest is true if the package is being built for running tests.
342336
IsTest bool
343337
SrcModTime time.Time
@@ -352,6 +346,43 @@ func (p PackageData) String() string {
352346
return fmt.Sprintf("%s [is_test=%v]", p.ImportPath, p.IsTest)
353347
}
354348

349+
// FileModTime returns the most recent modification time of the package's source
350+
// files. This includes all .go and .inc.js that would be included in the build,
351+
// but excludes any dependencies.
352+
func (p PackageData) FileModTime() time.Time {
353+
newest := time.Time{}
354+
for _, file := range p.JSFiles {
355+
if file.ModTime.After(newest) {
356+
newest = file.ModTime
357+
}
358+
}
359+
360+
// Unfortunately, build.Context methods don't allow us to Stat and individual
361+
// file, only to enumerate a directory. So we first get mtimes for all files
362+
// in the package directory, and then pick the newest for the relevant GoFiles.
363+
mtimes := map[string]time.Time{}
364+
files, err := buildutil.ReadDir(p.bctx, p.Dir)
365+
if err != nil {
366+
log.Errorf("Failed to enumerate files in the %q in context %v: %s. Assuming time.Now().", p.Dir, p.bctx, err)
367+
return time.Now()
368+
}
369+
for _, file := range files {
370+
mtimes[file.Name()] = file.ModTime()
371+
}
372+
373+
for _, file := range p.GoFiles {
374+
t, ok := mtimes[file]
375+
if !ok {
376+
log.Errorf("No mtime found for source file %q of package %q, assuming time.Now().", file, p.Name)
377+
return time.Now()
378+
}
379+
if t.After(newest) {
380+
newest = t
381+
}
382+
}
383+
return newest
384+
}
385+
355386
// InternalBuildContext returns the build context that produced the package.
356387
//
357388
// WARNING: This function is a part of internal API and will be removed in
@@ -499,11 +530,24 @@ func (s *Session) BuildFiles(filenames []string, pkgObj string, packagePath stri
499530
}
500531

501532
for _, file := range filenames {
502-
if strings.HasSuffix(file, ".inc.js") {
503-
pkg.JSFiles = append(pkg.JSFiles, file)
533+
if !strings.HasSuffix(file, ".inc.js") {
534+
pkg.GoFiles = append(pkg.GoFiles, file)
504535
continue
505536
}
506-
pkg.GoFiles = append(pkg.GoFiles, file)
537+
538+
content, err := os.ReadFile(file)
539+
if err != nil {
540+
return fmt.Errorf("failed to read %s: %w", file, err)
541+
}
542+
info, err := os.Stat(file)
543+
if err != nil {
544+
return fmt.Errorf("failed to stat %s: %w", file, err)
545+
}
546+
pkg.JSFiles = append(pkg.JSFiles, JSFile{
547+
Path: file,
548+
ModTime: info.ModTime(),
549+
Content: content,
550+
})
507551
}
508552

509553
archive, err := s.BuildPackage(pkg)
@@ -579,14 +623,8 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
579623
}
580624
}
581625

582-
for _, name := range append(pkg.GoFiles, pkg.JSFiles...) {
583-
fileInfo, err := statFile(filepath.Join(pkg.Dir, name))
584-
if err != nil {
585-
return nil, err
586-
}
587-
if fileInfo.ModTime().After(pkg.SrcModTime) {
588-
pkg.SrcModTime = fileInfo.ModTime()
589-
}
626+
if pkg.FileModTime().After(pkg.SrcModTime) {
627+
pkg.SrcModTime = pkg.FileModTime()
590628
}
591629

592630
if !s.options.NoCache {
@@ -603,7 +641,7 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
603641

604642
// Existing archive is out of date or doesn't exist, let's build the package.
605643
fileSet := token.NewFileSet()
606-
files, err := parseAndAugment(s.xctx, pkg, pkg.IsTest, fileSet)
644+
files, overlayJsFiles, err := parseAndAugment(s.xctx, pkg, pkg.IsTest, fileSet)
607645
if err != nil {
608646
return nil, err
609647
}
@@ -617,13 +655,9 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
617655
return nil, err
618656
}
619657

620-
for _, jsFile := range pkg.JSFiles {
621-
code, err := ioutil.ReadFile(filepath.Join(pkg.Dir, jsFile))
622-
if err != nil {
623-
return nil, err
624-
}
658+
for _, jsFile := range append(pkg.JSFiles, overlayJsFiles...) {
625659
archive.IncJSCode = append(archive.IncJSCode, []byte("\t(function() {\n")...)
626-
archive.IncJSCode = append(archive.IncJSCode, code...)
660+
archive.IncJSCode = append(archive.IncJSCode, jsFile.Content...)
627661
archive.IncJSCode = append(archive.IncJSCode, []byte("\n\t}).call($global);\n")...)
628662
}
629663

@@ -721,22 +755,6 @@ func NewMappingCallback(m *sourcemap.Map, goroot, gopath string, localMap bool)
721755
}
722756
}
723757

724-
// jsFilesFromDir finds and loads any *.inc.js packages in the build context
725-
// directory.
726-
func jsFilesFromDir(bctx *build.Context, dir string) ([]string, error) {
727-
files, err := buildutil.ReadDir(bctx, dir)
728-
if err != nil {
729-
return nil, err
730-
}
731-
var jsFiles []string
732-
for _, file := range files {
733-
if strings.HasSuffix(file.Name(), ".inc.js") && file.Name()[0] != '_' && file.Name()[0] != '.' {
734-
jsFiles = append(jsFiles, file.Name())
735-
}
736-
}
737-
return jsFiles, nil
738-
}
739-
740758
// hasGopathPrefix returns true and the length of the matched GOPATH workspace,
741759
// iff file has a prefix that matches one of the GOPATH workspaces.
742760
func hasGopathPrefix(file, gopath string) (hasGopathPrefix bool, prefixLen int) {

build/build_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func TestNativesDontImportExtraPackages(t *testing.T) {
9090

9191
// Use parseAndAugment to get a list of augmented AST files.
9292
fset := token.NewFileSet()
93-
files, err := parseAndAugment(stdOnly, pkgVariant, pkgVariant.IsTest, fset)
93+
files, _, err := parseAndAugment(stdOnly, pkgVariant, pkgVariant.IsTest, fset)
9494
if err != nil {
9595
t.Fatalf("github.com/gopherjs/gopherjs/build.parseAndAugment: %v", err)
9696
}

build/context.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"go/build"
66
"go/token"
7+
"io"
78
"net/http"
89
"os"
910
"os/exec"
@@ -439,3 +440,40 @@ func updateImports(sources []string, importPos map[string][]token.Position) (new
439440
sort.Strings(newImports)
440441
return newImports, newImportPos
441442
}
443+
444+
// jsFilesFromDir finds and loads any *.inc.js packages in the build context
445+
// directory.
446+
func jsFilesFromDir(bctx *build.Context, dir string) ([]JSFile, error) {
447+
files, err := buildutil.ReadDir(bctx, dir)
448+
if err != nil {
449+
return nil, err
450+
}
451+
var jsFiles []JSFile
452+
for _, file := range files {
453+
if !strings.HasSuffix(file.Name(), ".inc.js") || file.IsDir() {
454+
continue
455+
}
456+
if file.Name()[0] == '_' || file.Name()[0] == '.' {
457+
continue // Skip "hidden" files that are typically ignored by the Go build system.
458+
}
459+
460+
path := buildutil.JoinPath(bctx, dir, file.Name())
461+
f, err := buildutil.OpenFile(bctx, path)
462+
if err != nil {
463+
return nil, fmt.Errorf("failed to open %s from %v: %w", path, bctx, err)
464+
}
465+
defer f.Close()
466+
467+
content, err := io.ReadAll(f)
468+
if err != nil {
469+
return nil, fmt.Errorf("failed to read %s from %v: %w", path, bctx, err)
470+
}
471+
472+
jsFiles = append(jsFiles, JSFile{
473+
Path: path,
474+
ModTime: file.ModTime(),
475+
Content: content,
476+
})
477+
}
478+
return jsFiles, nil
479+
}

0 commit comments

Comments
 (0)