@@ -13,22 +13,21 @@ import (
13
13
"go/scanner"
14
14
"go/token"
15
15
"go/types"
16
- "io/ioutil"
17
16
"os"
18
17
"os/exec"
19
18
"path"
20
19
"path/filepath"
20
+ "sort"
21
21
"strconv"
22
22
"strings"
23
23
"time"
24
24
25
25
"github.com/fsnotify/fsnotify"
26
26
"github.com/gopherjs/gopherjs/compiler"
27
27
"github.com/gopherjs/gopherjs/compiler/astutil"
28
- "github.com/gopherjs/gopherjs/compiler/gopherjspkg "
28
+ log "github.com/sirupsen/logrus "
29
29
30
30
"github.com/neelance/sourcemap"
31
- "github.com/shurcooL/httpfs/vfsutil"
32
31
"golang.org/x/tools/go/buildutil"
33
32
34
33
"github.com/gopherjs/gopherjs/build/cache"
@@ -64,20 +63,6 @@ func NewBuildContext(installSuffix string, buildTags []string) XContext {
64
63
}
65
64
}
66
65
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
-
81
66
// Import returns details about the Go package named by the import path. If the
82
67
// path is a local import path naming a package that can be imported using
83
68
// 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
161
146
// as an existing file from the standard library). For all identifiers that exist
162
147
// in the original AND the overrides, the original identifier in the AST gets
163
148
// 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 ) {
165
150
var files []* ast.File
166
151
replacedDeclNames := make (map [string ]bool )
167
152
pruneOriginalFuncs := make (map [string ]bool )
@@ -172,9 +157,12 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
172
157
importPath = importPath [:len (importPath )- 5 ]
173
158
}
174
159
160
+ jsFiles := []JSFile {}
161
+
175
162
nativesContext := overlayCtx (xctx .Env ())
176
163
177
164
if nativesPkg , err := nativesContext .Import (importPath , "" , 0 ); err == nil {
165
+ jsFiles = nativesPkg .JSFiles
178
166
names := nativesPkg .GoFiles
179
167
if isTest {
180
168
names = append (names , nativesPkg .TestGoFiles ... )
@@ -229,7 +217,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
229
217
}
230
218
r , err := buildutil .OpenFile (pkg .bctx , name )
231
219
if err != nil {
232
- return nil , err
220
+ return nil , nil , err
233
221
}
234
222
file , err := parser .ParseFile (fileSet , name , r , parser .ParseComments )
235
223
r .Close ()
@@ -298,9 +286,9 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
298
286
}
299
287
300
288
if errList != nil {
301
- return nil , errList
289
+ return nil , nil , errList
302
290
}
303
- return files , nil
291
+ return files , jsFiles , nil
304
292
}
305
293
306
294
// Options controls build process behavior.
@@ -333,11 +321,18 @@ func (o *Options) PrintSuccess(format string, a ...interface{}) {
333
321
fmt .Fprintf (os .Stderr , format , a ... )
334
322
}
335
323
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
+
336
331
// PackageData is an extension of go/build.Package with additional metadata
337
332
// GopherJS requires.
338
333
type PackageData struct {
339
334
* build.Package
340
- JSFiles []string
335
+ JSFiles []JSFile
341
336
// IsTest is true if the package is being built for running tests.
342
337
IsTest bool
343
338
SrcModTime time.Time
@@ -352,6 +347,43 @@ func (p PackageData) String() string {
352
347
return fmt .Sprintf ("%s [is_test=%v]" , p .ImportPath , p .IsTest )
353
348
}
354
349
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
+
355
387
// InternalBuildContext returns the build context that produced the package.
356
388
//
357
389
// WARNING: This function is a part of internal API and will be removed in
@@ -485,12 +517,38 @@ func (s *Session) GoRelease() string {
485
517
//
486
518
// A ephemeral package will be created with only the provided files. This
487
519
// 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
+
489
547
pkg := & PackageData {
490
548
Package : & build.Package {
491
549
Name : "main" ,
492
550
ImportPath : "main" ,
493
- Dir : packagePath ,
551
+ Dir : dirList [ 0 ] ,
494
552
},
495
553
// This ephemeral package doesn't have a unique import path to be used as a
496
554
// build cache key, so we never cache it.
@@ -499,11 +557,24 @@ func (s *Session) BuildFiles(filenames []string, pkgObj string, packagePath stri
499
557
}
500
558
501
559
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 ) )
504
562
continue
505
563
}
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
+ })
507
578
}
508
579
509
580
archive , err := s .BuildPackage (pkg )
@@ -579,14 +650,8 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
579
650
}
580
651
}
581
652
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 ()
590
655
}
591
656
592
657
if ! s .options .NoCache {
@@ -603,7 +668,7 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
603
668
604
669
// Existing archive is out of date or doesn't exist, let's build the package.
605
670
fileSet := token .NewFileSet ()
606
- files , err := parseAndAugment (s .xctx , pkg , pkg .IsTest , fileSet )
671
+ files , overlayJsFiles , err := parseAndAugment (s .xctx , pkg , pkg .IsTest , fileSet )
607
672
if err != nil {
608
673
return nil , err
609
674
}
@@ -617,13 +682,9 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
617
682
return nil , err
618
683
}
619
684
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 ... ) {
625
686
archive .IncJSCode = append (archive .IncJSCode , []byte ("\t (function() {\n " )... )
626
- archive .IncJSCode = append (archive .IncJSCode , code ... )
687
+ archive .IncJSCode = append (archive .IncJSCode , jsFile . Content ... )
627
688
archive .IncJSCode = append (archive .IncJSCode , []byte ("\n \t }).call($global);\n " )... )
628
689
}
629
690
@@ -721,22 +782,6 @@ func NewMappingCallback(m *sourcemap.Map, goroot, gopath string, localMap bool)
721
782
}
722
783
}
723
784
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
-
740
785
// hasGopathPrefix returns true and the length of the matched GOPATH workspace,
741
786
// iff file has a prefix that matches one of the GOPATH workspaces.
742
787
func hasGopathPrefix (file , gopath string ) (hasGopathPrefix bool , prefixLen int ) {
0 commit comments