1 Star 0 Fork 0

独孤川/gopherjs_gopherjs

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
tool.go 29.77 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041
package main
import (
"bytes"
"errors"
"fmt"
"go/ast"
"go/build"
"go/doc"
"go/parser"
"go/scanner"
"go/token"
"go/types"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"syscall"
"text/template"
"time"
"unicode"
"unicode/utf8"
gbuild "github.com/gopherjs/gopherjs/build"
"github.com/gopherjs/gopherjs/compiler"
"github.com/gopherjs/gopherjs/internal/sysutil"
"github.com/neelance/sourcemap"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/sync/errgroup"
"golang.org/x/tools/go/buildutil"
)
var currentDirectory string
func init() {
var err error
currentDirectory, err = os.Getwd()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
currentDirectory, err = filepath.EvalSymlinks(currentDirectory)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
gopaths := filepath.SplitList(build.Default.GOPATH)
if len(gopaths) == 0 {
fmt.Fprintf(os.Stderr, "$GOPATH not set. For more details see: go help gopath\n")
os.Exit(1)
}
}
func main() {
var (
options = &gbuild.Options{CreateMapFile: true}
pkgObj string
tags string
)
flagVerbose := pflag.NewFlagSet("", 0)
flagVerbose.BoolVarP(&options.Verbose, "verbose", "v", false, "print the names of packages as they are compiled")
flagQuiet := pflag.NewFlagSet("", 0)
flagQuiet.BoolVarP(&options.Quiet, "quiet", "q", false, "suppress non-fatal warnings")
compilerFlags := pflag.NewFlagSet("", 0)
compilerFlags.BoolVarP(&options.Minify, "minify", "m", false, "minify generated code")
compilerFlags.BoolVar(&options.Color, "color", terminal.IsTerminal(int(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb", "colored output")
compilerFlags.StringVar(&tags, "tags", "", "a list of build tags to consider satisfied during the build")
compilerFlags.BoolVar(&options.MapToLocalDisk, "localmap", false, "use local paths for sourcemap")
flagWatch := pflag.NewFlagSet("", 0)
flagWatch.BoolVarP(&options.Watch, "watch", "w", false, "watch for changes to the source files")
cmdBuild := &cobra.Command{
Use: "build [packages]",
Short: "compile packages and dependencies",
}
cmdBuild.Flags().StringVarP(&pkgObj, "output", "o", "", "output file")
cmdBuild.Flags().AddFlagSet(flagVerbose)
cmdBuild.Flags().AddFlagSet(flagQuiet)
cmdBuild.Flags().AddFlagSet(compilerFlags)
cmdBuild.Flags().AddFlagSet(flagWatch)
cmdBuild.Run = func(cmd *cobra.Command, args []string) {
options.BuildTags = strings.Fields(tags)
for {
s, err := gbuild.NewSession(options)
if err != nil {
options.PrintError("%s\n", err)
os.Exit(1)
}
err = func() error {
// Handle "gopherjs build [files]" ad-hoc package mode.
if len(args) > 0 && (strings.HasSuffix(args[0], ".go") || strings.HasSuffix(args[0], ".inc.js")) {
for _, arg := range args {
if !strings.HasSuffix(arg, ".go") && !strings.HasSuffix(arg, ".inc.js") {
return fmt.Errorf("named files must be .go or .inc.js files")
}
}
if pkgObj == "" {
basename := filepath.Base(args[0])
pkgObj = basename[:len(basename)-3] + ".js"
}
names := make([]string, len(args))
for i, name := range args {
name = filepath.ToSlash(name)
names[i] = name
if s.Watcher != nil {
s.Watcher.Add(name)
}
}
err := s.BuildFiles(args, pkgObj, currentDirectory)
return err
}
xctx := gbuild.NewBuildContext(s.InstallSuffix(), options.BuildTags)
// Expand import path patterns.
pkgs, err := xctx.Match(args)
if err != nil {
return fmt.Errorf("failed to expand patterns %v: %w", args, err)
}
for _, pkgPath := range pkgs {
if s.Watcher != nil {
pkg, err := xctx.Import(pkgPath, "", build.FindOnly)
if err != nil {
return err
}
s.Watcher.Add(pkg.Dir)
}
pkg, err := xctx.Import(pkgPath, ".", 0)
if err != nil {
return err
}
archive, err := s.BuildPackage(pkg)
if err != nil {
return err
}
if len(pkgs) == 1 { // Only consider writing output if single package specified.
if pkgObj == "" {
pkgObj = filepath.Base(pkg.Dir) + ".js"
}
if pkg.IsCommand() && !pkg.UpToDate {
if err := s.WriteCommandPackage(archive, pkgObj); err != nil {
return err
}
}
}
}
return nil
}()
exitCode := handleError(err, options, nil)
if s.Watcher == nil {
os.Exit(exitCode)
}
s.WaitForChange()
}
}
cmdInstall := &cobra.Command{
Use: "install [packages]",
Short: "compile and install packages and dependencies",
}
cmdInstall.Flags().AddFlagSet(flagVerbose)
cmdInstall.Flags().AddFlagSet(flagQuiet)
cmdInstall.Flags().AddFlagSet(compilerFlags)
cmdInstall.Flags().AddFlagSet(flagWatch)
cmdInstall.Run = func(cmd *cobra.Command, args []string) {
options.BuildTags = strings.Fields(tags)
for {
s, err := gbuild.NewSession(options)
if err != nil {
options.PrintError("%s\n", err)
os.Exit(1)
}
err = func() error {
// Expand import path patterns.
xctx := gbuild.NewBuildContext(s.InstallSuffix(), options.BuildTags)
pkgs, err := xctx.Match(args)
if err != nil {
return fmt.Errorf("failed to expand patterns %v: %w", args, err)
}
if cmd.Name() == "get" {
goGet := exec.Command("go", append([]string{"get", "-d", "-tags=js"}, pkgs...)...)
goGet.Stdout = os.Stdout
goGet.Stderr = os.Stderr
if err := goGet.Run(); err != nil {
return err
}
}
for _, pkgPath := range pkgs {
pkg, err := xctx.Import(pkgPath, ".", 0)
if s.Watcher != nil && pkg != nil { // add watch even on error
s.Watcher.Add(pkg.Dir)
}
if err != nil {
return err
}
archive, err := s.BuildPackage(pkg)
if err != nil {
return err
}
if pkg.IsCommand() && !pkg.UpToDate {
if err := s.WriteCommandPackage(archive, pkg.PkgObj); err != nil {
return err
}
}
}
return nil
}()
exitCode := handleError(err, options, nil)
if s.Watcher == nil {
os.Exit(exitCode)
}
s.WaitForChange()
}
}
cmdDoc := &cobra.Command{
Use: "doc [arguments]",
Short: "display documentation for the requested, package, method or symbol",
}
cmdDoc.Run = func(cmd *cobra.Command, args []string) {
goDoc := exec.Command("go", append([]string{"doc"}, args...)...)
goDoc.Stdout = os.Stdout
goDoc.Stderr = os.Stderr
goDoc.Env = append(os.Environ(), "GOARCH=js")
err := goDoc.Run()
exitCode := handleError(err, options, nil)
os.Exit(exitCode)
}
cmdGet := &cobra.Command{
Use: "get [packages]",
Short: "download and install packages and dependencies",
}
cmdGet.Flags().AddFlagSet(flagVerbose)
cmdGet.Flags().AddFlagSet(flagQuiet)
cmdGet.Flags().AddFlagSet(compilerFlags)
cmdGet.Run = cmdInstall.Run
cmdRun := &cobra.Command{
Use: "run [gofiles...] [arguments...]",
Short: "compile and run Go program",
}
cmdRun.Flags().AddFlagSet(flagVerbose)
cmdRun.Flags().AddFlagSet(flagQuiet)
cmdRun.Flags().AddFlagSet(compilerFlags)
cmdRun.Run = func(cmd *cobra.Command, args []string) {
err := func() error {
lastSourceArg := 0
for {
if lastSourceArg == len(args) || !(strings.HasSuffix(args[lastSourceArg], ".go") || strings.HasSuffix(args[lastSourceArg], ".inc.js")) {
break
}
lastSourceArg++
}
if lastSourceArg == 0 {
return fmt.Errorf("gopherjs run: no go files listed")
}
tempfile, err := ioutil.TempFile(currentDirectory, filepath.Base(args[0])+".")
if err != nil && strings.HasPrefix(currentDirectory, runtime.GOROOT()) {
tempfile, err = ioutil.TempFile("", filepath.Base(args[0])+".")
}
if err != nil {
return err
}
defer func() {
tempfile.Close()
os.Remove(tempfile.Name())
os.Remove(tempfile.Name() + ".map")
}()
s, err := gbuild.NewSession(options)
if err != nil {
return err
}
if err := s.BuildFiles(args[:lastSourceArg], tempfile.Name(), currentDirectory); err != nil {
return err
}
if err := runNode(tempfile.Name(), args[lastSourceArg:], "", options.Quiet, nil); err != nil {
return err
}
return nil
}()
exitCode := handleError(err, options, nil)
os.Exit(exitCode)
}
cmdTest := &cobra.Command{
Use: "test [packages]",
Short: "test packages",
}
bench := cmdTest.Flags().String("bench", "", "Run benchmarks matching the regular expression. By default, no benchmarks run. To run all benchmarks, use '--bench=.'.")
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).")
count := cmdTest.Flags().String("count", "", "Run each test and benchmark n times (default 1). Examples are always run once.")
run := cmdTest.Flags().String("run", "", "Run only those tests and examples matching the regular expression.")
short := cmdTest.Flags().Bool("short", false, "Tell long-running tests to shorten their run time.")
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.")
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.")
outputFilename := cmdTest.Flags().StringP("output", "o", "", "Compile the test binary to the named file. The test still runs (unless -c is specified).")
parallelTests := cmdTest.Flags().IntP("parallel", "p", runtime.NumCPU(), "Allow running tests in parallel for up to -p packages. Tests within the same package are still executed sequentially.")
cmdTest.Flags().AddFlagSet(compilerFlags)
cmdTest.Run = func(cmd *cobra.Command, args []string) {
options.BuildTags = strings.Fields(tags)
err := func() error {
// Expand import path patterns.
patternContext := gbuild.NewBuildContext("", options.BuildTags)
matches, err := patternContext.Match(args)
if err != nil {
return fmt.Errorf("failed to expand patterns %v: %w", args, err)
}
if *compileOnly && len(matches) > 1 {
return errors.New("cannot use -c flag with multiple packages")
}
if *outputFilename != "" && len(matches) > 1 {
return errors.New("cannot use -o flag with multiple packages")
}
if *parallelTests < 1 {
return errors.New("--parallel cannot be less than 1")
}
parallelSlots := make(chan (bool), *parallelTests) // Semaphore for parallel test executions.
executions := errgroup.Group{}
pkgs := make([]*gbuild.PackageData, len(matches))
for i, pkgPath := range matches {
var err error
pkgs[i], err = gbuild.Import(pkgPath, 0, "", options.BuildTags)
if err != nil {
return err
}
}
var (
exitErr error
exitErrMu = &sync.Mutex{}
)
for _, pkg := range pkgs {
pkg := pkg // Capture for the goroutine.
if len(pkg.TestGoFiles) == 0 && len(pkg.XTestGoFiles) == 0 {
fmt.Printf("? \t%s\t[no test files]\n", pkg.ImportPath)
continue
}
s, err := gbuild.NewSession(options)
if err != nil {
return err
}
tests := &testFuncs{BuildContext: pkg.InternalBuildContext(), Package: pkg.Package}
collectTests := func(testPkg *gbuild.PackageData, testPkgName string, needVar *bool) error {
if testPkgName == "_test" {
for _, file := range pkg.TestGoFiles {
if err := tests.load(pkg.Package.Dir, file, testPkgName, &tests.ImportTest, &tests.NeedTest); err != nil {
return err
}
}
} else {
for _, file := range pkg.XTestGoFiles {
if err := tests.load(pkg.Package.Dir, file, "_xtest", &tests.ImportXtest, &tests.NeedXtest); err != nil {
return err
}
}
}
_, err := s.BuildPackage(testPkg)
return err
}
if err := collectTests(pkg.TestPackage(), "_test", &tests.NeedTest); err != nil {
return err
}
if err := collectTests(pkg.XTestPackage(), "_xtest", &tests.NeedXtest); err != nil {
return err
}
buf := new(bytes.Buffer)
if err := testmainTmpl.Execute(buf, tests); err != nil {
return err
}
fset := token.NewFileSet()
mainFile, err := parser.ParseFile(fset, "_testmain.go", buf, 0)
if err != nil {
return err
}
importContext := &compiler.ImportContext{
Packages: s.Types,
Import: func(path string) (*compiler.Archive, error) {
if path == pkg.ImportPath || path == pkg.ImportPath+"_test" {
return s.Archives[path], nil
}
return s.BuildImportPath(path)
},
}
mainPkgArchive, err := compiler.Compile("main", []*ast.File{mainFile}, fset, importContext, options.Minify)
if err != nil {
return err
}
if *compileOnly && *outputFilename == "" {
*outputFilename = pkg.Package.Name + "_test.js"
}
var outfile *os.File
if *outputFilename != "" {
outfile, err = os.Create(*outputFilename)
if err != nil {
return err
}
} else {
outfile, err = os.CreateTemp(currentDirectory, pkg.Package.Name+"_test.*.js")
if err != nil {
return err
}
outfile.Close() // Release file handle early, we only need the name.
}
cleanupTemp := func() {
if *outputFilename == "" {
os.Remove(outfile.Name())
os.Remove(outfile.Name() + ".map")
}
}
defer cleanupTemp() // Safety net in case cleanup after execution doesn't happen.
if err := s.WriteCommandPackage(mainPkgArchive, outfile.Name()); err != nil {
return err
}
if *compileOnly {
continue
}
var args []string
if *bench != "" {
args = append(args, "-test.bench", *bench)
}
if *benchtime != "" {
args = append(args, "-test.benchtime", *benchtime)
}
if *count != "" {
args = append(args, "-test.count", *count)
}
if *run != "" {
args = append(args, "-test.run", *run)
}
if *short {
args = append(args, "-test.short")
}
if *verbose {
args = append(args, "-test.v")
}
executions.Go(func() error {
parallelSlots <- true // Acquire slot
defer func() { <-parallelSlots }() // Release slot
status := "ok "
start := time.Now()
var testOut io.ReadWriter
if cap(parallelSlots) > 1 {
// If running in parallel, capture test output in a temporary buffer to avoid mixing
// output from different tests and print it later.
testOut = &bytes.Buffer{}
}
err := runNode(outfile.Name(), args, runTestDir(pkg), options.Quiet, testOut)
cleanupTemp() // Eagerly cleanup temporary compiled files after execution.
if testOut != nil {
io.Copy(os.Stdout, testOut)
}
if err != nil {
if _, ok := err.(*exec.ExitError); !ok {
return err
}
exitErrMu.Lock()
exitErr = err
exitErrMu.Unlock()
status = "FAIL"
}
fmt.Printf("%s\t%s\t%.3fs\n", status, pkg.ImportPath, time.Since(start).Seconds())
return nil
})
}
if err := executions.Wait(); err != nil {
return err
}
return exitErr
}()
exitCode := handleError(err, options, nil)
os.Exit(exitCode)
}
cmdServe := &cobra.Command{
Use: "serve [root]",
Short: "compile on-the-fly and serve",
}
cmdServe.Flags().AddFlagSet(flagVerbose)
cmdServe.Flags().AddFlagSet(flagQuiet)
cmdServe.Flags().AddFlagSet(compilerFlags)
var addr string
cmdServe.Flags().StringVarP(&addr, "http", "", ":8080", "HTTP bind address to serve")
cmdServe.Run = func(cmd *cobra.Command, args []string) {
options.BuildTags = strings.Fields(tags)
var root string
if len(args) > 1 {
cmdServe.HelpFunc()(cmd, args)
os.Exit(1)
}
if len(args) == 1 {
root = args[0]
}
// Create a new session eagerly to check if it fails, and report the error right away.
// Otherwise users will see it only after trying to serve a package, which is a bad experience.
_, err := gbuild.NewSession(options)
if err != nil {
options.PrintError("%s\n", err)
os.Exit(1)
}
sourceFiles := http.FileServer(serveCommandFileSystem{
serveRoot: root,
options: options,
sourceMaps: make(map[string][]byte),
})
ln, err := net.Listen("tcp", addr)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if tcpAddr := ln.Addr().(*net.TCPAddr); tcpAddr.IP.Equal(net.IPv4zero) || tcpAddr.IP.Equal(net.IPv6zero) { // Any available addresses.
fmt.Printf("serving at http://localhost:%d and on port %d of any available addresses\n", tcpAddr.Port, tcpAddr.Port)
} else { // Specific address.
fmt.Printf("serving at http://%s\n", tcpAddr)
}
fmt.Fprintln(os.Stderr, http.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}, sourceFiles))
}
cmdVersion := &cobra.Command{
Use: "version",
Short: "print GopherJS compiler version",
}
cmdVersion.Run = func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
cmdServe.HelpFunc()(cmd, args)
os.Exit(1)
}
fmt.Printf("GopherJS %s\n", compiler.Version)
}
rootCmd := &cobra.Command{
Use: "gopherjs",
Long: "GopherJS is a tool for compiling Go source code to JavaScript.",
}
rootCmd.AddCommand(cmdBuild, cmdGet, cmdInstall, cmdRun, cmdTest, cmdServe, cmdVersion, cmdDoc)
err := rootCmd.Execute()
if err != nil {
os.Exit(2)
}
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
type serveCommandFileSystem struct {
serveRoot string
options *gbuild.Options
sourceMaps map[string][]byte
}
func (fs serveCommandFileSystem) Open(requestName string) (http.File, error) {
name := path.Join(fs.serveRoot, requestName[1:]) // requestName[0] == '/'
log.Printf("Request: %s", name)
dir, file := path.Split(name)
base := path.Base(dir) // base is parent folder name, which becomes the output file name.
isPkg := file == base+".js"
isMap := file == base+".js.map"
isIndex := file == "index.html"
// Create a new session to pick up changes to source code on disk.
// TODO(dmitshur): might be possible to get a single session to detect changes to source code on disk
s, err := gbuild.NewSession(fs.options)
if err != nil {
return nil, err
}
if isPkg || isMap || isIndex {
// If we're going to be serving our special files, make sure there's a Go command in this folder.
pkg, err := gbuild.Import(path.Dir(name), 0, s.InstallSuffix(), fs.options.BuildTags)
if err != nil || pkg.Name != "main" {
isPkg = false
isMap = false
isIndex = false
}
switch {
case isPkg:
buf := new(bytes.Buffer)
browserErrors := new(bytes.Buffer)
err := func() error {
archive, err := s.BuildPackage(pkg)
if err != nil {
return err
}
sourceMapFilter := &compiler.SourceMapFilter{Writer: buf}
m := &sourcemap.Map{File: base + ".js"}
sourceMapFilter.MappingCallback = gbuild.NewMappingCallback(m, fs.options.GOROOT, fs.options.GOPATH, fs.options.MapToLocalDisk)
deps, err := compiler.ImportDependencies(archive, s.BuildImportPath)
if err != nil {
return err
}
if err := compiler.WriteProgramCode(deps, sourceMapFilter, s.GoRelease()); err != nil {
return err
}
mapBuf := new(bytes.Buffer)
m.WriteTo(mapBuf)
buf.WriteString("//# sourceMappingURL=" + base + ".js.map\n")
fs.sourceMaps[name+".map"] = mapBuf.Bytes()
return nil
}()
handleError(err, fs.options, browserErrors)
if err != nil {
buf = browserErrors
}
return newFakeFile(base+".js", buf.Bytes()), nil
case isMap:
if content, ok := fs.sourceMaps[name]; ok {
return newFakeFile(base+".js.map", content), nil
}
}
}
// First try to serve the request with a root prefix supplied in the CLI.
if f, err := fs.serveSourceTree(s.XContext(), name); err == nil {
return f, nil
}
// If that didn't work, try without the prefix.
if f, err := fs.serveSourceTree(s.XContext(), requestName); err == nil {
return f, nil
}
if isIndex {
// If there was no index.html file in any dirs, supply our own.
return newFakeFile("index.html", []byte(`<html><head><meta charset="utf-8"><script src="`+base+`.js"></script></head><body></body></html>`)), nil
}
return nil, os.ErrNotExist
}
func (fs serveCommandFileSystem) serveSourceTree(xctx gbuild.XContext, reqPath string) (http.File, error) {
parts := strings.Split(path.Clean(reqPath), "/")
// Under Go Modules different packages can be located in different module
// directories, which no longer align with import paths.
//
// We don't know which part of the requested path is package import path and
// which is a path under the package directory, so we try different slipt
// points until the package is found successfully.
for i := len(parts); i > 0; i-- {
pkgPath := path.Clean(path.Join(parts[:i]...))
filePath := path.Clean(path.Join(parts[i:]...))
if pkg, err := xctx.Import(pkgPath, ".", build.FindOnly); err == nil {
return http.Dir(pkg.Dir).Open(filePath)
}
}
return nil, os.ErrNotExist
}
type fakeFile struct {
name string
size int
io.ReadSeeker
}
func newFakeFile(name string, content []byte) *fakeFile {
return &fakeFile{name: name, size: len(content), ReadSeeker: bytes.NewReader(content)}
}
func (f *fakeFile) Close() error {
return nil
}
func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, os.ErrInvalid
}
func (f *fakeFile) Stat() (os.FileInfo, error) {
return f, nil
}
func (f *fakeFile) Name() string {
return f.name
}
func (f *fakeFile) Size() int64 {
return int64(f.size)
}
func (f *fakeFile) Mode() os.FileMode {
return 0
}
func (f *fakeFile) ModTime() time.Time {
return time.Time{}
}
func (f *fakeFile) IsDir() bool {
return false
}
func (f *fakeFile) Sys() interface{} {
return nil
}
// handleError handles err and returns an appropriate exit code.
// If browserErrors is non-nil, errors are written for presentation in browser.
func handleError(err error, options *gbuild.Options, browserErrors *bytes.Buffer) int {
switch err := err.(type) {
case nil:
return 0
case compiler.ErrorList:
for _, entry := range err {
printError(entry, options, browserErrors)
}
return 1
case *exec.ExitError:
return err.Sys().(syscall.WaitStatus).ExitStatus()
default:
printError(err, options, browserErrors)
return 1
}
}
// printError prints err to Stderr with options. If browserErrors is non-nil, errors are also written for presentation in browser.
func printError(err error, options *gbuild.Options, browserErrors *bytes.Buffer) {
e := sprintError(err)
options.PrintError("%s\n", e)
if browserErrors != nil {
fmt.Fprintln(browserErrors, `console.error("`+template.JSEscapeString(e)+`");`)
}
}
// sprintError returns an annotated error string without trailing newline.
func sprintError(err error) string {
makeRel := func(name string) string {
if relname, err := filepath.Rel(currentDirectory, name); err == nil {
return relname
}
return name
}
switch e := err.(type) {
case *scanner.Error:
return fmt.Sprintf("%s:%d:%d: %s", makeRel(e.Pos.Filename), e.Pos.Line, e.Pos.Column, e.Msg)
case types.Error:
pos := e.Fset.Position(e.Pos)
return fmt.Sprintf("%s:%d:%d: %s", makeRel(pos.Filename), pos.Line, pos.Column, e.Msg)
default:
return fmt.Sprintf("%s", e)
}
}
// runNode runs script with args using Node.js in directory dir.
// If dir is empty string, current directory is used.
// Is out is not nil, process stderr and stdout are redirected to it, otherwise
// os.Stdout and os.Stderr are used.
func runNode(script string, args []string, dir string, quiet bool, out io.Writer) error {
var allArgs []string
if b, _ := strconv.ParseBool(os.Getenv("SOURCE_MAP_SUPPORT")); os.Getenv("SOURCE_MAP_SUPPORT") == "" || b {
allArgs = []string{"--require", "source-map-support/register"}
if err := exec.Command("node", "--require", "source-map-support/register", "--eval", "").Run(); err != nil {
if !quiet {
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.")
}
allArgs = []string{}
}
}
if runtime.GOOS != "windows" {
// We've seen issues with stack space limits causing
// recursion-heavy standard library tests to fail (e.g., see
// https://github.com/gopherjs/gopherjs/pull/669#issuecomment-319319483).
//
// There are two separate limits in non-Windows environments:
//
// - OS process limit
// - Node.js (V8) limit
//
// GopherJS fetches the current OS process limit, and sets the
// Node.js limit to the same value. So both limits are kept in sync
// and can be controlled by setting OS process limit. E.g.:
//
// ulimit -s 10000 && gopherjs test
//
cur, err := sysutil.RlimitStack()
if err != nil {
return fmt.Errorf("failed to get stack size limit: %v", err)
}
allArgs = append(allArgs, fmt.Sprintf("--stack_size=%v", cur/1000)) // Convert from bytes to KB.
}
allArgs = append(allArgs, script)
allArgs = append(allArgs, args...)
node := exec.Command("node", allArgs...)
node.Dir = dir
node.Stdin = os.Stdin
if out != nil {
node.Stdout = out
node.Stderr = out
} else {
node.Stdout = os.Stdout
node.Stderr = os.Stderr
}
err := node.Run()
if _, ok := err.(*exec.ExitError); err != nil && !ok {
err = fmt.Errorf("could not run Node.js: %s", err.Error())
}
return err
}
// runTestDir returns the directory for Node.js to use when running tests for package p.
// Empty string means current directory.
func runTestDir(p *gbuild.PackageData) string {
if p.IsVirtual {
// The package is virtual and doesn't have a physical directory. Use current directory.
return ""
}
// Run tests in the package directory.
return p.Dir
}
type testFuncs struct {
BuildContext *build.Context
Tests []testFunc
Benchmarks []testFunc
Examples []testFunc
TestMain *testFunc
Package *build.Package
ImportTest bool
NeedTest bool
ImportXtest bool
NeedXtest bool
}
type testFunc struct {
Package string // imported package name (_test or _xtest)
Name string // function name
Output string // output, for examples
Unordered bool // output is allowed to be unordered.
}
var testFileSet = token.NewFileSet()
func (t *testFuncs) load(dir, file, pkg string, doImport, seen *bool) error {
f, err := buildutil.ParseFile(testFileSet, t.BuildContext, nil, dir, file, parser.ParseComments)
if err != nil {
return err
}
for _, d := range f.Decls {
n, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
if n.Recv != nil {
continue
}
name := n.Name.String()
switch {
case isTestMain(n):
if t.TestMain != nil {
return errors.New("multiple definitions of TestMain")
}
t.TestMain = &testFunc{pkg, name, "", false}
*doImport, *seen = true, true
case isTest(name, "Test"):
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
case isTest(name, "Benchmark"):
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
}
}
ex := doc.Examples(f)
sort.Sort(byOrder(ex))
for _, e := range ex {
*doImport = true // import test file whether executed or not
if e.Output == "" && !e.EmptyOutput {
// Don't run examples with no output.
continue
}
t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
*seen = true
}
return nil
}
type byOrder []*doc.Example
func (x byOrder) Len() int { return len(x) }
func (x byOrder) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byOrder) Less(i, j int) bool { return x[i].Order < x[j].Order }
// isTestMain tells whether fn is a TestMain(m *testing.M) function.
func isTestMain(fn *ast.FuncDecl) bool {
if fn.Name.String() != "TestMain" ||
fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
fn.Type.Params == nil ||
len(fn.Type.Params.List) != 1 ||
len(fn.Type.Params.List[0].Names) > 1 {
return false
}
ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
if !ok {
return false
}
// We can't easily check that the type is *testing.M
// because we don't know how testing has been imported,
// but at least check that it's *M or *something.M.
if name, ok := ptr.X.(*ast.Ident); ok && name.Name == "M" {
return true
}
if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == "M" {
return true
}
return false
}
// isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
// We don't want TesticularCancer.
func isTest(name, prefix string) bool {
if !strings.HasPrefix(name, prefix) {
return false
}
if len(name) == len(prefix) { // "Test" is ok
return true
}
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
}
var testmainTmpl = template.Must(template.New("main").Parse(`
package main
import (
{{if not .TestMain}}
"os"
{{end}}
"testing"
"testing/internal/testdeps"
{{if .ImportTest}}
{{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
{{end}}
{{if .ImportXtest}}
{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}}
)
var tests = []testing.InternalTest{
{{range .Tests}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var benchmarks = []testing.InternalBenchmark{
{{range .Benchmarks}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var examples = []testing.InternalExample{
{{range .Examples}}
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
{{end}}
}
func main() {
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
{{else}}
os.Exit(m.Run())
{{end}}
}
`))
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/dgchuan/gopherjs_gopherjs.git
git@gitee.com:dgchuan/gopherjs_gopherjs.git
dgchuan
gopherjs_gopherjs
gopherjs_gopherjs
master

搜索帮助