Skip to content

Commit d431144

Browse files
authored
Merge pull request #1119 from nevkontakte/inc-js
Support .inc.js files for standard library overlays.
2 parents 1497816 + 930b459 commit d431144

File tree

3 files changed

+142
-59
lines changed

3 files changed

+142
-59
lines changed

build/build.go

Lines changed: 103 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,21 @@ import (
1313
"go/scanner"
1414
"go/token"
1515
"go/types"
16-
"io/ioutil"
1716
"os"
1817
"os/exec"
1918
"path"
2019
"path/filepath"
20+
"sort"
2121
"strconv"
2222
"strings"
2323
"time"
2424

2525
"github.com/fsnotify/fsnotify"
2626
"github.com/gopherjs/gopherjs/compiler"
2727
"github.com/gopherjs/gopherjs/compiler/astutil"
28-
"github.com/gopherjs/gopherjs/compiler/gopherjspkg"
28+
log "github.com/sirupsen/logrus"
2929

3030
"github.com/neelance/sourcemap"
31-
"github.com/shurcooL/httpfs/vfsutil"
3231
"golang.org/x/tools/go/buildutil"
3332

3433
"github.com/gopherjs/gopherjs/build/cache"
@@ -64,20 +63,6 @@ func NewBuildContext(installSuffix string, buildTags []string) XContext {
6463
}
6564
}
6665

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-
8166
// Import returns details about the Go package named by the import path. If the
8267
// path is a local import path naming a package that can be imported using
8368
// a standard import path, the returned package will set p.ImportPath to
@@ -161,7 +146,7 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
161146
// as an existing file from the standard library). For all identifiers that exist
162147
// in the original AND the overrides, the original identifier in the AST gets
163148
// 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) {
149+
func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) {
165150
var files []*ast.File
166151
replacedDeclNames := make(map[string]bool)
167152
pruneOriginalFuncs := make(map[string]bool)
@@ -172,9 +157,12 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
172157
importPath = importPath[:len(importPath)-5]
173158
}
174159

160+
jsFiles := []JSFile{}
161+
175162
nativesContext := overlayCtx(xctx.Env())
176163

177164
if nativesPkg, err := nativesContext.Import(importPath, "", 0); err == nil {
165+
jsFiles = nativesPkg.JSFiles
178166
names := nativesPkg.GoFiles
179167
if isTest {
180168
names = append(names, nativesPkg.TestGoFiles...)
@@ -229,7 +217,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
229217
}
230218
r, err := buildutil.OpenFile(pkg.bctx, name)
231219
if err != nil {
232-
return nil, err
220+
return nil, nil, err
233221
}
234222
file, err := parser.ParseFile(fileSet, name, r, parser.ParseComments)
235223
r.Close()
@@ -298,9 +286,9 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
298286
}
299287

300288
if errList != nil {
301-
return nil, errList
289+
return nil, nil, errList
302290
}
303-
return files, nil
291+
return files, jsFiles, nil
304292
}
305293

306294
// Options controls build process behavior.
@@ -333,11 +321,18 @@ func (o *Options) PrintSuccess(format string, a ...interface{}) {
333321
fmt.Fprintf(os.Stderr, format, a...)
334322
}
335323

