tool.go 26 KB

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