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

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"go/ast"
     8  	"go/build"
     9  	"go/doc"
    10  	"go/parser"
    11  	"go/scanner"
    12  	"go/token"
    13  	"go/types"
    14  	"io"
    15  	"io/ioutil"
    16  	"net"
    17  	"net/http"
    18  	"os"
    19  	"os/exec"
    20  	"path"
    21  	"path/filepath"
    22  	"runtime"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"syscall"
    27  	"text/template"
    28  	"time"
    29  	"unicode"
    30  	"unicode/utf8"
    31  
    32  	gbuild "github.com/goplusjs/gopherjs/build"
    33  	"github.com/goplusjs/gopherjs/compiler"
    34  	"github.com/goplusjs/gopherjs/internal/sysutil"
    35  	"github.com/kisielk/gotool"
    36  	"github.com/neelance/sourcemap"
    37  	"github.com/spf13/cobra"
    38  	"github.com/spf13/pflag"
    39  	"golang.org/x/crypto/ssh/terminal"
    40  	"golang.org/x/tools/go/buildutil"
    41  )
    42  
    43  var currentDirectory string
    44  
    45  func init() {
    46  	var err error
    47  	currentDirectory, err = os.Getwd()
    48  	if err != nil {
    49  		fmt.Fprintln(os.Stderr, err)
    50  		os.Exit(1)
    51  	}
    52  	currentDirectory, err = filepath.EvalSymlinks(currentDirectory)
    53  	if err != nil {
    54  		fmt.Fprintln(os.Stderr, err)
    55  		os.Exit(1)
    56  	}
    57  	gopaths := filepath.SplitList(build.Default.GOPATH)
    58  	if len(gopaths) == 0 {
    59  		fmt.Fprintf(os.Stderr, "$GOPATH not set. For more details see: go help gopath\n")
    60  		os.Exit(1)
    61  	}
    62  	if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" {
    63  		build.Default.GOOS = "darwin"
    64  	}
    65  }
    66  
    67  func main() {
    68  	var (
    69  		options = &gbuild.Options{CreateMapFile: true}
    70  		pkgObj  string
    71  		tags    string
    72  	)
    73  
    74  	flagVerbose := pflag.NewFlagSet("", 0)
    75  	flagVerbose.BoolVarP(&options.Verbose, "verbose", "v", false, "print the names of packages as they are compiled")
    76  	flagQuiet := pflag.NewFlagSet("", 0)
    77  	flagQuiet.BoolVarP(&options.Quiet, "quiet", "q", false, "suppress non-fatal warnings")
    78  
    79  	compilerFlags := pflag.NewFlagSet("", 0)
    80  	compilerFlags.BoolVarP(&options.Minify, "minify", "m", false, "minify generated code")
    81  	compilerFlags.BoolVar(&options.Color, "color", terminal.IsTerminal(int(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb", "colored output")
    82  	compilerFlags.StringVar(&tags, "tags", "", "a list of build tags to consider satisfied during the build")
    83  	compilerFlags.BoolVar(&options.MapToLocalDisk, "localmap", false, "use local paths for sourcemap")
    84  	compilerFlags.BoolVarP(&options.Rebuild, "force", "a", false, "force rebuilding of packages that are already up-to-date")
    85  
    86  	flagWatch := pflag.NewFlagSet("", 0)
    87  	flagWatch.BoolVarP(&options.Watch, "watch", "w", false, "watch for changes to the source files")
    88  
    89  	cmdBuild := &cobra.Command{
    90  		Use:   "build [packages]",
    91  		Short: "compile packages and dependencies",
    92  	}
    93  	cmdBuild.Flags().StringVarP(&pkgObj, "output", "o", "", "output file")
    94  	cmdBuild.Flags().AddFlagSet(flagVerbose)
    95  	cmdBuild.Flags().AddFlagSet(flagQuiet)
    96  	cmdBuild.Flags().AddFlagSet(compilerFlags)
    97  	cmdBuild.Flags().AddFlagSet(flagWatch)
    98  	cmdBuild.Run = func(cmd *cobra.Command, args []string) {
    99  		options.BuildTags = strings.Fields(tags)
   100  		for {
   101  			s := gbuild.NewSession(options)
   102  
   103  			err := func() error {
   104  				// Handle "gopherjs build [files]" ad-hoc package mode.
   105  				if len(args) > 0 && (strings.HasSuffix(args[0], ".go") || strings.HasSuffix(args[0], ".inc.js")) {
   106  					for _, arg := range args {
   107  						if !strings.HasSuffix(arg, ".go") && !strings.HasSuffix(arg, ".inc.js") {
   108  							return fmt.Errorf("named files must be .go or .inc.js files")
   109  						}
   110  					}
   111  					if pkgObj == "" {
   112  						basename := filepath.Base(args[0])
   113  						pkgObj = basename[:len(basename)-3] + ".js"
   114  					}
   115  					names := make([]string, len(args))
   116  					for i, name := range args {
   117  						name = filepath.ToSlash(name)
   118  						names[i] = name
   119  						if s.Watcher != nil {
   120  							s.Watcher.Add(name)
   121  						}
   122  					}
   123  					err := s.BuildFiles(args, pkgObj, currentDirectory)
   124  					return err
   125  				}
   126  
   127  				// Expand import path patterns.
   128  				patternContext := gbuild.NewBuildContext("", options.BuildTags)
   129  				pkgs := (&gotool.Context{BuildContext: *patternContext}).ImportPaths(args)
   130  				for _, pkgPath := range pkgs {
   131  					if s.Watcher != nil {
   132  						pkg, err := gbuild.NewBuildContext(s.InstallSuffix(), options.BuildTags).Import(pkgPath, "", build.FindOnly)
   133  						if err != nil {
   134  							return err
   135  						}
   136  						s.Watcher.Add(pkg.Dir)
   137  					}
   138  					pkg, err := gbuild.Import(pkgPath, 0, s.InstallSuffix(), options.BuildTags)
   139  					if err != nil {
   140  						return err
   141  					}
   142  					archive, err := s.BuildPackage(pkg)
   143  					if err != nil {
   144  						return err
   145  					}
   146  					if len(pkgs) == 1 { // Only consider writing output if single package specified.
   147  						if pkgObj == "" {
   148  							pkgObj = filepath.Base(pkg.Dir) + ".js"
   149  						}
   150  						if pkg.IsCommand() && !pkg.UpToDate {
   151  							if err := s.WriteCommandPackage(archive, pkgObj); err != nil {
   152  								return err
   153  							}
   154  						}
   155  					}
   156  				}
   157  				return nil
   158  			}()
   159  			exitCode := handleError(err, options, nil)
   160  
   161  			if s.Watcher == nil {
   162  				os.Exit(exitCode)
   163  			}
   164  			s.WaitForChange()
   165  		}
   166  	}
   167  
   168  	cmdInstall := &cobra.Command{
   169  		Use:   "install [packages]",
   170  		Short: "compile and install packages and dependencies",
   171  	}
   172  	cmdInstall.Flags().AddFlagSet(flagVerbose)
   173  	cmdInstall.Flags().AddFlagSet(flagQuiet)
   174  	cmdInstall.Flags().AddFlagSet(compilerFlags)
   175  	cmdInstall.Flags().AddFlagSet(flagWatch)
   176  	cmdInstall.Run = func(cmd *cobra.Command, args []string) {
   177  		options.BuildTags = strings.Fields(tags)
   178  		for {
   179  			s := gbuild.NewSession(options)
   180  
   181  			err := func() error {
   182  				// Expand import path patterns.
   183  				patternContext := gbuild.NewBuildContext("", options.BuildTags)
   184  				pkgs := (&gotool.Context{BuildContext: *patternContext}).ImportPaths(args)
   185  
   186  				if cmd.Name() == "get" {
   187  					goGet := exec.Command("go", append([]string{"get", "-d", "-tags=js"}, pkgs...)...)
   188  					goGet.Stdout = os.Stdout
   189  					goGet.Stderr = os.Stderr
   190  					if err := goGet.Run(); err != nil {
   191  						return err
   192  					}
   193  				}
   194  				for _, pkgPath := range pkgs {
   195  					pkg, err := gbuild.Import(pkgPath, 0, s.InstallSuffix(), options.BuildTags)
   196  					if s.Watcher != nil && pkg != nil { // add watch even on error
   197  						s.Watcher.Add(pkg.Dir)
   198  					}
   199  					if err != nil {
   200  						return err
   201  					}
   202  					archive, err := s.BuildPackage(pkg)
   203  					if err != nil {
   204  						return err
   205  					}
   206  
   207  					if pkg.IsCommand() && !pkg.UpToDate {
   208  						if err := s.WriteCommandPackage(archive, pkg.PkgObj); err != nil {
   209  							return err
   210  						}
   211  					}
   212  				}
   213  				return nil
   214  			}()
   215  			exitCode := handleError(err, options, nil)
   216  
   217  			if s.Watcher == nil {
   218  				os.Exit(exitCode)
   219  			}
   220  			s.WaitForChange()
   221  		}
   222  	}
   223  
   224  	cmdDoc := &cobra.Command{
   225  		Use:   "doc [arguments]",
   226  		Short: "display documentation for the requested, package, method or symbol",
   227  	}
   228  	cmdDoc.Run = func(cmd *cobra.Command, args []string) {
   229  		goDoc := exec.Command("go", append([]string{"doc"}, args...)...)
   230  		goDoc.Stdout = os.Stdout
   231  		goDoc.Stderr = os.Stderr
   232  		goDoc.Env = append(os.Environ(), "GOARCH=js")
   233  		err := goDoc.Run()
   234  		exitCode := handleError(err, options, nil)
   235  		os.Exit(exitCode)
   236  	}
   237  
   238  	cmdGet := &cobra.Command{
   239  		Use:   "get [packages]",
   240  		Short: "download and install packages and dependencies",
   241  	}
   242  	cmdGet.Flags().AddFlagSet(flagVerbose)
   243  	cmdGet.Flags().AddFlagSet(flagQuiet)
   244  	cmdGet.Flags().AddFlagSet(compilerFlags)
   245  	cmdGet.Run = cmdInstall.Run
   246  
   247  	cmdRun := &cobra.Command{
   248  		Use:   "run [gofiles...] [arguments...]",
   249  		Short: "compile and run Go program",
   250  	}
   251  	cmdRun.Flags().AddFlagSet(flagVerbose)
   252  	cmdRun.Flags().AddFlagSet(flagQuiet)
   253  	cmdRun.Flags().AddFlagSet(compilerFlags)
   254  	cmdRun.Run = func(cmd *cobra.Command, args []string) {
   255  		err := func() error {
   256  			lastSourceArg := 0
   257  			for {
   258  				if lastSourceArg == len(args) || !(strings.HasSuffix(args[lastSourceArg], ".go") || strings.HasSuffix(args[lastSourceArg], ".inc.js")) {
   259  					break
   260  				}
   261  				lastSourceArg++
   262  			}
   263  			if lastSourceArg == 0 {
   264  				return fmt.Errorf("gopherjs run: no go files listed")
   265  			}
   266  
   267  			tempfile, err := ioutil.TempFile(currentDirectory, filepath.Base(args[0])+".")
   268  			if err != nil && strings.HasPrefix(currentDirectory, runtime.GOROOT()) {
   269  				tempfile, err = ioutil.TempFile("", filepath.Base(args[0])+".")
   270  			}
   271  			if err != nil {
   272  				return err
   273  			}
   274  			defer func() {
   275  				tempfile.Close()
   276  				os.Remove(tempfile.Name())
   277  				os.Remove(tempfile.Name() + ".map")
   278  			}()
   279  			s := gbuild.NewSession(options)
   280  			if err := s.BuildFiles(args[:lastSourceArg], tempfile.Name(), currentDirectory); err != nil {
   281  				return err
   282  			}
   283  			if err := runNode(tempfile.Name(), args[lastSourceArg:], "", options.Quiet); err != nil {
   284  				return err
   285  			}
   286  			return nil
   287  		}()
   288  		exitCode := handleError(err, options, nil)
   289  
   290  		os.Exit(exitCode)
   291  	}
   292  
   293  	cmdTest := &cobra.Command{
   294  		Use:   "test [packages]",
   295  		Short: "test packages",
   296  	}
   297  	bench := cmdTest.Flags().String("bench", "", "Run benchmarks matching the regular expression. By default, no benchmarks run. To run all benchmarks, use '--bench=.'.")
   298  	benchtime := cmdTest.Flags().String("benchtime", "", "Run enough iterations of each benchmark to take t, specified as a time.Duration (for example, -benchtime 1h30s). The default is 1 second (1s).")
   299  	count := cmdTest.Flags().String("count", "", "Run each test and benchmark n times (default 1). Examples are always run once.")
   300  	run := cmdTest.Flags().String("run", "", "Run only those tests and examples matching the regular expression.")
   301  	short := cmdTest.Flags().Bool("short", false, "Tell long-running tests to shorten their run time.")
   302  	verbose := cmdTest.Flags().BoolP("verbose", "v", false, "Log all tests as they are run. Also print all text from Log and Logf calls even if the test succeeds.")
   303  	compileOnly := cmdTest.Flags().BoolP("compileonly", "c", false, "Compile the test binary to pkg.test.js but do not run it (where pkg is the last element of the package's import path). The file name can be changed with the -o flag.")
   304  	outputFilename := cmdTest.Flags().StringP("output", "o", "", "Compile the test binary to the named file. The test still runs (unless -c is specified).")
   305  	cmdTest.Flags().AddFlagSet(compilerFlags)
   306  	cmdTest.Run = func(cmd *cobra.Command, args []string) {
   307  		options.BuildTags = strings.Fields(tags)
   308  		err := func() error {
   309  			// Expand import path patterns.
   310  			patternContext := gbuild.NewBuildContext("", options.BuildTags)
   311  			args = (&gotool.Context{BuildContext: *patternContext}).ImportPaths(args)
   312  
   313  			if *compileOnly && len(args) > 1 {
   314  				return errors.New("cannot use -c flag with multiple packages")
   315  			}
   316  			if *outputFilename != "" && len(args) > 1 {
   317  				return errors.New("cannot use -o flag with multiple packages")
   318  			}
   319  
   320  			pkgs := make([]*gbuild.PackageData, len(args))
   321  			for i, pkgPath := range args {
   322  				var err error
   323  				pkgs[i], err = gbuild.Import(pkgPath, 0, "", options.BuildTags)
   324  				if err != nil {
   325  					return err
   326  				}
   327  			}
   328  
   329  			var exitErr error
   330  			for _, pkg := range pkgs {
   331  				if len(pkg.TestGoFiles) == 0 && len(pkg.XTestGoFiles) == 0 {
   332  					fmt.Printf("?   \t%s\t[no test files]\n", pkg.ImportPath)
   333  					continue
   334  				}
   335  				s := gbuild.NewSession(options)
   336  
   337  				tests := &testFuncs{BuildContext: s.BuildContext(), Package: pkg.Package}
   338  				collectTests := func(testPkg *gbuild.PackageData, testPkgName string, needVar *bool) error {
   339  					if testPkgName == "_test" {
   340  						for _, file := range pkg.TestGoFiles {
   341  							if err := tests.load(pkg.Package.Dir, file, testPkgName, &tests.ImportTest, &tests.NeedTest); err != nil {
   342  								return err
   343  							}
   344  						}
   345  					} else {
   346  						for _, file := range pkg.XTestGoFiles {
   347  							if err := tests.load(pkg.Package.Dir, file, "_xtest", &tests.ImportXtest, &tests.NeedXtest); err != nil {
   348  								return err
   349  							}
   350  						}
   351  					}
   352  					_, err := s.BuildPackage(testPkg)
   353  					return err
   354  				}
   355  
   356  				if err := collectTests(makeTestPkg(pkg, false), "_test", &tests.NeedTest); err != nil {
   357  					return err
   358  				}
   359  
   360  				if err := collectTests(makeTestPkg(pkg, true), "_xtest", &tests.NeedXtest); err != nil {
   361  					return err
   362  				}
   363  
   364  				buf := new(bytes.Buffer)
   365  				if err := testmainTmpl.Execute(buf, tests); err != nil {
   366  					return err
   367  				}
   368  
   369  				fset := token.NewFileSet()
   370  				mainFile, err := parser.ParseFile(fset, "_testmain.go", buf, 0)
   371  				if err != nil {
   372  					return err
   373  				}
   374  
   375  				importContext := &compiler.ImportContext{
   376  					Packages: s.Types,
   377  					Import: func(path string) (*compiler.Archive, error) {
   378  						if path == pkg.ImportPath || path == pkg.ImportPath+"_test" {
   379  							return s.Archives[path], nil
   380  						}
   381  						return s.BuildImportPath(path)
   382  					},
   383  				}
   384  				mainPkgArchive, err := compiler.Compile("main", []*ast.File{mainFile}, fset, importContext, nil, options.Minify)
   385  				if err != nil {
   386  					return err
   387  				}
   388  
   389  				if *compileOnly && *outputFilename == "" {
   390  					*outputFilename = pkg.Package.Name + "_test.js"
   391  				}
   392  
   393  				var outfile *os.File
   394  				if *outputFilename != "" {
   395  					outfile, err = os.Create(*outputFilename)
   396  					if err != nil {
   397  						return err
   398  					}
   399  				} else {
   400  					outfile, err = ioutil.TempFile(currentDirectory, "test.")
   401  					if err != nil {
   402  						return err
   403  					}
   404  				}
   405  				defer func() {
   406  					outfile.Close()
   407  					if *outputFilename == "" {
   408  						os.Remove(outfile.Name())
   409  						os.Remove(outfile.Name() + ".map")
   410  					}
   411  				}()
   412  
   413  				if err := s.WriteCommandPackage(mainPkgArchive, outfile.Name()); err != nil {
   414  					return err
   415  				}
   416  
   417  				if *compileOnly {
   418  					continue
   419  				}
   420  
   421  				var args []string
   422  				if *bench != "" {
   423  					args = append(args, "-test.bench", *bench)
   424  				}
   425  				if *benchtime != "" {
   426  					args = append(args, "-test.benchtime", *benchtime)
   427  				}
   428  				if *count != "" {
   429  					args = append(args, "-test.count", *count)
   430  				}
   431  				if *run != "" {
   432  					args = append(args, "-test.run", *run)
   433  				}
   434  				if *short {
   435  					args = append(args, "-test.short")
   436  				}
   437  				if *verbose {
   438  					args = append(args, "-test.v")
   439  				}
   440  				status := "ok  "
   441  				start := time.Now()
   442  				if err := runNode(outfile.Name(), args, runTestDir(pkg), options.Quiet); err != nil {
   443  					if _, ok := err.(*exec.ExitError); !ok {
   444  						return err
   445  					}
   446  					exitErr = err
   447  					status = "FAIL"
   448  				}
   449  				fmt.Printf("%s\t%s\t%.3fs\n", status, pkg.ImportPath, time.Since(start).Seconds())
   450  			}
   451  			return exitErr
   452  		}()
   453  		exitCode := handleError(err, options, nil)
   454  
   455  		os.Exit(exitCode)
   456  	}
   457  
   458  	cmdServe := &cobra.Command{
   459  		Use:   "serve [root]",
   460  		Short: "compile on-the-fly and serve",
   461  	}
   462  	cmdServe.Flags().AddFlagSet(flagVerbose)
   463  	cmdServe.Flags().AddFlagSet(flagQuiet)
   464  	cmdServe.Flags().AddFlagSet(compilerFlags)
   465  	var addr string
   466  	cmdServe.Flags().StringVarP(&addr, "http", "", ":8080", "HTTP bind address to serve")
   467  	cmdServe.Run = func(cmd *cobra.Command, args []string) {
   468  		options.BuildTags = strings.Fields(tags)
   469  		dirs := append(filepath.SplitList(build.Default.GOPATH), build.Default.GOROOT)
   470  		var root string
   471  
   472  		if len(args) > 1 {
   473  			cmdServe.HelpFunc()(cmd, args)
   474  			os.Exit(1)
   475  		}
   476  
   477  		if len(args) == 1 {
   478  			root = args[0]
   479  			if strings.HasPrefix(root, ".") {
   480  				root = filepath.Join(currentDirectory, root)
   481  			}
   482  		}
   483  
   484  		sourceFiles := http.FileServer(serveCommandFileSystem{
   485  			serveRoot:  root,
   486  			options:    options,
   487  			dirs:       dirs,
   488  			sourceMaps: make(map[string][]byte),
   489  		})
   490  
   491  		ln, err := net.Listen("tcp", addr)
   492  		if err != nil {
   493  			fmt.Fprintln(os.Stderr, err)
   494  			os.Exit(1)
   495  		}
   496  		if tcpAddr := ln.Addr().(*net.TCPAddr); tcpAddr.IP.Equal(net.IPv4zero) || tcpAddr.IP.Equal(net.IPv6zero) { // Any available addresses.
   497  			fmt.Printf("serving at http://localhost:%d and on port %d of any available addresses\n", tcpAddr.Port, tcpAddr.Port)
   498  		} else { // Specific address.
   499  			fmt.Printf("serving at http://%s\n", tcpAddr)
   500  		}
   501  		fmt.Fprintln(os.Stderr, http.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}, sourceFiles))
   502  	}
   503  
   504  	cmdVersion := &cobra.Command{
   505  		Use:   "version",
   506  		Short: "print GopherJS compiler version",
   507  	}
   508  	cmdVersion.Run = func(cmd *cobra.Command, args []string) {
   509  		if len(args) > 0 {
   510  			cmdServe.HelpFunc()(cmd, args)
   511  			os.Exit(1)
   512  		}
   513  		golangVersion := fmt.Sprintf("%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH)
   514  
   515  		fmt.Printf("GopherJS %s (build on %s)\n", compiler.Version, golangVersion)
   516  	}
   517  
   518  	rootCmd := &cobra.Command{
   519  		Use:  "gopherjs",
   520  		Long: "GopherJS is a tool for compiling Go source code to JavaScript.",
   521  	}
   522  	rootCmd.AddCommand(cmdBuild, cmdGet, cmdInstall, cmdRun, cmdTest, cmdServe, cmdVersion, cmdDoc)
   523  	err := rootCmd.Execute()
   524  	if err != nil {
   525  		os.Exit(2)
   526  	}
   527  }
   528  
   529  // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
   530  // connections. It's used by ListenAndServe and ListenAndServeTLS so
   531  // dead TCP connections (e.g. closing laptop mid-download) eventually
   532  // go away.
   533  type tcpKeepAliveListener struct {
   534  	*net.TCPListener
   535  }
   536  
   537  func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
   538  	tc, err := ln.AcceptTCP()
   539  	if err != nil {
   540  		return
   541  	}
   542  	tc.SetKeepAlive(true)
   543  	tc.SetKeepAlivePeriod(3 * time.Minute)
   544  	return tc, nil
   545  }
   546  
   547  type serveCommandFileSystem struct {
   548  	serveRoot  string
   549  	options    *gbuild.Options
   550  	dirs       []string
   551  	sourceMaps map[string][]byte
   552  }
   553  
   554  func (fs serveCommandFileSystem) Open(requestName string) (http.File, error) {
   555  	name := path.Join(fs.serveRoot, requestName[1:]) // requestName[0] == '/'
   556  	dir, file := path.Split(name)
   557  	base := path.Base(dir) // base is parent folder name, which becomes the output file name.
   558  
   559  	isPkg := file == base+".js"
   560  	isMap := file == base+".js.map"
   561  	isIndex := file == "index.html"
   562  
   563  	if isPkg || isMap || isIndex {
   564  		// If we're going to be serving our special files, make sure there's a Go command in this folder.
   565  		s := gbuild.NewSession(fs.options)
   566  		pkg, err := gbuild.Import(path.Dir(name), 0, s.InstallSuffix(), fs.options.BuildTags)
   567  		if err != nil || pkg.Name != "main" {
   568  			isPkg = false
   569  			isMap = false
   570  			isIndex = false
   571  		}
   572  		switch {
   573  		case isPkg:
   574  			buf := new(bytes.Buffer)
   575  			browserErrors := new(bytes.Buffer)
   576  			err := func() error {
   577  				archive, err := s.BuildPackage(pkg)
   578  				if err != nil {
   579  					return err
   580  				}
   581  
   582  				sourceMapFilter := &compiler.SourceMapFilter{Writer: buf}
   583  				m := &sourcemap.Map{File: base + ".js"}
   584  				sourceMapFilter.MappingCallback = gbuild.NewMappingCallback(m, fs.options.GOROOT, fs.options.GOPATH, fs.options.MapToLocalDisk)
   585  
   586  				deps, err := compiler.ImportDependencies(archive, func(path string) (*compiler.Archive, error) {
   587  					_, archive, err := s.BuildImportPathWithPackage(path, pkg)
   588  					return archive, err
   589  				})
   590  				if err != nil {
   591  					return err
   592  				}
   593  				if err := compiler.WriteProgramCode(deps, sourceMapFilter); err != nil {
   594  					return err
   595  				}
   596  
   597  				mapBuf := new(bytes.Buffer)
   598  				m.WriteTo(mapBuf)
   599  				buf.WriteString("//# sourceMappingURL=" + base + ".js.map\n")
   600  				fs.sourceMaps[name+".map"] = mapBuf.Bytes()
   601  
   602  				return nil
   603  			}()
   604  			handleError(err, fs.options, browserErrors)
   605  			if err != nil {
   606  				buf = browserErrors
   607  			}
   608  			return newFakeFile(base+".js", buf.Bytes()), nil
   609  
   610  		case isMap:
   611  			if content, ok := fs.sourceMaps[name]; ok {
   612  				return newFakeFile(base+".js.map", content), nil
   613  			}
   614  		}
   615  	}
   616  
   617  	if fs.serveRoot != "" {
   618  		dir := http.Dir(fs.serveRoot)
   619  		if f, err := dir.Open(name); err == nil {
   620  			return f, nil
   621  		}
   622  		if f, err := dir.Open(requestName); err == nil {
   623  			return f, nil
   624  		}
   625  	}
   626  
   627  	for _, d := range fs.dirs {
   628  		dir := http.Dir(filepath.Join(d, "src"))
   629  
   630  		f, err := dir.Open(name)
   631  		if err == nil {
   632  			return f, nil
   633  		}
   634  
   635  		// source maps are served outside of serveRoot
   636  		f, err = dir.Open(requestName)
   637  		if err == nil {
   638  			return f, nil
   639  		}
   640  	}
   641  
   642  	if isIndex {
   643  		// If there was no index.html file in any dirs, supply our own.
   644  		return newFakeFile("index.html", []byte(`<html><head><meta charset="utf-8"><script src="`+base+`.js"></script></head><body></body></html>`)), nil
   645  	}
   646  
   647  	return nil, os.ErrNotExist
   648  }
   649  
   650  type fakeFile struct {
   651  	name string
   652  	size int
   653  	io.ReadSeeker
   654  }
   655  
   656  func newFakeFile(name string, content []byte) *fakeFile {
   657  	return &fakeFile{name: name, size: len(content), ReadSeeker: bytes.NewReader(content)}
   658  }
   659  
   660  func (f *fakeFile) Close() error {
   661  	return nil
   662  }
   663  
   664  func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) {
   665  	return nil, os.ErrInvalid
   666  }
   667  
   668  func (f *fakeFile) Stat() (os.FileInfo, error) {
   669  	return f, nil
   670  }
   671  
   672  func (f *fakeFile) Name() string {
   673  	return f.name
   674  }
   675  
   676  func (f *fakeFile) Size() int64 {
   677  	return int64(f.size)
   678  }
   679  
   680  func (f *fakeFile) Mode() os.FileMode {
   681  	return 0
   682  }
   683  
   684  func (f *fakeFile) ModTime() time.Time {
   685  	return time.Time{}
   686  }
   687  
   688  func (f *fakeFile) IsDir() bool {
   689  	return false
   690  }
   691  
   692  func (f *fakeFile) Sys() interface{} {
   693  	return nil
   694  }
   695  
   696  // handleError handles err and returns an appropriate exit code.
   697  // If browserErrors is non-nil, errors are written for presentation in browser.
   698  func handleError(err error, options *gbuild.Options, browserErrors *bytes.Buffer) int {
   699  	switch err := err.(type) {
   700  	case nil:
   701  		return 0
   702  	case compiler.ErrorList:
   703  		for _, entry := range err {
   704  			printError(entry, options, browserErrors)
   705  		}
   706  		return 1
   707  	case *exec.ExitError:
   708  		return err.Sys().(syscall.WaitStatus).ExitStatus()
   709  	default:
   710  		printError(err, options, browserErrors)
   711  		return 1
   712  	}
   713  }
   714  
   715  // printError prints err to Stderr with options. If browserErrors is non-nil, errors are also written for presentation in browser.
   716  func printError(err error, options *gbuild.Options, browserErrors *bytes.Buffer) {
   717  	e := sprintError(err)
   718  	options.PrintError("%s\n", e)
   719  	if browserErrors != nil {
   720  		fmt.Fprintln(browserErrors, `console.error("`+template.JSEscapeString(e)+`");`)
   721  	}
   722  }
   723  
   724  // sprintError returns an annotated error string without trailing newline.
   725  func sprintError(err error) string {
   726  	makeRel := func(name string) string {
   727  		if relname, err := filepath.Rel(currentDirectory, name); err == nil {
   728  			return relname
   729  		}
   730  		return name
   731  	}
   732  
   733  	switch e := err.(type) {
   734  	case *scanner.Error:
   735  		return fmt.Sprintf("%s:%d:%d: %s", makeRel(e.Pos.Filename), e.Pos.Line, e.Pos.Column, e.Msg)
   736  	case types.Error:
   737  		pos := e.Fset.Position(e.Pos)
   738  		return fmt.Sprintf("%s:%d:%d: %s", makeRel(pos.Filename), pos.Line, pos.Column, e.Msg)
   739  	default:
   740  		return fmt.Sprintf("%s", e)
   741  	}
   742  }
   743  
   744  // runNode runs script with args using Node.js in directory dir.
   745  // If dir is empty string, current directory is used.
   746  func runNode(script string, args []string, dir string, quiet bool) error {
   747  	var allArgs []string
   748  	if b, _ := strconv.ParseBool(os.Getenv("SOURCE_MAP_SUPPORT")); os.Getenv("SOURCE_MAP_SUPPORT") == "" || b {
   749  		allArgs = []string{"--require", "source-map-support/register"}
   750  		if err := exec.Command("node", "--require", "source-map-support/register", "--eval", "").Run(); err != nil {
   751  			if !quiet {
   752  				fmt.Fprintln(os.Stderr, "gopherjs: Source maps disabled. Install source-map-support module for nice stack traces. See https://github.com/gopherjs/gopherjs#gopherjs-run-gopherjs-test.")
   753  			}
   754  			allArgs = []string{}
   755  		}
   756  	}
   757  
   758  	if runtime.GOOS != "windows" {
   759  		// We've seen issues with stack space limits causing
   760  		// recursion-heavy standard library tests to fail (e.g., see
   761  		// https://github.com/gopherjs/gopherjs/pull/669#issuecomment-319319483).
   762  		//
   763  		// There are two separate limits in non-Windows environments:
   764  		//
   765  		// -	OS process limit
   766  		// -	Node.js (V8) limit
   767  		//
   768  		// GopherJS fetches the current OS process limit, and sets the
   769  		// Node.js limit to the same value. So both limits are kept in sync
   770  		// and can be controlled by setting OS process limit. E.g.:
   771  		//
   772  		// 	ulimit -s 10000 && gopherjs test
   773  		//
   774  		cur, err := sysutil.RlimitStack()
   775  		if err != nil {
   776  			return fmt.Errorf("failed to get stack size limit: %v", err)
   777  		}
   778  		allArgs = append(allArgs, fmt.Sprintf("--stack_size=%v", cur/1000)) // Convert from bytes to KB.
   779  	}
   780  
   781  	allArgs = append(allArgs, script)
   782  	allArgs = append(allArgs, args...)
   783  
   784  	node := exec.Command("node", allArgs...)
   785  	node.Dir = dir
   786  	node.Stdin = os.Stdin
   787  	node.Stdout = os.Stdout
   788  	node.Stderr = os.Stderr
   789  	err := node.Run()
   790  	if _, ok := err.(*exec.ExitError); err != nil && !ok {
   791  		err = fmt.Errorf("could not run Node.js: %s", err.Error())
   792  	}
   793  	return err
   794  }
   795  
   796  // runTestDir returns the directory for Node.js to use when running tests for package p.
   797  // Empty string means current directory.
   798  func runTestDir(p *gbuild.PackageData) string {
   799  	if p.IsVirtual {
   800  		// The package is virtual and doesn't have a physical directory. Use current directory.
   801  		return ""
   802  	}
   803  	// Run tests in the package directory.
   804  	return p.Dir
   805  }
   806  
   807  type testFuncs struct {
   808  	BuildContext *build.Context
   809  	Tests        []testFunc
   810  	Benchmarks   []testFunc
   811  	Examples     []testFunc
   812  	TestMain     *testFunc
   813  	Package      *build.Package
   814  	ImportTest   bool
   815  	NeedTest     bool
   816  	ImportXtest  bool
   817  	NeedXtest    bool
   818  }
   819  
   820  type testFunc struct {
   821  	Package   string // imported package name (_test or _xtest)
   822  	Name      string // function name
   823  	Output    string // output, for examples
   824  	Unordered bool   // output is allowed to be unordered.
   825  }
   826  
   827  var testFileSet = token.NewFileSet()
   828  
   829  func (t *testFuncs) load(dir, file, pkg string, doImport, seen *bool) error {
   830  	f, err := buildutil.ParseFile(testFileSet, t.BuildContext, nil, dir, file, parser.ParseComments)
   831  	if err != nil {
   832  		return err
   833  	}
   834  	for _, d := range f.Decls {
   835  		n, ok := d.(*ast.FuncDecl)
   836  		if !ok {
   837  			continue
   838  		}
   839  		if n.Recv != nil {
   840  			continue
   841  		}
   842  		name := n.Name.String()
   843  		switch {
   844  		case isTestMain(n):
   845  			if t.TestMain != nil {
   846  				return errors.New("multiple definitions of TestMain")
   847  			}
   848  			t.TestMain = &testFunc{pkg, name, "", false}
   849  			*doImport, *seen = true, true
   850  		case isTest(name, "Test"):
   851  			t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
   852  			*doImport, *seen = true, true
   853  		case isTest(name, "Benchmark"):
   854  			t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
   855  			*doImport, *seen = true, true
   856  		}
   857  	}
   858  	if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
   859  		ex := doc.Examples(f)
   860  		sort.Sort(byOrder(ex))
   861  		for _, e := range ex {
   862  			*doImport = true // import test file whether executed or not
   863  			if e.Output == "" && !e.EmptyOutput {
   864  				// Don't run examples with no output.
   865  				continue
   866  			}
   867  			t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
   868  			*seen = true
   869  		}
   870  	}
   871  
   872  	return nil
   873  }
   874  
   875  type byOrder []*doc.Example
   876  
   877  func (x byOrder) Len() int           { return len(x) }
   878  func (x byOrder) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }
   879  func (x byOrder) Less(i, j int) bool { return x[i].Order < x[j].Order }
   880  
   881  // isTestMain tells whether fn is a TestMain(m *testing.M) function.
   882  func isTestMain(fn *ast.FuncDecl) bool {
   883  	if fn.Name.String() != "TestMain" ||
   884  		fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
   885  		fn.Type.Params == nil ||
   886  		len(fn.Type.Params.List) != 1 ||
   887  		len(fn.Type.Params.List[0].Names) > 1 {
   888  		return false
   889  	}
   890  	ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
   891  	if !ok {
   892  		return false
   893  	}
   894  	// We can't easily check that the type is *testing.M
   895  	// because we don't know how testing has been imported,
   896  	// but at least check that it's *M or *something.M.
   897  	if name, ok := ptr.X.(*ast.Ident); ok && name.Name == "M" {
   898  		return true
   899  	}
   900  	if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == "M" {
   901  		return true
   902  	}
   903  	return false
   904  }
   905  
   906  // isTest tells whether name looks like a test (or benchmark, according to prefix).
   907  // It is a Test (say) if there is a character after Test that is not a lower-case letter.
   908  // We don't want TesticularCancer.
   909  func isTest(name, prefix string) bool {
   910  	if !strings.HasPrefix(name, prefix) {
   911  		return false
   912  	}
   913  	if len(name) == len(prefix) { // "Test" is ok
   914  		return true
   915  	}
   916  	rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
   917  	return !unicode.IsLower(rune)
   918  }
   919  
   920  var testmainTmpl = template.Must(template.New("main").Parse(`
   921  package main
   922  
   923  import (
   924  {{if not .TestMain}}
   925  	"os"
   926  {{end}}
   927  	"testing"
   928  	"testing/internal/testdeps"
   929  
   930  {{if .ImportTest}}
   931  	{{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
   932  {{end}}
   933  {{if .ImportXtest}}
   934  	{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
   935  {{end}}
   936  )
   937  
   938  var tests = []testing.InternalTest{
   939  {{range .Tests}}
   940  	{"{{.Name}}", {{.Package}}.{{.Name}}},
   941  {{end}}
   942  }
   943  
   944  var benchmarks = []testing.InternalBenchmark{
   945  {{range .Benchmarks}}
   946  	{"{{.Name}}", {{.Package}}.{{.Name}}},
   947  {{end}}
   948  }
   949  
   950  var examples = []testing.InternalExample{
   951  {{range .Examples}}
   952  	{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
   953  {{end}}
   954  }
   955  
   956  func main() {
   957  	m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
   958  {{with .TestMain}}
   959  	{{.Package}}.{{.Name}}(m)
   960  {{else}}
   961  	os.Exit(m.Run())
   962  {{end}}
   963  }
   964  
   965  `))