@@ -27,12 +27,11 @@ import (
27
27
"github.com/fsnotify/fsnotify"
28
28
"github.com/gopherjs/gopherjs/compiler"
29
29
"github.com/gopherjs/gopherjs/compiler/astutil"
30
+ "github.com/gopherjs/gopherjs/internal/testmain"
30
31
log "github.com/sirupsen/logrus"
31
32
32
33
"github.com/neelance/sourcemap"
33
34
"golang.org/x/tools/go/buildutil"
34
-
35
- "github.com/gopherjs/gopherjs/build/cache"
36
35
)
37
36
38
37
// DefaultGOROOT is the default GOROOT value for builds.
@@ -744,14 +743,61 @@ func (p *PackageData) InstallPath() string {
744
743
return p .PkgObj
745
744
}
746
745
746
+ // ParsedPackage is the results from building a package before it is compiled.
747
+ type ParsedPackage struct {
748
+ ImportPath string // import path of package ("" if unknown)
749
+ Dir string // directory containing package sources
750
+
751
+ // GoFiles is the parsed and augmented Go AST files for the package.
752
+ GoFiles []* ast.File
753
+ FileSet * token.FileSet
754
+ JSFiles []JSFile
755
+ }
756
+
757
+ // Imports calculates the import paths of the package's dependencies
758
+ // based on all the imports in the augmented files.
759
+ //
760
+ // The given skip paths will not be returned in the results.
761
+ // This will not return any `*_test` packages in the results.
762
+ func (p * ParsedPackage ) Imports (skip ... string ) []string {
763
+ importMap := make (map [string ]struct {})
764
+ for _ , file := range p .GoFiles {
765
+ for _ , imp := range file .Imports {
766
+ path := strings .Trim (imp .Path .Value , `"` )
767
+ if ! strings .HasSuffix (path , "_test" ) {
768
+ importMap [path ] = struct {}{}
769
+ }
770
+ }
771
+ }
772
+ for _ , skip := range skip {
773
+ delete (importMap , skip )
774
+ }
775
+
776
+ imports := make ([]string , 0 , len (importMap ))
777
+ for imp := range importMap {
778
+ imports = append (imports , imp )
779
+ }
780
+ sort .Strings (imports )
781
+ return imports
782
+ }
783
+
747
784
// Session manages internal state GopherJS requires to perform a build.
748
785
//
749
786
// This is the main interface to GopherJS build system. Session lifetime is
750
787
// roughly equivalent to a single GopherJS tool invocation.
751
788
type Session struct {
752
- options * Options
753
- xctx XContext
754
- buildCache cache.BuildCache
789
+ options * Options
790
+ xctx XContext
791
+
792
+ // importPaths is a map of the resolved import paths given the srcDir
793
+ // and path. This is used to avoid redetermining build packages during
794
+ // compilation when we're looking up parsed packages.
795
+ importPaths map [string ]map [string ]string
796
+
797
+ // parsePackage is a map of parsed packages that have been built and augmented.
798
+ // This is keyed using resolved import paths. This is used to avoid
799
+ // rebuilding and augmenting packages that are imported by several packages.
800
+ parsedPackages map [string ]* ParsedPackage
755
801
756
802
// Binary archives produced during the current session and assumed to be
757
803
// up to date with input sources and dependencies. In the -w ("watch") mode
@@ -767,6 +813,8 @@ func NewSession(options *Options) (*Session, error) {
767
813
768
814
s := & Session {
769
815
options : options ,
816
+ importPaths : make (map [string ]map [string ]string ),
817
+ parsedPackages : make (map [string ]* ParsedPackage ),
770
818
UpToDateArchives : make (map [string ]* compiler.Archive ),
771
819
}
772
820
s .xctx = NewBuildContext (s .InstallSuffix (), s .options .BuildTags )
@@ -777,15 +825,6 @@ func NewSession(options *Options) (*Session, error) {
777
825
return nil , err
778
826
}
779
827
780
- s .buildCache = cache.BuildCache {
781
- GOOS : env .GOOS ,
782
- GOARCH : env .GOARCH ,
783
- GOROOT : env .GOROOT ,
784
- GOPATH : env .GOPATH ,
785
- BuildTags : append ([]string {}, env .BuildTags ... ),
786
- Minify : options .Minify ,
787
- TestedPackage : options .TestedPackage ,
788
- }
789
828
s .Types = make (map [string ]* types.Package )
790
829
if options .Watch {
791
830
if out , err := exec .Command ("ulimit" , "-n" ).Output (); err == nil {
@@ -900,7 +939,7 @@ func (s *Session) BuildFiles(filenames []string, pkgObj string, cwd string) erro
900
939
})
901
940
}
902
941
903
- archive , err := s .BuildPackage (pkg )
942
+ archive , err := s .BuildProject (pkg )
904
943
if err != nil {
905
944
return err
906
945
}
@@ -910,19 +949,76 @@ func (s *Session) BuildFiles(filenames []string, pkgObj string, cwd string) erro
910
949
return s .WriteCommandPackage (archive , pkgObj )
911
950
}
912
951
913
- // BuildImportPath loads and compiles package with the given import path.
914
- //
915
- // Relative paths are interpreted relative to the current working dir.
916
- func (s * Session ) BuildImportPath (path string ) (* compiler.Archive , error ) {
917
- _ , archive , err := s .buildImportPathWithSrcDir (path , "" )
918
- return archive , err
952
+ // BuildProject builds a command project (one with a main method) or
953
+ // builds a test project (one with a synthesized test main package).
954
+ func (s * Session ) BuildProject (pkg * PackageData ) (* compiler.Archive , error ) {
955
+ // ensure that runtime for gopherjs is imported
956
+ pkg .Imports = append (pkg .Imports , `runtime` )
957
+
958
+ // Build the project to get the parsed packages.
959
+ var parsed * ParsedPackage
960
+ var err error
961
+ if pkg .IsTest {
962
+ parsed , err = s .buildTestPackage (pkg )
963
+ } else {
964
+ parsed , err = s .buildPackages (pkg )
965
+ }
966
+ if err != nil {
967
+ return nil , err
968
+ }
969
+
970
+ // TODO(grantnelson-wf): At this point we have all the parsed packages we
971
+ // need to compile the whole project, including testmain, if needed.
972
+ // We can perform analysis on the whole project at this point to propagate
973
+ // flatten, blocking, etc. information and check types to get the type info
974
+ // with all the instances for all generics in the whole project.
975
+
976
+ return s .compilePackages (parsed )
977
+ }
978
+
979
+ func (s * Session ) buildTestPackage (pkg * PackageData ) (* ParsedPackage , error ) {
980
+ _ , err := s .buildPackages (pkg .TestPackage ())
981
+ if err != nil {
982
+ return nil , err
983
+ }
984
+ _ , err = s .buildPackages (pkg .XTestPackage ())
985
+ if err != nil {
986
+ return nil , err
987
+ }
988
+
989
+ // Generate a synthetic testmain package.
990
+ fset := token .NewFileSet ()
991
+ tests := testmain.TestMain {Package : pkg .Package , Context : pkg .bctx }
992
+ tests .Scan (fset )
993
+ mainPkg , mainFile , err := tests .Synthesize (fset )
994
+ if err != nil {
995
+ return nil , fmt .Errorf ("failed to generate testmain package for %s: %w" , pkg .ImportPath , err )
996
+ }
997
+
998
+ // Create a parsed package for the testmain package.
999
+ parsed := & ParsedPackage {
1000
+ ImportPath : mainPkg .ImportPath ,
1001
+ Dir : mainPkg .Dir ,
1002
+ GoFiles : []* ast.File {mainFile },
1003
+ FileSet : fset ,
1004
+ }
1005
+
1006
+ // Import dependencies for the testmain package.
1007
+ for _ , importedPkgPath := range parsed .Imports () {
1008
+ _ , _ , err := s .buildImportPathWithSrcDir (importedPkgPath , pkg .Dir )
1009
+ if err != nil {
1010
+ return nil , err
1011
+ }
1012
+ }
1013
+
1014
+ return parsed , nil
919
1015
}
920
1016
921
- // buildImportPathWithSrcDir builds the package specified by the import path.
1017
+ // buildImportPathWithSrcDir builds the parsed package specified by the import path.
922
1018
//
923
1019
// Relative import paths are interpreted relative to the passed srcDir. If
924
1020
// srcDir is empty, current working directory is assumed.
925
- func (s * Session ) buildImportPathWithSrcDir (path string , srcDir string ) (* PackageData , * compiler. Archive , error ) {
1021
+ func (s * Session ) buildImportPathWithSrcDir (path , srcDir string ) (* PackageData , * ParsedPackage , error ) {
926
1022
pkg , err := s .xctx .Import (path , srcDir , 0 )
927
1023
if s .Watcher != nil && pkg != nil { // add watch even on error
928
1024
s .Watcher .Add (pkg .Dir )
@@ -931,12 +1027,25 @@ func (s *Session) buildImportPathWithSrcDir(path string, srcDir string) (*Packag
931
1027
return nil , nil , err
932
1028
}
933
1029
934
- archive , err := s .BuildPackage (pkg )
1030
+ parsed , err := s .buildPackages (pkg )
935
1031
if err != nil {
936
1032
return nil , nil , err
937
1033
}
938
1034
939
- return pkg , archive , nil
1035
+ s .cacheImportPath (path , srcDir , pkg .ImportPath )
1036
+ return pkg , parsed , nil
1037
+ }
1038
+
1039
+ // cacheImportPath stores the import path for the build package so we can look
1040
+ // it up later without getting the whole build package.
1041
+ // The given path and source directly are the ones passed into
1042
+ // XContext.Import to the get the build package originally.
1043
+ func (s * Session ) cacheImportPath (path , srcDir , importPath string ) {
1044
+ if paths , ok := s .importPaths [srcDir ]; ok {
1045
+ paths [path ] = importPath
1046
+ } else {
1047
+ s .importPaths [srcDir ] = map [string ]string {path : importPath }
1048
+ }
940
1049
}
941
1050
942
1051
// getExeModTime will determine the mod time of the GopherJS binary
@@ -965,10 +1074,9 @@ var getExeModTime = func() func() time.Time {
965
1074
}
966
1075
}()
967
1076
968
- // BuildPackage compiles an already loaded package.
969
- func (s * Session ) BuildPackage (pkg * PackageData ) (* compiler.Archive , error ) {
970
- if archive , ok := s .UpToDateArchives [pkg .ImportPath ]; ok {
971
- return archive , nil
1077
+ func (s * Session ) buildPackages (pkg * PackageData ) (* ParsedPackage , error ) {
1078
+ if parsed , ok := s .parsedPackages [pkg .ImportPath ]; ok {
1079
+ return parsed , nil
972
1080
}
973
1081
974
1082
if exeModTime := getExeModTime (); exeModTime .After (pkg .SrcModTime ) {
@@ -993,16 +1101,7 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
993
1101
pkg .SrcModTime = fileModTime
994
1102
}
995
1103
996
- if ! s .options .NoCache {
997
- archive := s .buildCache .LoadArchive (pkg .ImportPath , pkg .SrcModTime , s .Types )
998
- if archive != nil {
999
- s .UpToDateArchives [pkg .ImportPath ] = archive
1000
- // Existing archive is up to date, no need to build it from scratch.
1001
- return archive , nil
1002
- }
1003
- }
1004
-
1005
- // Existing archive is out of date or doesn't exist, let's build the package.
1104
+ // Build the package by parsing and augmenting the original files with overlay files.
1006
1105
fileSet := token .NewFileSet ()
1007
1106
files , overlayJsFiles , err := parseAndAugment (s .xctx , pkg , pkg .IsTest , fileSet )
1008
1107
if err != nil {
@@ -1016,16 +1115,42 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
1016
1115
files = append (files , embed )
1017
1116
}
1018
1117
1118
+ parsed := & ParsedPackage {
1119
+ ImportPath : pkg .ImportPath ,
1120
+ Dir : pkg .Dir ,
1121
+ GoFiles : files ,
1122
+ FileSet : fileSet ,
1123
+ JSFiles : append (pkg .JSFiles , overlayJsFiles ... ),
1124
+ }
1125
+ s .parsedPackages [pkg .ImportPath ] = parsed
1126
+
1127
+ // Import dependencies from the augmented files,
1128
+ // whilst skipping any that have been already imported.
1129
+ for _ , importedPkgPath := range parsed .Imports (pkg .Imports ... ) {
1130
+ _ , _ , err := s .buildImportPathWithSrcDir (importedPkgPath , pkg .Dir )
1131
+ if err != nil {
1132
+ return nil , err
1133
+ }
1134
+ }
1135
+
1136
+ return parsed , nil
1137
+ }
1138
+
1139
+ func (s * Session ) compilePackages (pkg * ParsedPackage ) (* compiler.Archive , error ) {
1140
+ if archive , ok := s .UpToDateArchives [pkg .ImportPath ]; ok {
1141
+ return archive , nil
1142
+ }
1143
+
1019
1144
importContext := & compiler.ImportContext {
1020
1145
Packages : s .Types ,
1021
- Import : s .ImportResolverFor (pkg ),
1146
+ Import : s .ImportResolverFor (pkg . Dir ),
1022
1147
}
1023
- archive , err := compiler .Compile (pkg .ImportPath , files , fileSet , importContext , s .options .Minify )
1148
+ archive , err := compiler .Compile (pkg .ImportPath , pkg . GoFiles , pkg . FileSet , importContext , s .options .Minify )
1024
1149
if err != nil {
1025
1150
return nil , err
1026
1151
}
1027
1152
1028
- for _ , jsFile := range append ( pkg .JSFiles , overlayJsFiles ... ) {
1153
+ for _ , jsFile := range pkg .JSFiles {
1029
1154
archive .IncJSCode = append (archive .IncJSCode , []byte ("\t (function() {\n " )... )
1030
1155
archive .IncJSCode = append (archive .IncJSCode , jsFile .Content ... )
1031
1156
archive .IncJSCode = append (archive .IncJSCode , []byte ("\n \t }).call($global);\n " )... )
@@ -1035,21 +1160,50 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
1035
1160
fmt .Println (pkg .ImportPath )
1036
1161
}
1037
1162
1038
- s .buildCache .StoreArchive (archive , time .Now ())
1039
1163
s .UpToDateArchives [pkg .ImportPath ] = archive
1040
1164
1041
1165
return archive , nil
1042
1166
}
1043
1167
1168
+ func (s * Session ) getImportPath (path , srcDir string ) (string , error ) {
1169
+ // If path is for an xtest package, just return it.
1170
+ if strings .HasSuffix (path , "_test" ) {
1171
+ return path , nil
1172
+ }
1173
+
1174
+ // Check if the import path is already cached.
1175
+ if importPath , ok := s.importPaths [srcDir ][path ]; ok {
1176
+ return importPath , nil
1177
+ }
1178
+
1179
+ // Fall back to the slop import of the build package.
1180
+ pkg , err := s .xctx .Import (path , srcDir , 0 )
1181
+ if err != nil {
1182
+ return `` , err
1183
+ }
1184
+ s .cacheImportPath (path , srcDir , pkg .ImportPath )
1185
+ return pkg .ImportPath , nil
1186
+ }
1187
+
1044
1188
// ImportResolverFor returns a function which returns a compiled package archive
1045
1189
// given an import path.
1046
- func (s * Session ) ImportResolverFor (pkg * PackageData ) func (string ) (* compiler.Archive , error ) {
1190
+ func (s * Session ) ImportResolverFor (srcDir string ) func (string ) (* compiler.Archive , error ) {
1047
1191
return func (path string ) (* compiler.Archive , error ) {
1048
- if archive , ok := s .UpToDateArchives [path ]; ok {
1192
+ importPath , err := s .getImportPath (path , srcDir )
1193
+ if err != nil {
1194
+ return nil , err
1195
+ }
1196
+
1197
+ if archive , ok := s .UpToDateArchives [importPath ]; ok {
1049
1198
return archive , nil
1050
1199
}
1051
- _ , archive , err := s .buildImportPathWithSrcDir (path , pkg .Dir )
1052
- return archive , err
1200
+
1201
+ // The archive hasn't been compiled yet so compile it with the parsed package.
1202
+ if parsed , ok := s .parsedPackages [importPath ]; ok {
1203
+ return s .compilePackages (parsed )
1204
+ }
1205
+
1206
+ return nil , fmt .Errorf (`parsed package for %q not found` , importPath )
1053
1207
}
1054
1208
}
1055
1209
@@ -1087,13 +1241,7 @@ func (s *Session) WriteCommandPackage(archive *compiler.Archive, pkgObj string)
1087
1241
sourceMapFilter .MappingCallback = s .SourceMappingCallback (m )
1088
1242
}
1089
1243
1090
- deps , err := compiler .ImportDependencies (archive , func (path string ) (* compiler.Archive , error ) {
1091
- if archive , ok := s .UpToDateArchives [path ]; ok {
1092
- return archive , nil
1093
- }
1094
- _ , archive , err := s .buildImportPathWithSrcDir (path , "" )
1095
- return archive , err
1096
- })
1244
+ deps , err := compiler .ImportDependencies (archive , s .ImportResolverFor ("" ))
1097
1245
if err != nil {
1098
1246
return err
1099
1247
}
0 commit comments