324+
// JSFile represents a *.inc.js file metadata and content.
325+
type JSFile struct {
326+
Path string // Full file path for the build context the file came from.
327+
ModTime time.Time
328+
Content []byte
329+
}
330+
336331
// PackageData is an extension of go/build.Package with additional metadata
337332
// GopherJS requires.
338333
type PackageData struct {
339334
*build.Package
340-
JSFiles []string
335+
JSFiles []JSFile
341336
// IsTest is true if the package is being built for running tests.
342337
IsTest bool
343338
SrcModTime time.Time
@@ -352,6 +347,43 @@ func (p PackageData) String() string {
352347
return fmt.Sprintf("%s [is_test=%v]", p.ImportPath, p.IsTest)
353348
}
354349

350+
// FileModTime returns the most recent modification time of the package's source
351+
// files. This includes all .go and .inc.js that would be included in the build,
352+
// but excludes any dependencies.
353+
func (p PackageData) FileModTime() time.Time {
354+
newest := time.Time{}
355+
for _, file := range p.JSFiles {
356+
if file.ModTime.After(newest) {
357+
newest = file.ModTime
358+
}
359+
}
360+
361+
// Unfortunately, build.Context methods don't allow us to Stat and individual
362+
// file, only to enumerate a directory. So we first get mtimes for all files
363+
// in the package directory, and then pick the newest for the relevant GoFiles.
364+
mtimes := map[string]time.Time{}
365+
files, err := buildutil.ReadDir(p.bctx, p.Dir)
366+
if err != nil {
367+
log.Errorf("Failed to enumerate files in the %q in context %v: %s. Assuming time.Now().", p.Dir, p.bctx, err)
368+
return time.Now()
369+
}
370+
for _, file := range files {
371+
mtimes[file.Name()] = file.ModTime()
372+
}
373+
374+
for _, file := range p.GoFiles {
375+
t, ok := mtimes[file]
376+
if !ok {
377+
log.Errorf("No mtime found for source file %q of package %q, assuming time.Now().", file, p.Name)
378+
return time.Now()
379+
}
380+
if t.After(newest) {
381+
newest = t
382+
}
383+
}
384+
return newest
385+
}
386+
355387
// InternalBuildContext returns the build context that produced the package.
356388
//
357389
// WARNING: This function is a part of internal API and will be removed in
@@ -485,12 +517,38 @@ func (s *Session) GoRelease() string {
485517
//
486518
// A ephemeral package will be created with only the provided files. This
487519
// function is intended for use with, for example, `gopherjs run main.go`.
488-
func (s *Session) BuildFiles(filenames []string, pkgObj string, packagePath string) error {
520+
func (s *Session) BuildFiles(filenames []string, pkgObj string, cwd string) error {
521+
if len(filenames) == 0 {
522+
return fmt.Errorf("no input sources are provided")
523+
}
524+
525+
normalizedDir := func(filename string) string {
526+
d := filepath.Dir(filename)
527+
if !filepath.IsAbs(d) {
528+
d = filepath.Join(cwd, d)
529+
}
530+
return filepath.Clean(d)
531+
}
532+
533+
// Ensure all source files are in the same directory.
534+
dirSet := map[string]bool{}
535+
for _, file := range filenames {
536+
dirSet[normalizedDir(file)] = true
537+
}
538+
dirList := []string{}
539+
for dir := range dirSet {
540+
dirList = append(dirList, dir)
541+
}
542+
sort.Strings(dirList)
543+
if len(dirList) != 1 {
544+
return fmt.Errorf("named files must all be in one directory; have: %v", strings.Join(dirList, ", "))
545+
}
546+
489547
pkg := &PackageData{
490548
Package: &build.Package{
491549
Name: "main",
492550
ImportPath: "main",
493-
Dir: packagePath,
551+
Dir: dirList[0],
494552
},
495553
// This ephemeral package doesn't have a unique import path to be used as a
496554
// build cache key, so we never cache it.
@@ -499,11 +557,24 @@ func (s *Session) BuildFiles(filenames []string, pkgObj string, packagePath stri
499557
}
500558

501559
for _, file := range filenames {
502-
if strings.HasSuffix(file, ".inc.js") {
503-
pkg.JSFiles = append(pkg.JSFiles, file)
560+
if !strings.HasSuffix(file, ".inc.js") {
561+
pkg.GoFiles = append(pkg.GoFiles, filepath.Base(file))
504562
continue
505563
}
506-
pkg.GoFiles = append(pkg.GoFiles, file)
564+
565+
content, err := os.ReadFile(file)
566+
if err != nil {
567+
return fmt.Errorf("failed to read %s: %w", file, err)
568+
}
569+
info, err := os.Stat(file)
570+
if err != nil {
571+
return fmt.Errorf("failed to stat %s: %w", file, err)
572+
}
573+
pkg.JSFiles = append(pkg.JSFiles, JSFile{
574+
Path: filepath.Join(pkg.Dir, filepath.Base(file)),
575+
ModTime: info.ModTime(),
576+
Content: content,
577+
})
507578
}
508579

509580
archive, err := s.BuildPackage(pkg)
@@ -579,14 +650,8 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
579650
}
580651
}
581652

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-
}
653+
if pkg.FileModTime().After(pkg.SrcModTime) {
654+
pkg.SrcModTime = pkg.FileModTime()
590655
}
591656

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

604669
// Existing archive is out of date or doesn't exist, let's build the package.
605670
fileSet := token.NewFileSet()
606-
files, err := parseAndAugment(s.xctx, pkg, pkg.IsTest, fileSet)
671+
files, overlayJsFiles, err := parseAndAugment(s.xctx, pkg, pkg.IsTest, fileSet)
607672
if err != nil {
608673
return nil, err
609674
}
@@ -617,13 +682,9 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
617682
return nil, err
618683
}
619684

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-
}
685+
for _, jsFile := range append(pkg.JSFiles, overlayJsFiles...) {
625686
archive.IncJSCode = append(archive.IncJSCode, []byte("\t(function() {\n")...)
626-
archive.IncJSCode = append(archive.IncJSCode, code...)
687+
archive.IncJSCode = append(archive.IncJSCode, jsFile.Content...)
627688
archive.IncJSCode = append(archive.IncJSCode, []byte("\n\t}).call($global);\n")...)
628689
}
629690

@@ -721,22 +782,6 @@ func NewMappingCallback(m *sourcemap.Map, goroot, gopath string, localMap bool)
721782
}
722783
}
723784

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-
740785
// hasGopathPrefix returns true and the length of the matched GOPATH workspace,
741786
// iff file has a prefix that matches one of the GOPATH workspaces.
742787
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)