github.com/goplusjs/gopherjs@v1.2.6-0.20211206034512-f187917453b8/build/build.go (about)

     1  package build
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/build"
     7  	"go/parser"
     8  	"go/scanner"
     9  	"go/token"
    10  	"go/types"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"path"
    16  	"path/filepath"
    17  	"runtime"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/fsnotify/fsnotify"
    24  	"github.com/goplusjs/gopherjs/compiler"
    25  	"github.com/goplusjs/gopherjs/compiler/gopherjspkg"
    26  	"github.com/goplusjs/gopherjs/compiler/natives"
    27  	"github.com/goplusjs/gopherjs/internal/goversion"
    28  	"github.com/neelance/sourcemap"
    29  	"github.com/shurcooL/httpfs/vfsutil"
    30  	"github.com/visualfc/fastmod"
    31  	"golang.org/x/tools/go/buildutil"
    32  )
    33  
    34  type ImportCError struct {
    35  	pkgPath string
    36  }
    37  
    38  func (e *ImportCError) Error() string {
    39  	return e.pkgPath + `: importing "C" is not supported by GopherJS`
    40  }
    41  
    42  // NewBuildContext creates a build context for building Go packages
    43  // with GopherJS compiler.
    44  //
    45  // Core GopherJS packages (i.e., "github.com/gopherjs/gopherjs/js", "github.com/gopherjs/gopherjs/nosync")
    46  // are loaded from gopherjspkg.FS virtual filesystem rather than GOPATH.
    47  func NewBuildContext(installSuffix string, buildTags []string) *build.Context {
    48  	gopherjsRoot := filepath.Join(build.Default.GOROOT, "src", "github.com", "gopherjs", "gopherjs")
    49  	return &build.Context{
    50  		GOROOT:        build.Default.GOROOT,
    51  		GOPATH:        build.Default.GOPATH,
    52  		GOOS:          build.Default.GOOS,
    53  		GOARCH:        "js",
    54  		InstallSuffix: installSuffix,
    55  		Compiler:      "gc",
    56  		BuildTags: append(buildTags,
    57  			"netgo",  // See https://godoc.org/net#hdr-Name_Resolution.
    58  			"purego", // See https://golang.org/issues/23172.
    59  		),
    60  		ReleaseTags: goversion.ReleaseTags(),
    61  		CgoEnabled:  true, // detect `import "C"` to throw proper error
    62  
    63  		IsDir: func(path string) bool {
    64  			if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) {
    65  				path = filepath.ToSlash(path[len(gopherjsRoot):])
    66  				if fi, err := vfsutil.Stat(gopherjspkg.FS, path); err == nil {
    67  					return fi.IsDir()
    68  				}
    69  			}
    70  			fi, err := os.Stat(path)
    71  			return err == nil && fi.IsDir()
    72  		},
    73  		ReadDir: func(path string) ([]os.FileInfo, error) {
    74  			if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) {
    75  				path = filepath.ToSlash(path[len(gopherjsRoot):])
    76  				if fis, err := vfsutil.ReadDir(gopherjspkg.FS, path); err == nil {
    77  					return fis, nil
    78  				}
    79  			}
    80  			return ioutil.ReadDir(path)
    81  		},
    82  		OpenFile: func(path string) (io.ReadCloser, error) {
    83  			if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) {
    84  				path = filepath.ToSlash(path[len(gopherjsRoot):])
    85  				if f, err := gopherjspkg.FS.Open(path); err == nil {
    86  					return f, nil
    87  				}
    88  			}
    89  			return os.Open(path)
    90  		},
    91  	}
    92  }
    93  
    94  // statFile returns an os.FileInfo describing the named file.
    95  // For files in "$GOROOT/src/github.com/gopherjs/gopherjs" directory,
    96  // gopherjspkg.FS is consulted first.
    97  func statFile(path string) (os.FileInfo, error) {
    98  	gopherjsRoot := filepath.Join(build.Default.GOROOT, "src", "github.com", "gopherjs", "gopherjs")
    99  	if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) {
   100  		path = filepath.ToSlash(path[len(gopherjsRoot):])
   101  		if fi, err := vfsutil.Stat(gopherjspkg.FS, path); err == nil {
   102  			return fi, nil
   103  		}
   104  	}
   105  	return os.Stat(path)
   106  }
   107  
   108  // Import returns details about the Go package named by the import path. If the
   109  // path is a local import path naming a package that can be imported using
   110  // a standard import path, the returned package will set p.ImportPath to
   111  // that path.
   112  //
   113  // In the directory containing the package, .go and .inc.js files are
   114  // considered part of the package except for:
   115  //
   116  //    - .go files in package documentation
   117  //    - files starting with _ or . (likely editor temporary files)
   118  //    - files with build constraints not satisfied by the context
   119  //
   120  // If an error occurs, Import returns a non-nil error and a nil
   121  // *PackageData.
   122  func Import(path string, mode build.ImportMode, installSuffix string, buildTags []string) (*PackageData, error) {
   123  	wd, err := os.Getwd()
   124  	if err != nil {
   125  		// Getwd may fail if we're in GOARCH=js mode. That's okay, handle
   126  		// it by falling back to empty working directory. It just means
   127  		// Import will not be able to resolve relative import paths.
   128  		wd = ""
   129  	}
   130  	bctx := NewBuildContext(installSuffix, buildTags)
   131  	return importWithSrcDir(*bctx, path, wd, mode, installSuffix, nil)
   132  }
   133  
   134  func importWithSrcDir(bctx build.Context, path string, srcDir string, mode build.ImportMode, installSuffix string, mod *fastmod.Package) (*PackageData, error) {
   135  	// bctx is passed by value, so it can be modified here.
   136  	var isVirtual bool
   137  	if path == "github.com/goplusjs/gopherjs/js" {
   138  		path = "github.com/gopherjs/gopherjs/js"
   139  	} else if path == "github.com/goplusjs/gopherjs/nosync" {
   140  		path = "github.com/gopherjs/gopherjs/nosync"
   141  	}
   142  	switch path {
   143  	case "syscall":
   144  		// syscall needs to use a typical GOARCH like amd64 to pick up definitions for _Socklen, BpfInsn, IFNAMSIZ, Timeval, BpfStat, SYS_FCNTL, Flock_t, etc.
   145  		bctx.GOARCH = runtime.GOARCH
   146  		bctx.InstallSuffix = "js"
   147  		if installSuffix != "" {
   148  			bctx.InstallSuffix += "_" + installSuffix
   149  		}
   150  	case "syscall/js":
   151  		// There are no buildable files in this package, but we need to use files in the virtual directory.
   152  		mode |= build.FindOnly
   153  	case "math/big":
   154  		// Use pure Go version of math/big; we don't want non-Go assembly versions.
   155  		bctx.BuildTags = append(bctx.BuildTags, "math_big_pure_go")
   156  	case "crypto/x509", "os/user":
   157  		// These stdlib packages have cgo and non-cgo versions (via build tags); we want the latter.
   158  		bctx.CgoEnabled = false
   159  	case "github.com/gopherjs/gopherjs/js", "github.com/gopherjs/gopherjs/nosync":
   160  		// These packages are already embedded via gopherjspkg.FS virtual filesystem (which can be
   161  		// safely vendored). Don't try to use vendor directory to resolve them.
   162  		mode |= build.IgnoreVendor
   163  		isVirtual = true
   164  		_, name := filepath.Split(path)
   165  		var gofiles []string
   166  		if name == "js" {
   167  			gofiles = []string{"js.go"}
   168  		} else {
   169  			gofiles = []string{"map.go", "mutex.go", "once.go", "pool.go"}
   170  		}
   171  		pkg := &build.Package{
   172  			Dir:        filepath.Join(bctx.GOROOT, "src", path),
   173  			Name:       name,
   174  			ImportPath: path,
   175  			GoFiles:    gofiles,
   176  		}
   177  		return &PackageData{Package: pkg, IsVirtual: isVirtual}, nil
   178  	}
   179  	var pkg *build.Package
   180  	var err error
   181  	if mod != nil && mod.IsValid() && !mod.IsStd() {
   182  		if _, dir, typ := mod.Lookup(path); typ != fastmod.PkgTypeNil {
   183  			srcDir = dir
   184  			pkg, err = bctx.ImportDir(srcDir, mode)
   185  			if err == nil {
   186  				pkg.ImportPath = path
   187  			}
   188  		}
   189  	}
   190  	if pkg == nil {
   191  		if filepath.IsAbs(path) {
   192  			pkg, err = bctx.ImportDir(path, mode)
   193  		} else {
   194  			pkg, err = bctx.Import(path, srcDir, mode)
   195  		}
   196  	}
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	switch path {
   202  	case "os":
   203  		pkg.GoFiles = excludeExecutable(pkg.GoFiles) // Need to exclude executable implementation files, because some of them contain package scope variables that perform (indirectly) syscalls on init.
   204  		pkg.GoFiles = exclude(pkg.GoFiles, "dirent_js.go")
   205  	case "runtime":
   206  		pkg.GoFiles = []string{"typekind.go", "error.go"}
   207  	case "runtime/internal/sys":
   208  		pkg.GoFiles = []string{fmt.Sprintf("zgoos_%s.go", bctx.GOOS), "zversion.go", "stubs.go", "zgoarch_386.go", "arch_386.go", "arch.go"}
   209  	case "runtime/pprof":
   210  		pkg.GoFiles = nil
   211  	case "internal/poll":
   212  		pkg.GoFiles = exclude(pkg.GoFiles, "fd_poll_runtime.go")
   213  	case "crypto/rand":
   214  		pkg.GoFiles = []string{"rand.go", "util.go"}
   215  		pkg.TestGoFiles = exclude(pkg.TestGoFiles, "rand_linux_test.go") // Don't want linux-specific tests (since linux-specific package files are excluded too).
   216  	case "crypto/x509":
   217  		var files []string
   218  		for _, v := range pkg.GoFiles {
   219  			if strings.HasPrefix(v, "root_") {
   220  				continue
   221  			}
   222  			files = append(files, v)
   223  		}
   224  		pkg.GoFiles = files
   225  	case "syscall":
   226  		pkg.GoFiles = exclude(pkg.GoFiles, "ptrace_ios.go")
   227  	}
   228  
   229  	if len(pkg.CgoFiles) > 0 {
   230  		return nil, &ImportCError{path}
   231  	}
   232  
   233  	if pkg.IsCommand() {
   234  		pkg.PkgObj = filepath.Join(pkg.BinDir, filepath.Base(pkg.ImportPath)+".js")
   235  	}
   236  
   237  	if _, err := os.Stat(pkg.PkgObj); os.IsNotExist(err) && strings.HasPrefix(pkg.PkgObj, build.Default.GOROOT) {
   238  		// fall back to GOPATH
   239  		firstGopathWorkspace := filepath.SplitList(build.Default.GOPATH)[0] // TODO: Need to check inside all GOPATH workspaces.
   240  		gopathPkgObj := filepath.Join(firstGopathWorkspace, pkg.PkgObj[len(build.Default.GOROOT):])
   241  		if _, err := os.Stat(gopathPkgObj); err == nil {
   242  			pkg.PkgObj = gopathPkgObj
   243  		}
   244  	}
   245  
   246  	jsFiles, err := jsFilesFromDir(&bctx, pkg.Dir)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	return &PackageData{Package: pkg, JSFiles: jsFiles, IsVirtual: isVirtual}, nil
   252  }
   253  
   254  // excludeExecutable excludes all executable implementation .go files.
   255  // They have "executable_" prefix.
   256  func excludeExecutable(goFiles []string) []string {
   257  	var s []string
   258  	for _, f := range goFiles {
   259  		if strings.HasPrefix(f, "executable_") {
   260  			continue
   261  		}
   262  		s = append(s, f)
   263  	}
   264  	return s
   265  }
   266  
   267  // exclude returns files, excluding specified files.
   268  func exclude(files []string, exclude ...string) []string {
   269  	var s []string
   270  Outer:
   271  	for _, f := range files {
   272  		for _, e := range exclude {
   273  			if f == e {
   274  				continue Outer
   275  			}
   276  		}
   277  		s = append(s, f)
   278  	}
   279  	return s
   280  }
   281  
   282  // ImportDir is like Import but processes the Go package found in the named
   283  // directory.
   284  func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTags []string) (*PackageData, error) {
   285  	bctx := NewBuildContext(installSuffix, buildTags)
   286  	pkg, err := bctx.ImportDir(dir, mode)
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  
   291  	jsFiles, err := jsFilesFromDir(bctx, pkg.Dir)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	return &PackageData{Package: pkg, JSFiles: jsFiles}, nil
   297  }
   298  
   299  // parseAndAugment parses and returns all .go files of given pkg.
   300  // Standard Go library packages are augmented with files in compiler/natives folder.
   301  // If isTest is true and pkg.ImportPath has no _test suffix, package is built for running internal tests.
   302  // If isTest is true and pkg.ImportPath has _test suffix, package is built for running external tests.
   303  //
   304  // The native packages are augmented by the contents of natives.FS in the following way.
   305  // The file names do not matter except the usual `_test` suffix. The files for
   306  // native overrides get added to the package (even if they have the same name
   307  // as an existing file from the standard library). For all identifiers that exist
   308  // in the original AND the overrides, the original identifier in the AST gets
   309  // replaced by `_`. New identifiers that don't exist in original package get added.
   310  func parseAndAugment(bctx *build.Context, pkg *build.Package, isTest bool, fileSet *token.FileSet) ([]*ast.File, error) {
   311  	var files []*ast.File
   312  	replacedDeclNames := make(map[string]bool)
   313  	funcName := func(d *ast.FuncDecl) string {
   314  		if d.Recv == nil || len(d.Recv.List) == 0 {
   315  			return d.Name.Name
   316  		}
   317  		recv := d.Recv.List[0].Type
   318  		if star, ok := recv.(*ast.StarExpr); ok {
   319  			recv = star.X
   320  		}
   321  		return recv.(*ast.Ident).Name + "." + d.Name.Name
   322  	}
   323  	isXTest := strings.HasSuffix(pkg.ImportPath, "_test")
   324  	importPath := pkg.ImportPath
   325  	if isXTest {
   326  		importPath = importPath[:len(importPath)-5]
   327  	}
   328  
   329  	nativesContext := &build.Context{
   330  		GOROOT:      "/",
   331  		GOOS:        build.Default.GOOS,
   332  		GOARCH:      "js",
   333  		Compiler:    "gc",
   334  		ReleaseTags: goversion.ReleaseTags(),
   335  		JoinPath:    path.Join,
   336  		SplitPathList: func(list string) []string {
   337  			if list == "" {
   338  				return nil
   339  			}
   340  			return strings.Split(list, "/")
   341  		},
   342  		IsAbsPath: path.IsAbs,
   343  		IsDir: func(name string) bool {
   344  			dir, err := natives.FS.Open(name)
   345  			if err != nil {
   346  				return false
   347  			}
   348  			defer dir.Close()
   349  			info, err := dir.Stat()
   350  			if err != nil {
   351  				return false
   352  			}
   353  			return info.IsDir()
   354  		},
   355  		HasSubdir: func(root, name string) (rel string, ok bool) {
   356  			panic("not implemented")
   357  		},
   358  		ReadDir: func(name string) (fi []os.FileInfo, err error) {
   359  			dir, err := natives.FS.Open(name)
   360  			if err != nil {
   361  				return nil, err
   362  			}
   363  			defer dir.Close()
   364  			return dir.Readdir(0)
   365  		},
   366  		OpenFile: func(name string) (r io.ReadCloser, err error) {
   367  			return natives.FS.Open(name)
   368  		},
   369  	}
   370  
   371  	if importPath == "syscall" {
   372  		nativesContext.BuildTags = []string{runtime.GOARCH}
   373  	}
   374  
   375  	if nativesPkg, err := nativesContext.Import(importPath, "", 0); err == nil {
   376  		names := nativesPkg.GoFiles
   377  		if isTest {
   378  			names = append(names, nativesPkg.TestGoFiles...)
   379  		}
   380  		if isXTest {
   381  			names = nativesPkg.XTestGoFiles
   382  		}
   383  		for _, name := range names {
   384  			fullPath := path.Join(nativesPkg.Dir, name)
   385  			r, err := nativesContext.OpenFile(fullPath)
   386  			if err != nil {
   387  				panic(err)
   388  			}
   389  			file, err := parser.ParseFile(fileSet, fullPath, r, parser.ParseComments)
   390  			if err != nil {
   391  				panic(err)
   392  			}
   393  			r.Close()
   394  			for _, decl := range file.Decls {
   395  				switch d := decl.(type) {
   396  				case *ast.FuncDecl:
   397  					replacedDeclNames[funcName(d)] = true
   398  				case *ast.GenDecl:
   399  					switch d.Tok {
   400  					case token.TYPE:
   401  						for _, spec := range d.Specs {
   402  							replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = true
   403  						}
   404  					case token.VAR, token.CONST:
   405  						for _, spec := range d.Specs {
   406  							for _, name := range spec.(*ast.ValueSpec).Names {
   407  								replacedDeclNames[name.Name] = true
   408  							}
   409  						}
   410  					}
   411  				}
   412  			}
   413  			files = append(files, file)
   414  		}
   415  	}
   416  	delete(replacedDeclNames, "init")
   417  
   418  	var errList compiler.ErrorList
   419  	for _, name := range pkg.GoFiles {
   420  		if !filepath.IsAbs(name) { // name might be absolute if specified directly. E.g., `gopherjs build /abs/file.go`.
   421  			name = filepath.Join(pkg.Dir, name)
   422  		}
   423  		r, err := buildutil.OpenFile(bctx, name)
   424  		if err != nil {
   425  			return nil, err
   426  		}
   427  		file, err := parser.ParseFile(fileSet, name, r, parser.ParseComments)
   428  		r.Close()
   429  		if err != nil {
   430  			if list, isList := err.(scanner.ErrorList); isList {
   431  				if len(list) > 10 {
   432  					list = append(list[:10], &scanner.Error{Pos: list[9].Pos, Msg: "too many errors"})
   433  				}
   434  				for _, entry := range list {
   435  					errList = append(errList, entry)
   436  				}
   437  				continue
   438  			}
   439  			errList = append(errList, err)
   440  			continue
   441  		}
   442  
   443  		switch pkg.ImportPath {
   444  		case "crypto/rand", "encoding/gob", "encoding/json", "expvar", "go/token", "log", "math/big", "math/rand", "regexp", "testing", "time":
   445  			for _, spec := range file.Imports {
   446  				path, _ := strconv.Unquote(spec.Path.Value)
   447  				if path == "sync" {
   448  					if spec.Name == nil {
   449  						spec.Name = ast.NewIdent("sync")
   450  					}
   451  					spec.Path.Value = `"github.com/gopherjs/gopherjs/nosync"`
   452  				}
   453  			}
   454  		}
   455  
   456  		for _, decl := range file.Decls {
   457  			switch d := decl.(type) {
   458  			case *ast.FuncDecl:
   459  				if replacedDeclNames[funcName(d)] {
   460  					d.Name = ast.NewIdent("_")
   461  				}
   462  			case *ast.GenDecl:
   463  				switch d.Tok {
   464  				case token.TYPE:
   465  					for _, spec := range d.Specs {
   466  						s := spec.(*ast.TypeSpec)
   467  						if replacedDeclNames[s.Name.Name] {
   468  							s.Name = ast.NewIdent("_")
   469  						}
   470  					}
   471  				case token.VAR, token.CONST:
   472  					for _, spec := range d.Specs {
   473  						s := spec.(*ast.ValueSpec)
   474  						for i, name := range s.Names {
   475  							if replacedDeclNames[name.Name] {
   476  								s.Names[i] = ast.NewIdent("_")
   477  							}
   478  						}
   479  					}
   480  				}
   481  			}
   482  		}
   483  		files = append(files, file)
   484  	}
   485  	if errList != nil {
   486  		return nil, errList
   487  	}
   488  	return files, nil
   489  }
   490  
   491  type Options struct {
   492  	GOROOT         string
   493  	GOPATH         string
   494  	Verbose        bool
   495  	Quiet          bool
   496  	Watch          bool
   497  	CreateMapFile  bool
   498  	MapToLocalDisk bool
   499  	Minify         bool
   500  	Color          bool
   501  	BuildTags      []string
   502  	Rebuild        bool
   503  }
   504  
   505  func (o *Options) PrintError(format string, a ...interface{}) {
   506  	if o.Color {
   507  		format = "\x1B[31m" + format + "\x1B[39m"
   508  	}
   509  	fmt.Fprintf(os.Stderr, format, a...)
   510  }
   511  
   512  func (o *Options) PrintSuccess(format string, a ...interface{}) {
   513  	if o.Color {
   514  		format = "\x1B[32m" + format + "\x1B[39m"
   515  	}
   516  	fmt.Fprintf(os.Stderr, format, a...)
   517  }
   518  
   519  type PackageData struct {
   520  	*build.Package
   521  	JSFiles    []string
   522  	IsTest     bool // IsTest is true if the package is being built for running tests.
   523  	SrcModTime time.Time
   524  	UpToDate   bool
   525  	IsVirtual  bool // If true, the package does not have a corresponding physical directory on disk.
   526  }
   527  
   528  type linkname struct {
   529  	pos    token.Pos
   530  	local  string
   531  	traget string
   532  }
   533  
   534  type Session struct {
   535  	options          *Options
   536  	bctx             *build.Context
   537  	mod              *fastmod.Package
   538  	Archives         map[string]*compiler.Archive
   539  	Packages         map[string]*PackageData
   540  	Types            map[string]*types.Package
   541  	Watcher          *fsnotify.Watcher
   542  	compilePkg       string
   543  	compileLinkNames map[string][]compiler.LinkName
   544  }
   545  
   546  func (s *Session) checkMod(pkg *PackageData) (err error) {
   547  	s.mod.Clear()
   548  	if pkg != nil && !pkg.Goroot {
   549  		err := s.mod.LoadModule(pkg.Dir)
   550  		if err != nil {
   551  			return err
   552  		}
   553  	}
   554  	return nil
   555  }
   556  
   557  func NewSession(options *Options) *Session {
   558  	if options.GOROOT == "" {
   559  		options.GOROOT = build.Default.GOROOT
   560  	}
   561  	if options.GOPATH == "" {
   562  		options.GOPATH = build.Default.GOPATH
   563  	}
   564  	options.Verbose = options.Verbose || options.Watch
   565  
   566  	s := &Session{
   567  		options:          options,
   568  		Archives:         make(map[string]*compiler.Archive),
   569  		Packages:         make(map[string]*PackageData),
   570  		compileLinkNames: make(map[string][]compiler.LinkName),
   571  	}
   572  	s.bctx = NewBuildContext(s.InstallSuffix(), s.options.BuildTags)
   573  	s.mod = fastmod.NewPackage(s.bctx)
   574  	s.Types = make(map[string]*types.Package)
   575  	if options.Watch {
   576  		if out, err := exec.Command("ulimit", "-n").Output(); err == nil {
   577  			if n, err := strconv.Atoi(strings.TrimSpace(string(out))); err == nil && n < 1024 {
   578  				fmt.Printf("Warning: The maximum number of open file descriptors is very low (%d). Change it with 'ulimit -n 8192'.\n", n)
   579  			}
   580  		}
   581  
   582  		var err error
   583  		s.Watcher, err = fsnotify.NewWatcher()
   584  		if err != nil {
   585  			panic(err)
   586  		}
   587  	}
   588  	return s
   589  }
   590  
   591  // BuildContext returns the session's build context.
   592  func (s *Session) BuildContext() *build.Context { return s.bctx }
   593  
   594  func (s *Session) InstallSuffix() string {
   595  	if s.options.Minify {
   596  		return "min"
   597  	}
   598  	return ""
   599  }
   600  
   601  func (s *Session) BuildDir(packagePath string, importPath string, pkgObj string) error {
   602  	if s.Watcher != nil {
   603  		s.Watcher.Add(packagePath)
   604  	}
   605  	buildPkg, err := s.bctx.ImportDir(packagePath, 0)
   606  	if err != nil {
   607  		return err
   608  	}
   609  	pkg := &PackageData{Package: buildPkg}
   610  	jsFiles, err := jsFilesFromDir(s.bctx, pkg.Dir)
   611  	if err != nil {
   612  		return err
   613  	}
   614  	pkg.JSFiles = jsFiles
   615  	archive, err := s.buildPackage(pkg)
   616  	if err != nil {
   617  		return err
   618  	}
   619  	if pkgObj == "" {
   620  		pkgObj = filepath.Base(packagePath) + ".js"
   621  	}
   622  	if pkg.IsCommand() && !pkg.UpToDate {
   623  		if err := s.WriteCommandPackage(archive, pkgObj); err != nil {
   624  			return err
   625  		}
   626  	}
   627  	return nil
   628  }
   629  
   630  func (s *Session) BuildFiles(filenames []string, pkgObj string, packagePath string) error {
   631  	pkg := &PackageData{
   632  		Package: &build.Package{
   633  			Name:       "main",
   634  			ImportPath: "main",
   635  			Dir:        packagePath,
   636  		},
   637  	}
   638  	err := s.checkMod(pkg)
   639  	if err != nil {
   640  		return err
   641  	}
   642  
   643  	for _, file := range filenames {
   644  		if strings.HasSuffix(file, ".inc.js") {
   645  			pkg.JSFiles = append(pkg.JSFiles, file)
   646  			continue
   647  		}
   648  		pkg.GoFiles = append(pkg.GoFiles, file)
   649  	}
   650  
   651  	archive, err := s.buildPackage(pkg)
   652  	if err != nil {
   653  		return err
   654  	}
   655  	if s.Types["main"].Name() != "main" {
   656  		return fmt.Errorf("cannot build/run non-main package")
   657  	}
   658  	return s.WriteCommandPackage(archive, pkgObj)
   659  }
   660  
   661  func (s *Session) BuildImportPath(path string) (*compiler.Archive, error) {
   662  	_, archive, err := s.BuildImportPathWithPackage(path, nil)
   663  	return archive, err
   664  }
   665  
   666  func (s *Session) BuildImportPathWithPackage(path string, pkgData *PackageData) (*PackageData, *compiler.Archive, error) {
   667  	var srcDir string
   668  	var mod *fastmod.Package
   669  	if pkgData != nil {
   670  		srcDir = pkgData.Dir
   671  		if !pkgData.Goroot {
   672  			mod = s.mod
   673  		}
   674  	}
   675  	pkg, err := importWithSrcDir(*s.bctx, path, srcDir, 0, s.InstallSuffix(), mod)
   676  	if s.Watcher != nil && pkg != nil { // add watch even on error
   677  		s.Watcher.Add(pkg.Dir)
   678  	}
   679  	if err != nil {
   680  		return nil, nil, err
   681  	}
   682  
   683  	archive, err := s.buildPackage(pkg)
   684  	if err != nil {
   685  		return nil, nil, err
   686  	}
   687  
   688  	return pkg, archive, nil
   689  }
   690  
   691  func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
   692  	err := s.checkMod(pkg)
   693  	if err != nil {
   694  		return nil, err
   695  	}
   696  	return s.buildPackage(pkg)
   697  }
   698  
   699  func (s *Session) checkLinkNames(importPath string, fileSet *token.FileSet, files []*ast.File) (linknames []compiler.LinkName, err error) {
   700  	if importPath == "internal/bytealg" || importPath == "runtime" {
   701  		return
   702  	}
   703  	for _, f := range files {
   704  		for _, group := range f.Comments {
   705  			for _, c := range group.List {
   706  				// method //go:linkname _Println fmt.Println
   707  				// method //go:linkname _WriteString bytes.(*Buffer).WriteString
   708  				if strings.HasPrefix(c.Text, "//go:linkname ") {
   709  					f := strings.Fields(c.Text)
   710  					var target string
   711  					if len(f) == 3 {
   712  						target = f[2]
   713  					}
   714  					var targetName, targetRecv, targetMethod, targetImportPath string
   715  					pos := strings.Index(target, ".")
   716  					if pos > 0 {
   717  						// pkg.func
   718  						// pkg.recv.method
   719  						// pkg.(*recv).method
   720  						targetImportPath = target[:pos]
   721  						fnname := target[pos+1:]
   722  						pos = strings.Index(fnname, ".")
   723  						if pos > 0 {
   724  							if fnname[0] == '(' {
   725  								if fnname[1] != '*' || fnname[pos-1] != ')' {
   726  									return nil, fmt.Errorf("relocation target %v not defined", target)
   727  								}
   728  								targetRecv = fnname[1 : pos-1]
   729  								targetMethod = fnname[pos+1:]
   730  								targetName = "__linkname__" + targetRecv[1:] + "_" + targetMethod
   731  								target = "(*" + targetImportPath + "." + targetRecv[1:] + ")." + targetMethod
   732  							} else {
   733  								targetRecv = fnname[:pos]
   734  								targetMethod = fnname[pos+1:]
   735  								targetName = "__linkname__" + targetRecv + "_" + targetMethod
   736  								target = "(" + targetImportPath + "." + targetRecv + ")." + targetMethod
   737  							}
   738  						} else {
   739  							targetName = "__linkname__" + fnname
   740  						}
   741  					} else {
   742  						targetName = target
   743  					}
   744  					linknames = append(linknames, compiler.LinkName{Local: f[1], Target: target, TargetName: targetName, TargetRecv: targetRecv, TargetMethod: targetMethod, TargetImportPath: targetImportPath})
   745  				}
   746  			}
   747  		}
   748  	}
   749  	if len(linknames) == 0 {
   750  		return
   751  	}
   752  	var linkImports []string
   753  	for _, link := range linknames {
   754  		if link.TargetImportPath != "" {
   755  			var found bool
   756  			for _, v := range linkImports {
   757  				if v == link.TargetImportPath {
   758  					found = true
   759  				}
   760  			}
   761  			if !found {
   762  				linkImports = append(linkImports, link.TargetImportPath)
   763  			}
   764  		}
   765  	}
   766  	if len(linkImports) == 0 {
   767  		return
   768  	}
   769  	sort.Strings(linkImports)
   770  
   771  	for _, im := range linkImports {
   772  		if s.compilePkg == im {
   773  			s.compileLinkNames[im] = linknames
   774  			continue
   775  		}
   776  		ar, ok := s.Archives[im]
   777  		if !ok {
   778  			ar, err = s.BuildImportPath(im)
   779  			if err != nil {
   780  				return
   781  			}
   782  		}
   783  		compiler.UpdateLinkNames(ar, linknames)
   784  		if pkg, ok := s.Packages[im]; ok {
   785  			s.writeArchive(ar, pkg)
   786  		}
   787  	}
   788  	return
   789  }
   790  
   791  func (s *Session) buildPackage(pkg *PackageData) (*compiler.Archive, error) {
   792  	if archive, ok := s.Archives[pkg.ImportPath]; ok {
   793  		return archive, nil
   794  	}
   795  
   796  	if pkg.PkgObj != "" {
   797  		var fileInfo os.FileInfo
   798  		gopherjsBinary, err := os.Executable()
   799  		if err == nil {
   800  			fileInfo, err = os.Stat(gopherjsBinary)
   801  			if err == nil {
   802  				pkg.SrcModTime = fileInfo.ModTime()
   803  			}
   804  		}
   805  		if err != nil {
   806  			os.Stderr.WriteString("Could not get GopherJS binary's modification timestamp. Please report issue.\n")
   807  			pkg.SrcModTime = time.Now()
   808  		}
   809  
   810  		for _, importedPkgPath := range pkg.Imports {
   811  			// Ignore all imports that aren't mentioned in import specs of pkg.
   812  			// For example, this ignores imports such as runtime/internal/sys and runtime/internal/atomic.
   813  			ignored := true
   814  			for _, pos := range pkg.ImportPos[importedPkgPath] {
   815  				importFile := filepath.Base(pos.Filename)
   816  				for _, file := range pkg.GoFiles {
   817  					if importFile == file {
   818  						ignored = false
   819  						break
   820  					}
   821  				}
   822  				if !ignored {
   823  					break
   824  				}
   825  			}
   826  
   827  			if importedPkgPath == "unsafe" || ignored {
   828  				continue
   829  			}
   830  			importedPkg, _, err := s.BuildImportPathWithPackage(importedPkgPath, pkg)
   831  			if err != nil {
   832  				return nil, err
   833  			}
   834  			impModTime := importedPkg.SrcModTime
   835  			if impModTime.After(pkg.SrcModTime) {
   836  				pkg.SrcModTime = impModTime
   837  			}
   838  		}
   839  
   840  		for _, name := range append(pkg.GoFiles, pkg.JSFiles...) {
   841  			fileInfo, err := statFile(filepath.Join(pkg.Dir, name))
   842  			if err != nil {
   843  				return nil, err
   844  			}
   845  			if fileInfo.ModTime().After(pkg.SrcModTime) {
   846  				pkg.SrcModTime = fileInfo.ModTime()
   847  			}
   848  		}
   849  
   850  		pkgObjFileInfo, err := os.Stat(pkg.PkgObj)
   851  		if !s.options.Rebuild && err == nil && !pkg.SrcModTime.After(pkgObjFileInfo.ModTime()) {
   852  			// package object is up to date, load from disk if library
   853  			pkg.UpToDate = true
   854  			if pkg.IsCommand() {
   855  				return nil, nil
   856  			}
   857  
   858  			objFile, err := os.Open(pkg.PkgObj)
   859  			if err != nil {
   860  				return nil, err
   861  			}
   862  			defer objFile.Close()
   863  
   864  			archive, err := compiler.ReadArchive(pkg.PkgObj, pkg.ImportPath, objFile, s.Types)
   865  			if err != nil {
   866  				return nil, err
   867  			}
   868  
   869  			if archive.Version == goversion.Version {
   870  				s.Archives[pkg.ImportPath] = archive
   871  				return archive, nil
   872  			}
   873  		}
   874  	}
   875  
   876  	fileSet := token.NewFileSet()
   877  	files, err := parseAndAugment(s.bctx, pkg.Package, pkg.IsTest, fileSet)
   878  	if err != nil {
   879  		return nil, err
   880  	}
   881  
   882  	localImportPathCache := make(map[string]*compiler.Archive)
   883  	importContext := &compiler.ImportContext{
   884  		Packages: s.Types,
   885  		Import: func(path string) (*compiler.Archive, error) {
   886  			if archive, ok := localImportPathCache[path]; ok {
   887  				return archive, nil
   888  			}
   889  			_, archive, err := s.BuildImportPathWithPackage(path, pkg)
   890  			if err != nil {
   891  				return nil, err
   892  			}
   893  			localImportPathCache[path] = archive
   894  			return archive, nil
   895  		},
   896  	}
   897  	embedfile, err := s.checkEmbed(pkg, fileSet, files)
   898  	if err != nil {
   899  		return nil, fmt.Errorf("check embed error: %v", err)
   900  	}
   901  	if embedfile != nil {
   902  		files = append(files, embedfile)
   903  	}
   904  
   905  	linknames, err := s.checkLinkNames(pkg.ImportPath, fileSet, files)
   906  	if err != nil {
   907  		return nil, err
   908  	}
   909  	s.compilePkg = pkg.ImportPath
   910  	s.compileLinkNames[pkg.ImportPath] = nil
   911  	archive, err := compiler.Compile(pkg.ImportPath, files, fileSet, importContext, linknames, s.options.Minify)
   912  	if err != nil {
   913  		return nil, err
   914  	}
   915  	if links := s.compileLinkNames[pkg.ImportPath]; links != nil {
   916  		compiler.UpdateLinkNames(archive, links)
   917  	}
   918  
   919  	for _, jsFile := range pkg.JSFiles {
   920  		code, err := ioutil.ReadFile(filepath.Join(pkg.Dir, jsFile))
   921  		if err != nil {
   922  			return nil, err
   923  		}
   924  		archive.IncJSCode = append(archive.IncJSCode, []byte("\t(function() {\n")...)
   925  		archive.IncJSCode = append(archive.IncJSCode, code...)
   926  		archive.IncJSCode = append(archive.IncJSCode, []byte("\n\t}).call($global);\n")...)
   927  	}
   928  
   929  	if s.options.Verbose {
   930  		fmt.Println(pkg.Dir)
   931  	}
   932  	s.Archives[pkg.ImportPath] = archive
   933  	s.Packages[pkg.ImportPath] = pkg
   934  
   935  	return s.writeArchive(archive, pkg)
   936  }
   937  
   938  func (s *Session) writeArchive(archive *compiler.Archive, pkg *PackageData) (*compiler.Archive, error) {
   939  	if pkg.PkgObj == "" || pkg.IsCommand() {
   940  		return archive, nil
   941  	}
   942  	if err := s.writeLibraryPackage(archive, pkg.PkgObj); err != nil {
   943  		if strings.HasPrefix(pkg.PkgObj, s.options.GOROOT) {
   944  			// fall back to first GOPATH workspace
   945  			firstGopathWorkspace := filepath.SplitList(s.options.GOPATH)[0]
   946  			if err := s.writeLibraryPackage(archive, filepath.Join(firstGopathWorkspace, pkg.PkgObj[len(s.options.GOROOT):])); err != nil {
   947  				return nil, err
   948  			}
   949  			return archive, nil
   950  		}
   951  		return nil, err
   952  	}
   953  	return archive, nil
   954  }
   955  
   956  func (s *Session) writeLibraryPackage(archive *compiler.Archive, pkgObj string) error {
   957  	if err := os.MkdirAll(filepath.Dir(pkgObj), 0777); err != nil {
   958  		return err
   959  	}
   960  
   961  	objFile, err := os.Create(pkgObj)
   962  	if err != nil {
   963  		return err
   964  	}
   965  	defer objFile.Close()
   966  
   967  	archive.Version = goversion.Version
   968  	return compiler.WriteArchive(archive, objFile)
   969  }
   970  
   971  func (s *Session) WriteCommandPackage(archive *compiler.Archive, pkgObj string) error {
   972  	if err := os.MkdirAll(filepath.Dir(pkgObj), 0777); err != nil {
   973  		return err
   974  	}
   975  	codeFile, err := os.Create(pkgObj)
   976  	if err != nil {
   977  		return err
   978  	}
   979  	defer codeFile.Close()
   980  
   981  	sourceMapFilter := &compiler.SourceMapFilter{Writer: codeFile}
   982  	if s.options.CreateMapFile {
   983  		m := &sourcemap.Map{File: filepath.Base(pkgObj)}
   984  		mapFile, err := os.Create(pkgObj + ".map")
   985  		if err != nil {
   986  			return err
   987  		}
   988  
   989  		defer func() {
   990  			m.WriteTo(mapFile)
   991  			mapFile.Close()
   992  			fmt.Fprintf(codeFile, "//# sourceMappingURL=%s.map\n", filepath.Base(pkgObj))
   993  		}()
   994  
   995  		sourceMapFilter.MappingCallback = NewMappingCallback(m, s.options.GOROOT, s.options.GOPATH, s.options.MapToLocalDisk)
   996  	}
   997  
   998  	deps, err := compiler.ImportDependencies(archive, func(path string) (*compiler.Archive, error) {
   999  		if archive, ok := s.Archives[path]; ok {
  1000  			return archive, nil
  1001  		}
  1002  		_, archive, err := s.BuildImportPathWithPackage(path, nil)
  1003  		return archive, err
  1004  	})
  1005  	if err != nil {
  1006  		return err
  1007  	}
  1008  	return compiler.WriteProgramCode(deps, sourceMapFilter)
  1009  }
  1010  
  1011  func NewMappingCallback(m *sourcemap.Map, goroot, gopath string, localMap bool) func(generatedLine, generatedColumn int, originalPos token.Position) {
  1012  	return func(generatedLine, generatedColumn int, originalPos token.Position) {
  1013  		if !originalPos.IsValid() {
  1014  			m.AddMapping(&sourcemap.Mapping{GeneratedLine: generatedLine, GeneratedColumn: generatedColumn})
  1015  			return
  1016  		}
  1017  
  1018  		file := originalPos.Filename
  1019  
  1020  		switch hasGopathPrefix, prefixLen := hasGopathPrefix(file, gopath); {
  1021  		case localMap:
  1022  			// no-op:  keep file as-is
  1023  		case hasGopathPrefix:
  1024  			file = filepath.ToSlash(file[prefixLen+4:])
  1025  		case strings.HasPrefix(file, goroot):
  1026  			file = filepath.ToSlash(file[len(goroot)+4:])
  1027  		default:
  1028  			file = filepath.Base(file)
  1029  		}
  1030  
  1031  		m.AddMapping(&sourcemap.Mapping{GeneratedLine: generatedLine, GeneratedColumn: generatedColumn, OriginalFile: file, OriginalLine: originalPos.Line, OriginalColumn: originalPos.Column})
  1032  	}
  1033  }
  1034  
  1035  func jsFilesFromDir(bctx *build.Context, dir string) ([]string, error) {
  1036  	files, err := buildutil.ReadDir(bctx, dir)
  1037  	if err != nil {
  1038  		return nil, err
  1039  	}
  1040  	var jsFiles []string
  1041  	for _, file := range files {
  1042  		if strings.HasSuffix(file.Name(), ".inc.js") && file.Name()[0] != '_' && file.Name()[0] != '.' {
  1043  			jsFiles = append(jsFiles, file.Name())
  1044  		}
  1045  	}
  1046  	return jsFiles, nil
  1047  }
  1048  
  1049  // hasGopathPrefix returns true and the length of the matched GOPATH workspace,
  1050  // iff file has a prefix that matches one of the GOPATH workspaces.
  1051  func hasGopathPrefix(file, gopath string) (hasGopathPrefix bool, prefixLen int) {
  1052  	gopathWorkspaces := filepath.SplitList(gopath)
  1053  	for _, gopathWorkspace := range gopathWorkspaces {
  1054  		gopathWorkspace = filepath.Clean(gopathWorkspace)
  1055  		if strings.HasPrefix(file, gopathWorkspace) {
  1056  			return true, len(gopathWorkspace)
  1057  		}
  1058  	}
  1059  	return false, 0
  1060  }
  1061  
  1062  func (s *Session) WaitForChange() {
  1063  	s.options.PrintSuccess("watching for changes...\n")
  1064  	for {
  1065  		select {
  1066  		case ev := <-s.Watcher.Events:
  1067  			if ev.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Remove|fsnotify.Rename) == 0 || filepath.Base(ev.Name)[0] == '.' {
  1068  				continue
  1069  			}
  1070  			if !strings.HasSuffix(ev.Name, ".go") && !strings.HasSuffix(ev.Name, ".inc.js") {
  1071  				continue
  1072  			}
  1073  			s.options.PrintSuccess("change detected: %s\n", ev.Name)
  1074  		case err := <-s.Watcher.Errors:
  1075  			s.options.PrintError("watcher error: %s\n", err.Error())
  1076  		}
  1077  		break
  1078  	}
  1079  
  1080  	go func() {
  1081  		for range s.Watcher.Events {
  1082  			// consume, else Close() may deadlock
  1083  		}
  1084  	}()
  1085  	s.Watcher.Close()
  1086  }