Skip to content

Use sources instead of parsed package #1362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 50 additions & 86 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import (
"github.com/fsnotify/fsnotify"
"github.com/gopherjs/gopherjs/compiler"
"github.com/gopherjs/gopherjs/compiler/astutil"
"github.com/gopherjs/gopherjs/compiler/jsFile"
"github.com/gopherjs/gopherjs/compiler/sources"
"github.com/gopherjs/gopherjs/internal/errorList"
"github.com/gopherjs/gopherjs/internal/testmain"
log "github.com/sirupsen/logrus"

Expand Down Expand Up @@ -163,7 +166,7 @@ type overrideInfo struct {
// - Otherwise for identifiers that exist in the original and the overrides,
// the original is removed.
// - New identifiers that don't exist in original package get added.
func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) {
func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []jsFile.JSFile, error) {
jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, isTest, fileSet)

originalFiles, err := parserOriginalFiles(pkg, fileSet)
Expand Down Expand Up @@ -192,7 +195,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke

// parseOverlayFiles loads and parses overlay files
// to augment the original files with.
func parseOverlayFiles(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]JSFile, []*ast.File) {
func parseOverlayFiles(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]jsFile.JSFile, []*ast.File) {
isXTest := strings.HasSuffix(pkg.ImportPath, "_test")
importPath := pkg.ImportPath
if isXTest {
Expand Down Expand Up @@ -238,7 +241,7 @@ func parseOverlayFiles(xctx XContext, pkg *PackageData, isTest bool, fileSet *to
// parserOriginalFiles loads and parses the original files to augment.
func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File, error) {
var files []*ast.File
var errList compiler.ErrorList
var errList errorList.ErrorList
for _, name := range pkg.GoFiles {
if !filepath.IsAbs(name) { // name might be absolute if specified directly. E.g., `gopherjs build /abs/file.go`.
name = filepath.Join(pkg.Dir, name)
Expand Down Expand Up @@ -622,18 +625,11 @@ func (o *Options) PrintSuccess(format string, a ...interface{}) {
fmt.Fprintf(os.Stderr, format, a...)
}

// JSFile represents a *.inc.js file metadata and content.
type JSFile struct {
Path string // Full file path for the build context the file came from.
ModTime time.Time
Content []byte
}

// PackageData is an extension of go/build.Package with additional metadata
// GopherJS requires.
type PackageData struct {
*build.Package
JSFiles []JSFile
JSFiles []jsFile.JSFile
// IsTest is true if the package is being built for running tests.
IsTest bool
SrcModTime time.Time
Expand Down Expand Up @@ -743,43 +739,6 @@ func (p *PackageData) InstallPath() string {
return p.PkgObj
}

// ParsedPackage is the results from building a package before it is compiled.
type ParsedPackage struct {
ImportPath string // import path of package ("" if unknown)
Dir string // directory containing package sources

// GoFiles is the parsed and augmented Go AST files for the package.
GoFiles []*ast.File
FileSet *token.FileSet
JSFiles []JSFile
}

// Imports calculates the import paths of the package's dependencies
// based on all the imports in the augmented files.
//
// The given skip paths will not be returned in the results.
// This will not return any `*_test` packages in the results.
func (p *ParsedPackage) Imports(skip ...string) []string {
seen := make(map[string]struct{})
for _, s := range skip {
seen[s] = struct{}{}
}
imports := []string{}
for _, file := range p.GoFiles {
for _, imp := range file.Imports {
path := strings.Trim(imp.Path.Value, `"`)
if _, ok := seen[path]; !ok {
if !strings.HasSuffix(path, "_test") {
imports = append(imports, path)
}
seen[path] = struct{}{}
}
}
}
sort.Strings(imports)
return imports
}

// Session manages internal state GopherJS requires to perform a build.
//
// This is the main interface to GopherJS build system. Session lifetime is
Expand All @@ -797,10 +756,11 @@ type Session struct {
// is the unresolved import path and source directory.
importPaths map[string]map[string]string

// parsePackage is a map of parsed packages that have been built and augmented.
// sources is a map of parsed packages that have been built and augmented.
// This is keyed using resolved import paths. This is used to avoid
// rebuilding and augmenting packages that are imported by several packages.
parsedPackages map[string]*ParsedPackage
// These sources haven't been sorted nor simplified yet.
sources map[string]*sources.Sources

// Binary archives produced during the current session and assumed to be
// up to date with input sources and dependencies. In the -w ("watch") mode
Expand All @@ -817,7 +777,7 @@ func NewSession(options *Options) (*Session, error) {
s := &Session{
options: options,
importPaths: make(map[string]map[string]string),
parsedPackages: make(map[string]*ParsedPackage),
sources: make(map[string]*sources.Sources),
UpToDateArchives: make(map[string]*compiler.Archive),
}
s.xctx = NewBuildContext(s.InstallSuffix(), s.options.BuildTags)
Expand Down Expand Up @@ -935,7 +895,7 @@ func (s *Session) BuildFiles(filenames []string, pkgObj string, cwd string) erro
if err != nil {
return fmt.Errorf("failed to stat %s: %w", file, err)
}
pkg.JSFiles = append(pkg.JSFiles, JSFile{
pkg.JSFiles = append(pkg.JSFiles, jsFile.JSFile{
Path: filepath.Join(pkg.Dir, filepath.Base(file)),
ModTime: info.ModTime(),
Content: content,
Expand All @@ -958,13 +918,13 @@ func (s *Session) BuildProject(pkg *PackageData) (*compiler.Archive, error) {
// ensure that runtime for gopherjs is imported
pkg.Imports = append(pkg.Imports, `runtime`)

// Build the project to get the parsed packages.
var parsed *ParsedPackage
// Build the project to get the sources for the parsed packages.
var srcs *sources.Sources
var err error
if pkg.IsTest {
parsed, err = s.loadTestPackage(pkg)
srcs, err = s.loadTestPackage(pkg)
} else {
parsed, err = s.loadPackages(pkg)
srcs, err = s.loadPackages(pkg)
}
if err != nil {
return nil, err
Expand All @@ -976,10 +936,10 @@ func (s *Session) BuildProject(pkg *PackageData) (*compiler.Archive, error) {
// flatten, blocking, etc. information and check types to get the type info
// with all the instances for all generics in the whole project.

return s.compilePackages(parsed)
return s.compilePackages(srcs)
}

func (s *Session) loadTestPackage(pkg *PackageData) (*ParsedPackage, error) {
func (s *Session) loadTestPackage(pkg *PackageData) (*sources.Sources, error) {
_, err := s.loadPackages(pkg.TestPackage())
if err != nil {
return nil, err
Expand All @@ -998,30 +958,30 @@ func (s *Session) loadTestPackage(pkg *PackageData) (*ParsedPackage, error) {
return nil, fmt.Errorf("failed to generate testmain package for %s: %w", pkg.ImportPath, err)
}

// Create a parsed package for the testmain package.
parsed := &ParsedPackage{
// Create the sources for parsed package for the testmain package.
srcs := &sources.Sources{
ImportPath: mainPkg.ImportPath,
Dir: mainPkg.Dir,
GoFiles: []*ast.File{mainFile},
Files: []*ast.File{mainFile},
FileSet: fset,
}

// Import dependencies for the testmain package.
for _, importedPkgPath := range parsed.Imports() {
for _, importedPkgPath := range srcs.UnresolvedImports() {
_, _, err := s.loadImportPathWithSrcDir(importedPkgPath, pkg.Dir)
if err != nil {
return nil, err
}
}

return parsed, nil
return srcs, nil
}

// loadImportPathWithSrcDir gets the parsed package specified by the import path.
//
// Relative import paths are interpreted relative to the passed srcDir.
// If srcDir is empty, current working directory is assumed.
func (s *Session) loadImportPathWithSrcDir(path, srcDir string) (*PackageData, *ParsedPackage, error) {
func (s *Session) loadImportPathWithSrcDir(path, srcDir string) (*PackageData, *sources.Sources, error) {
pkg, err := s.xctx.Import(path, srcDir, 0)
if s.Watcher != nil && pkg != nil { // add watch even on error
s.Watcher.Add(pkg.Dir)
Expand All @@ -1030,13 +990,13 @@ func (s *Session) loadImportPathWithSrcDir(path, srcDir string) (*PackageData, *
return nil, nil, err
}

parsed, err := s.loadPackages(pkg)
srcs, err := s.loadPackages(pkg)
if err != nil {
return nil, nil, err
}

s.cacheImportPath(path, srcDir, pkg.ImportPath)
return pkg, parsed, nil
return pkg, srcs, nil
}

// cacheImportPath stores the resolved import path for the build package
Expand Down Expand Up @@ -1077,9 +1037,13 @@ var getExeModTime = func() func() time.Time {
}
}()

func (s *Session) loadPackages(pkg *PackageData) (*ParsedPackage, error) {
if parsed, ok := s.parsedPackages[pkg.ImportPath]; ok {
return parsed, nil
// loadPackages will recursively load and parse the given package and
// its dependencies. This will return the sources for the given package.
// The returned source and sources for the dependencies will be added
// to the session's sources map.
func (s *Session) loadPackages(pkg *PackageData) (*sources.Sources, error) {
if srcs, ok := s.sources[pkg.ImportPath]; ok {
return srcs, nil
}

if exeModTime := getExeModTime(); exeModTime.After(pkg.SrcModTime) {
Expand Down Expand Up @@ -1118,52 +1082,52 @@ func (s *Session) loadPackages(pkg *PackageData) (*ParsedPackage, error) {
files = append(files, embed)
}

parsed := &ParsedPackage{
srcs := &sources.Sources{
ImportPath: pkg.ImportPath,
Dir: pkg.Dir,
GoFiles: files,
Files: files,
FileSet: fileSet,
JSFiles: append(pkg.JSFiles, overlayJsFiles...),
}
s.parsedPackages[pkg.ImportPath] = parsed
s.sources[pkg.ImportPath] = srcs

// Import dependencies from the augmented files,
// whilst skipping any that have been already imported.
for _, importedPkgPath := range parsed.Imports(pkg.Imports...) {
for _, importedPkgPath := range srcs.UnresolvedImports(pkg.Imports...) {
_, _, err := s.loadImportPathWithSrcDir(importedPkgPath, pkg.Dir)
if err != nil {
return nil, err
}
}

return parsed, nil
return srcs, nil
}

func (s *Session) compilePackages(pkg *ParsedPackage) (*compiler.Archive, error) {
if archive, ok := s.UpToDateArchives[pkg.ImportPath]; ok {
func (s *Session) compilePackages(srcs *sources.Sources) (*compiler.Archive, error) {
if archive, ok := s.UpToDateArchives[srcs.ImportPath]; ok {
return archive, nil
}

importContext := &compiler.ImportContext{
Packages: s.Types,
Import: s.ImportResolverFor(pkg.Dir),
Packages: s.Types,
ImportArchive: s.ImportResolverFor(srcs.Dir),
}
archive, err := compiler.Compile(pkg.ImportPath, pkg.GoFiles, pkg.FileSet, importContext, s.options.Minify)
archive, err := compiler.Compile(*srcs, importContext, s.options.Minify)
if err != nil {
return nil, err
}

for _, jsFile := range pkg.JSFiles {
for _, jsFile := range srcs.JSFiles {
archive.IncJSCode = append(archive.IncJSCode, []byte("\t(function() {\n")...)
archive.IncJSCode = append(archive.IncJSCode, jsFile.Content...)
archive.IncJSCode = append(archive.IncJSCode, []byte("\n\t}).call($global);\n")...)
}

if s.options.Verbose {
fmt.Println(pkg.ImportPath)
fmt.Println(srcs.ImportPath)
}

s.UpToDateArchives[pkg.ImportPath] = archive
s.UpToDateArchives[srcs.ImportPath] = archive

return archive, nil
}
Expand Down Expand Up @@ -1201,12 +1165,12 @@ func (s *Session) ImportResolverFor(srcDir string) func(string) (*compiler.Archi
return archive, nil
}

// The archive hasn't been compiled yet so compile it with the parsed package.
if parsed, ok := s.parsedPackages[importPath]; ok {
return s.compilePackages(parsed)
// The archive hasn't been compiled yet so compile it with the sources.
if srcs, ok := s.sources[importPath]; ok {
return s.compilePackages(srcs)
}

return nil, fmt.Errorf(`parsed package for %q not found`, importPath)
return nil, fmt.Errorf(`sources for %q not found`, importPath)
}
}

Expand Down
41 changes: 2 additions & 39 deletions build/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"go/build"
"go/token"
"io"
"net/http"
"os"
"os/exec"
Expand All @@ -16,6 +15,7 @@ import (
_ "github.com/gopherjs/gopherjs/build/versionhack" // go/build release tags hack.
"github.com/gopherjs/gopherjs/compiler"
"github.com/gopherjs/gopherjs/compiler/gopherjspkg"
"github.com/gopherjs/gopherjs/compiler/jsFile"
"github.com/gopherjs/gopherjs/compiler/natives"
"golang.org/x/tools/go/buildutil"
)
Expand Down Expand Up @@ -91,7 +91,7 @@ func (sc simpleCtx) Import(importPath string, srcDir string, mode build.ImportMo
if err != nil {
return nil, err
}
jsFiles, err := jsFilesFromDir(&sc.bctx, pkg.Dir)
jsFiles, err := jsFile.JSFilesFromDir(&sc.bctx, pkg.Dir)
if err != nil {
return nil, fmt.Errorf("failed to enumerate .inc.js files in %s: %w", pkg.Dir, err)
}
Expand Down Expand Up @@ -440,40 +440,3 @@ func updateImports(sources []string, importPos map[string][]token.Position) (new
sort.Strings(newImports)
return newImports, newImportPos
}

// jsFilesFromDir finds and loads any *.inc.js packages in the build context
// directory.
func jsFilesFromDir(bctx *build.Context, dir string) ([]JSFile, error) {
files, err := buildutil.ReadDir(bctx, dir)
if err != nil {
return nil, err
}
var jsFiles []JSFile
for _, file := range files {
if !strings.HasSuffix(file.Name(), ".inc.js") || file.IsDir() {
continue
}
if file.Name()[0] == '_' || file.Name()[0] == '.' {
continue // Skip "hidden" files that are typically ignored by the Go build system.
}

path := buildutil.JoinPath(bctx, dir, file.Name())
f, err := buildutil.OpenFile(bctx, path)
if err != nil {
return nil, fmt.Errorf("failed to open %s from %v: %w", path, bctx, err)
}
defer f.Close()

content, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("failed to read %s from %v: %w", path, bctx, err)
}

jsFiles = append(jsFiles, JSFile{
Path: path,
ModTime: file.ModTime(),
Content: content,
})
}
return jsFiles, nil
}
Loading
Loading