// Build creates a build and returns the first error happening. All errors are typed. func (b *builder) Build() error { if b.target == nil { return ErrorMissingTarget{} } fileSet := token.NewFileSet() files := []*ast.File{} for name, reader := range b.files { if reader == nil { return ErrorParsing{name, "reader must not be nil"} } f, err := parser.ParseFile(fileSet, name, reader, 0) if err != nil { return ErrorParsing{name, err.Error()} } files = append(files, f) } s := build.NewSession(b.options) archive, err := compiler.Compile(b.pkgName, files, fileSet, s.ImportContext, b.options.Minify) if err != nil { return ErrorCompiling(err.Error()) } deps, err := compiler.ImportDependencies(archive, s.ImportContext.Import) if err != nil { return ErrorImportingDependencies(err.Error()) } return compiler.WriteProgramCode(deps, &compiler.SourceMapFilter{Writer: b.target}) }
func Compile(ctx context.Context, source []byte, mapping bool) ([]byte, error) { vos := vosctx.FromContext(ctx) options := &build.Options{ GOROOT: vos.Getenv("GOROOT"), GOPATH: vos.Getenv("GOPATH"), CreateMapFile: mapping, } s := build.NewSession(options) packages := make(map[string]*compiler.Archive) importContext := &compiler.ImportContext{ Packages: s.Types, Import: s.BuildImportPath, } fileSet := token.NewFileSet() file, err := parser.ParseFile(fileSet, "prog.go", source, parser.ParseComments) if err != nil { return nil, kerr.Wrap("NCYFEKGCWX", err) } mainPkg, err := compiler.Compile("main", []*ast.File{file}, fileSet, importContext, false) if err != nil { return nil, kerr.Wrap("KPHUKOLTBX", err) } packages["main"] = mainPkg bufCode := bytes.NewBuffer(nil) filter := &compiler.SourceMapFilter{Writer: bufCode} allPkgs, err := compiler.ImportDependencies(mainPkg, importContext.Import) if err != nil { return nil, kerr.Wrap("TIMQHFQTWL", err) } if mapping { bufMap := bytes.NewBuffer(nil) smap := &sourcemap.Map{File: "script.js"} filter.MappingCallback = build.NewMappingCallback(smap, options.GOROOT, options.GOPATH, false) if err := compiler.WriteProgramCode(allPkgs, filter); err != nil { return nil, kerr.Wrap("YKQEKRKBPL", err) } if err := smap.WriteTo(bufMap); err != nil { return nil, kerr.Wrap("VYQGYAAADG", err) } return bufMap.Bytes(), nil } if err := compiler.WriteProgramCode(allPkgs, filter); err != nil { return nil, kerr.Wrap("DPPVHCOTBQ", err) } if _, err := bufCode.WriteString("//# sourceMappingURL=script.js.map\n"); err != nil { return nil, kerr.Wrap("CXXKWQVGUI", err) } return bufCode.Bytes(), nil }
func main() { options := &gbuild.Options{CreateMapFile: true} var pkgObj string pflag.BoolVarP(&options.Verbose, "verbose", "v", false, "print the names of packages as they are compiled") flagVerbose := pflag.Lookup("verbose") pflag.BoolVarP(&options.Quiet, "quiet", "q", false, "suppress non-fatal warnings") flagQuiet := pflag.Lookup("quiet") pflag.BoolVarP(&options.Watch, "watch", "w", false, "watch for changes to the source files") flagWatch := pflag.Lookup("watch") pflag.BoolVarP(&options.Minify, "minify", "m", false, "minify generated code") flagMinify := pflag.Lookup("minify") pflag.BoolVar(&options.Color, "color", terminal.IsTerminal(int(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb", "colored output") flagColor := pflag.Lookup("color") tags := pflag.String("tags", "", "a list of build tags to consider satisfied during the build") flagTags := pflag.Lookup("tags") cmdBuild := &cobra.Command{ Use: "build [packages]", Short: "compile packages and dependencies", } cmdBuild.Flags().StringVarP(&pkgObj, "output", "o", "", "output file") cmdBuild.Flags().AddFlag(flagVerbose) cmdBuild.Flags().AddFlag(flagQuiet) cmdBuild.Flags().AddFlag(flagWatch) cmdBuild.Flags().AddFlag(flagMinify) cmdBuild.Flags().AddFlag(flagColor) cmdBuild.Flags().AddFlag(flagTags) cmdBuild.Run = func(cmd *cobra.Command, args []string) { options.BuildTags = strings.Fields(*tags) for { s := gbuild.NewSession(options) exitCode := handleError(func() error { if len(args) == 0 { return s.BuildDir(currentDirectory, currentDirectory, pkgObj) } if 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 err := s.BuildFiles(args, pkgObj, currentDirectory); err != nil { return err } return nil } for _, pkgPath := range args { pkgPath = filepath.ToSlash(pkgPath) pkg, err := gbuild.Import(pkgPath, 0, s.InstallSuffix(), options.BuildTags) if err != nil { return err } archive, err := s.BuildPackage(pkg) if err != nil { return err } if pkgObj == "" { pkgObj = filepath.Base(args[0]) + ".js" } if pkg.IsCommand() && !pkg.UpToDate { if err := s.WriteCommandPackage(archive, pkgObj); err != nil { return err } } } return nil }, options, nil) os.Exit(exitCode) } } cmdInstall := &cobra.Command{ Use: "install [packages]", Short: "compile and install packages and dependencies", } cmdInstall.Flags().AddFlag(flagVerbose) cmdInstall.Flags().AddFlag(flagQuiet) cmdInstall.Flags().AddFlag(flagWatch) cmdInstall.Flags().AddFlag(flagMinify) cmdInstall.Flags().AddFlag(flagColor) cmdInstall.Flags().AddFlag(flagTags) cmdInstall.Run = func(cmd *cobra.Command, args []string) { options.BuildTags = strings.Fields(*tags) for { s := gbuild.NewSession(options) exitCode := handleError(func() error { pkgs := args if len(pkgs) == 0 { firstGopathWorkspace := filepath.SplitList(build.Default.GOPATH)[0] // TODO: The GOPATH workspace that contains the package source should be chosen. srcDir, err := filepath.EvalSymlinks(filepath.Join(firstGopathWorkspace, "src")) if err != nil { return err } if !strings.HasPrefix(currentDirectory, srcDir) { return fmt.Errorf("gopherjs install: no install location for directory %s outside GOPATH", currentDirectory) } pkgPath, err := filepath.Rel(srcDir, currentDirectory) if err != nil { return err } pkgs = []string{pkgPath} } 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 { pkgPath = filepath.ToSlash(pkgPath) pkg, err := gbuild.Import(pkgPath, 0, s.InstallSuffix(), options.BuildTags) 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 }, options, nil) os.Exit(exitCode) } } cmdGet := &cobra.Command{ Use: "get [packages]", Short: "download and install packages and dependencies", } cmdGet.Flags().AddFlag(flagVerbose) cmdGet.Flags().AddFlag(flagQuiet) cmdGet.Flags().AddFlag(flagWatch) cmdGet.Flags().AddFlag(flagMinify) cmdGet.Flags().AddFlag(flagColor) cmdGet.Flags().AddFlag(flagTags) cmdGet.Run = cmdInstall.Run cmdRun := &cobra.Command{ Use: "run [gofiles...] [arguments...]", Short: "compile and run Go program", } cmdRun.Run = func(cmd *cobra.Command, args []string) { os.Exit(handleError(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 := gbuild.NewSession(options) if err := s.BuildFiles(args[:lastSourceArg], tempfile.Name(), currentDirectory); err != nil { return err } if err := runNode(tempfile.Name(), args[lastSourceArg:], "", options.Quiet); err != nil { return err } return nil }, options, nil)) } 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=.'.") 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).") cmdTest.Flags().AddFlag(flagMinify) cmdTest.Flags().AddFlag(flagColor) cmdTest.Run = func(cmd *cobra.Command, args []string) { os.Exit(handleError(func() error { pkgs := make([]*gbuild.PackageData, len(args)) for i, pkgPath := range args { pkgPath = filepath.ToSlash(pkgPath) var err error pkgs[i], err = gbuild.Import(pkgPath, 0, "", nil) if err != nil { return err } } if len(pkgs) == 0 { firstGopathWorkspace := filepath.SplitList(build.Default.GOPATH)[0] srcDir, err := filepath.EvalSymlinks(filepath.Join(firstGopathWorkspace, "src")) if err != nil { return err } var pkg *gbuild.PackageData if strings.HasPrefix(currentDirectory, srcDir) { pkgPath, err := filepath.Rel(srcDir, currentDirectory) if err != nil { return err } if pkg, err = gbuild.Import(pkgPath, 0, "", nil); err != nil { return err } } if pkg == nil { if pkg, err = gbuild.ImportDir(currentDirectory, 0); err != nil { return err } pkg.ImportPath = "_" + currentDirectory } pkgs = []*gbuild.PackageData{pkg} } var exitErr error for _, pkg := range pkgs { if len(pkg.TestGoFiles) == 0 && len(pkg.XTestGoFiles) == 0 { fmt.Printf("? \t%s\t[no test files]\n", pkg.ImportPath) continue } s := gbuild.NewSession(options) tests := &testFuncs{Package: pkg.Package} collectTests := func(testPkg *gbuild.PackageData, testPkgName string, needVar *bool) error { archive, err := s.BuildPackage(testPkg) if err != nil { return err } for _, decl := range archive.Declarations { if strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Test") { tests.Tests = append(tests.Tests, testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]}) *needVar = true } if strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Benchmark") { tests.Benchmarks = append(tests.Benchmarks, testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]}) *needVar = true } } return nil } if err := collectTests(&gbuild.PackageData{ Package: &build.Package{ ImportPath: pkg.ImportPath, Dir: pkg.Dir, GoFiles: append(pkg.GoFiles, pkg.TestGoFiles...), Imports: append(pkg.Imports, pkg.TestImports...), }, IsTest: true, JSFiles: pkg.JSFiles, }, "_test", &tests.NeedTest); err != nil { return err } if err := collectTests(&gbuild.PackageData{ Package: &build.Package{ ImportPath: pkg.ImportPath + "_test", Dir: pkg.Dir, GoFiles: pkg.XTestGoFiles, Imports: pkg.XTestImports, }, IsTest: true, }, "_xtest", &tests.NeedXtest); err != nil { return err } buf := bytes.NewBuffer(nil) 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 = ioutil.TempFile(currentDirectory, "test.") if err != nil { return err } } defer func() { outfile.Close() if *outputFilename == "" { os.Remove(outfile.Name()) os.Remove(outfile.Name() + ".map") } }() 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 *run != "" { args = append(args, "-test.run", *run) } if *short { args = append(args, "-test.short") } if *verbose { args = append(args, "-test.v") } status := "ok " start := time.Now() if err := runNode(outfile.Name(), args, pkg.Dir, options.Quiet); err != nil { if _, ok := err.(*exec.ExitError); !ok { return err } exitErr = err status = "FAIL" } fmt.Printf("%s\t%s\t%.3fs\n", status, pkg.ImportPath, time.Now().Sub(start).Seconds()) } return exitErr }, options, nil)) } cmdTool := &cobra.Command{ Use: "tool [command] [args...]", Short: "run specified go tool", } cmdTool.Flags().BoolP("e", "e", false, "") cmdTool.Flags().BoolP("l", "l", false, "") cmdTool.Flags().StringP("o", "o", "", "") cmdTool.Flags().StringP("D", "D", "", "") cmdTool.Flags().StringP("I", "I", "", "") cmdTool.Run = func(cmd *cobra.Command, args []string) { os.Exit(handleError(func() error { if len(args) == 2 { switch args[0][1] { case 'g': basename := filepath.Base(args[1]) s := gbuild.NewSession(options) if err := s.BuildFiles([]string{args[1]}, basename[:len(basename)-3]+".js", currentDirectory); err != nil { return err } return nil } } cmdTool.Help() return nil }, options, nil)) } cmdServe := &cobra.Command{ Use: "serve [root]", Short: "compile on-the-fly and serve", } cmdServe.Flags().AddFlag(flagVerbose) cmdServe.Flags().AddFlag(flagQuiet) cmdServe.Flags().AddFlag(flagMinify) cmdServe.Flags().AddFlag(flagColor) cmdServe.Flags().AddFlag(flagTags) 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) dirs := append(filepath.SplitList(build.Default.GOPATH), build.Default.GOROOT) var root string if len(args) > 1 { cmdServe.Help() return } if len(args) == 1 { root = args[0] } sourceFiles := http.FileServer(serveCommandFileSystem{ serveRoot: root, options: options, dirs: dirs, 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)) } rootCmd := &cobra.Command{ Use: "gopherjs", Long: "GopherJS is a tool for compiling Go source code to JavaScript.", } rootCmd.AddCommand(cmdBuild, cmdGet, cmdInstall, cmdRun, cmdTest, cmdTool, cmdServe) rootCmd.Execute() }
func (s *Session) BuildPackage(pkg *PackageData) error { s.Packages[pkg.ImportPath] = pkg if pkg.ImportPath == "unsafe" { return nil } if pkg.PkgObj != "" { var fileInfo os.FileInfo gopherjsBinary, err := osext.Executable() if err == nil { fileInfo, err = os.Stat(gopherjsBinary) if err == nil { pkg.SrcModTime = fileInfo.ModTime() } } if err != nil { os.Stderr.WriteString("Could not get GopherJS binary's modification timestamp. Please report issue.\n") pkg.SrcModTime = time.Now() } for _, importedPkgPath := range pkg.Imports { ignored := true for _, pos := range pkg.ImportPos[importedPkgPath] { importFile := filepath.Base(pos.Filename) for _, file := range pkg.GoFiles { if importFile == file { ignored = false break } } if !ignored { break } } if importedPkgPath == "unsafe" || ignored { continue } _, err := s.BuildImportPath(importedPkgPath) if err != nil { return err } impModeTime := s.Packages[importedPkgPath].SrcModTime if impModeTime.After(pkg.SrcModTime) { pkg.SrcModTime = impModeTime } } for _, name := range append(pkg.GoFiles, pkg.JSFiles...) { fileInfo, err := os.Stat(filepath.Join(pkg.Dir, name)) if err != nil { return err } if fileInfo.ModTime().After(pkg.SrcModTime) { pkg.SrcModTime = fileInfo.ModTime() } } pkgObjFileInfo, err := os.Stat(pkg.PkgObj) if err == nil && !pkg.SrcModTime.After(pkgObjFileInfo.ModTime()) { // package object is up to date, load from disk if library pkg.UpToDate = true if pkg.IsCommand() { return nil } objFile, err := os.Open(pkg.PkgObj) if err != nil { return err } defer objFile.Close() pkg.Archive, err = compiler.ReadArchive(pkg.PkgObj, pkg.ImportPath, objFile, s.Types) if err != nil { return err } return nil } } fileSet := token.NewFileSet() files, err := parse(pkg.Package, pkg.IsTest, fileSet) if err != nil { return err } importContext := &compiler.ImportContext{ Packages: s.Types, Import: func(path string) (*compiler.Archive, error) { return s.buildImportPathWithSrcDir(path, pkg.Dir) }, } pkg.Archive, err = compiler.Compile(pkg.ImportPath, files, fileSet, importContext, s.options.Minify) if err != nil { return err } for _, jsFile := range pkg.JSFiles { code, err := ioutil.ReadFile(filepath.Join(pkg.Dir, jsFile)) if err != nil { return err } pkg.Archive.IncJSCode = append(pkg.Archive.IncJSCode, []byte("\t(function() {\n")...) pkg.Archive.IncJSCode = append(pkg.Archive.IncJSCode, code...) pkg.Archive.IncJSCode = append(pkg.Archive.IncJSCode, []byte("\n\t}).call($global);\n")...) } if s.options.Verbose { fmt.Println(pkg.ImportPath) } if pkg.PkgObj == "" || pkg.IsCommand() { return nil } if err := s.writeLibraryPackage(pkg, pkg.PkgObj); err != nil { if strings.HasPrefix(pkg.PkgObj, s.options.GOROOT) { // fall back to first GOPATH workspace firstGopathWorkspace := filepath.SplitList(s.options.GOPATH)[0] if err := s.writeLibraryPackage(pkg, filepath.Join(firstGopathWorkspace, pkg.PkgObj[len(s.options.GOROOT):])); err != nil { return err } return nil } return err } return nil }
func main() { flags := flag.NewFlagSet("", flag.ContinueOnError) cmd := "help" var cmdArgs []string if err := flags.Parse(os.Args[1:]); err == nil && flags.NArg() != 0 { cmd = flags.Arg(0) cmdArgs = flags.Args()[1:] if cmd == "help" && flags.NArg() == 2 { cmd = flags.Arg(1) cmdArgs = []string{"--help"} } } options := &gbuild.Options{CreateMapFile: true} switch cmd { case "build": buildFlags := flag.NewFlagSet("build command", flag.ExitOnError) var pkgObj string buildFlags.StringVar(&pkgObj, "o", "", "output file") buildFlags.BoolVar(&options.Verbose, "v", false, "print the names of packages as they are compiled") buildFlags.BoolVar(&options.Watch, "w", false, "watch for changes to the source files") buildFlags.BoolVar(&options.Minify, "m", false, "minify generated code") buildFlags.Parse(cmdArgs) for { s := gbuild.NewSession(options) exitCode := handleError(func() error { if buildFlags.NArg() == 0 { return s.BuildDir(currentDirectory, currentDirectory, pkgObj) } if strings.HasSuffix(buildFlags.Arg(0), ".go") { for _, arg := range buildFlags.Args() { if !strings.HasSuffix(arg, ".go") { return fmt.Errorf("named files must be .go files") } } if pkgObj == "" { basename := filepath.Base(buildFlags.Arg(0)) pkgObj = basename[:len(basename)-3] + ".js" } names := make([]string, buildFlags.NArg()) for i, name := range buildFlags.Args() { name = filepath.ToSlash(name) names[i] = name if s.Watcher != nil { s.Watcher.Watch(filepath.ToSlash(name)) } } if err := s.BuildFiles(buildFlags.Args(), pkgObj, currentDirectory); err != nil { return err } return nil } for _, pkgPath := range buildFlags.Args() { pkgPath = filepath.ToSlash(pkgPath) if s.Watcher != nil { s.Watcher.Watch(pkgPath) } buildPkg, err := gbuild.Import(pkgPath, 0, s.ArchSuffix()) if err != nil { return err } pkg := &gbuild.PackageData{Package: buildPkg} if err := s.BuildPackage(pkg); err != nil { return err } if pkgObj == "" { pkgObj = filepath.Base(buildFlags.Arg(0)) + ".js" } if err := s.WriteCommandPackage(pkg, pkgObj); err != nil { return err } } return nil }) if s.Watcher == nil { os.Exit(exitCode) } s.WaitForChange() } case "install": installFlags := flag.NewFlagSet("install command", flag.ExitOnError) installFlags.BoolVar(&options.Verbose, "v", false, "print the names of packages as they are compiled") installFlags.BoolVar(&options.Watch, "w", false, "watch for changes to the source files") installFlags.BoolVar(&options.Minify, "m", false, "minify generated code") installFlags.Parse(cmdArgs) for { s := gbuild.NewSession(options) exitCode := handleError(func() error { pkgs := installFlags.Args() if len(pkgs) == 0 { firstGopathWorkspace := filepath.SplitList(build.Default.GOPATH)[0] // TODO: The GOPATH workspace that contains the package source should be chosen. srcDir, err := filepath.EvalSymlinks(filepath.Join(firstGopathWorkspace, "src")) if err != nil { return err } if !strings.HasPrefix(currentDirectory, srcDir) { return fmt.Errorf("gopherjs install: no install location for directory %s outside GOPATH", currentDirectory) } pkgPath, err := filepath.Rel(srcDir, currentDirectory) if err != nil { return err } pkgs = []string{pkgPath} } for _, pkgPath := range pkgs { pkgPath = filepath.ToSlash(pkgPath) if _, err := s.ImportPackage(pkgPath); err != nil { return err } pkg := s.Packages[pkgPath] if err := s.WriteCommandPackage(pkg, pkg.PkgObj); err != nil { return err } } return nil }) if s.Watcher == nil { os.Exit(exitCode) } s.WaitForChange() } case "run": runFlags := flag.NewFlagSet("run command", flag.ExitOnError) runFlags.Parse(cmdArgs) os.Exit(handleError(func() error { lastSourceArg := 0 for { if !strings.HasSuffix(runFlags.Arg(lastSourceArg), ".go") { break } lastSourceArg++ } if lastSourceArg == 0 { return fmt.Errorf("gopherjs run: no go files listed") } tempfile, err := ioutil.TempFile("", filepath.Base(runFlags.Arg(0))+".") if err != nil { return err } defer func() { tempfile.Close() os.Remove(tempfile.Name()) }() s := gbuild.NewSession(options) if err := s.BuildFiles(runFlags.Args()[:lastSourceArg], tempfile.Name(), currentDirectory); err != nil { return err } if err := runNode(tempfile.Name(), runFlags.Args()[lastSourceArg:], ""); err != nil { return err } return nil })) case "test": testFlags := flag.NewFlagSet("test command", flag.ExitOnError) verbose := testFlags.Bool("v", false, "verbose") short := testFlags.Bool("short", false, "short") testFlags.BoolVar(&options.Minify, "m", false, "minify generated code") testFlags.Parse(cmdArgs) os.Exit(handleError(func() error { pkgs := make([]*build.Package, testFlags.NArg()) for i, pkgPath := range testFlags.Args() { pkgPath = filepath.ToSlash(pkgPath) var err error pkgs[i], err = gbuild.Import(pkgPath, 0, "js") if err != nil { return err } } if len(pkgs) == 0 { firstGopathWorkspace := filepath.SplitList(build.Default.GOPATH)[0] // TODO: Not sure if always picking first GOPATH workspace here is the right thing. srcDir, err := filepath.EvalSymlinks(filepath.Join(firstGopathWorkspace, "src")) if err != nil { return err } var pkg *build.Package if strings.HasPrefix(currentDirectory, srcDir) { pkgPath, err := filepath.Rel(srcDir, currentDirectory) if err != nil { return err } if pkg, err = gbuild.Import(pkgPath, 0, "js"); err != nil { return err } } if pkg == nil { if pkg, err = build.ImportDir(currentDirectory, 0); err != nil { return err } pkg.ImportPath = "_" + currentDirectory } pkgs = []*build.Package{pkg} } var exitErr error for _, buildPkg := range pkgs { if len(buildPkg.TestGoFiles) == 0 && len(buildPkg.XTestGoFiles) == 0 { fmt.Printf("? \t%s\t[no test files]\n", buildPkg.ImportPath) continue } buildPkg.PkgObj = "" buildPkg.GoFiles = append(buildPkg.GoFiles, buildPkg.TestGoFiles...) pkg := &gbuild.PackageData{Package: buildPkg} s := gbuild.NewSession(options) if err := s.BuildPackage(pkg); err != nil { return err } tests := &testFuncs{Package: buildPkg} collectTests := func(pkg *gbuild.PackageData, testPkg string) bool { for _, name := range pkg.Archive.Tests { tests.Tests = append(tests.Tests, testFunc{Package: testPkg, Name: name}) } return len(pkg.Archive.Tests) != 0 } tests.NeedTest = collectTests(pkg, "_test") if len(pkg.XTestGoFiles) != 0 { testPkg := &gbuild.PackageData{Package: &build.Package{ ImportPath: pkg.ImportPath + "_test", Dir: pkg.Dir, GoFiles: pkg.XTestGoFiles, }} if err := s.BuildPackage(testPkg); err != nil { return err } tests.NeedXtest = collectTests(testPkg, "_xtest") } buf := bytes.NewBuffer(nil) err := testmainTmpl.Execute(buf, tests) if err != nil { return err } fset := token.NewFileSet() mainFile, err := parser.ParseFile(fset, "_testmain.go", buf, 0) if err != nil { return err } mainPkg := &gbuild.PackageData{ Package: &build.Package{ Name: "main", ImportPath: "main", }, } mainPkg.Archive, err = compiler.Compile("main", []*ast.File{mainFile}, fset, s.ImportContext, options.Minify) if err != nil { return err } tempfile, err := ioutil.TempFile("", "test.") if err != nil { return err } defer func() { tempfile.Close() os.Remove(tempfile.Name()) }() if err := s.WriteCommandPackage(mainPkg, tempfile.Name()); err != nil { return err } var args []string if *verbose { args = append(args, "-test.v") } if *short { args = append(args, "-test.short") } status := "ok " start := time.Now() if err := runNode(tempfile.Name(), args, buildPkg.Dir); err != nil { if _, ok := err.(*exec.ExitError); !ok { return err } exitErr = err status = "FAIL" } fmt.Printf("%s\t%s\t%.3fs\n", status, buildPkg.ImportPath, time.Now().Sub(start).Seconds()) } return exitErr })) case "tool": tool := cmdArgs[0] toolFlags := flag.NewFlagSet("tool command", flag.ExitOnError) toolFlags.Bool("e", false, "") toolFlags.Bool("l", false, "") toolFlags.Bool("m", false, "") toolFlags.String("o", "", "") toolFlags.String("D", "", "") toolFlags.String("I", "", "") toolFlags.Parse(flags.Args()[2:]) os.Exit(handleError(func() error { if len(tool) == 2 { switch tool[1] { case 'g': basename := filepath.Base(toolFlags.Arg(0)) s := gbuild.NewSession(options) if err := s.BuildFiles([]string{toolFlags.Arg(0)}, basename[:len(basename)-3]+".js", currentDirectory); err != nil { return err } return nil } } return fmt.Errorf("Tool not supported: " + tool) })) case "help", "": os.Stderr.WriteString(`GopherJS is a tool for compiling Go source code to JavaScript. Usage: gopherjs command [arguments] The commands are: build compile packages and dependencies install compile and install packages and dependencies run compile and run Go program (requires Node.js) test test packages (requires Node.js) Use "go help [command]" for more information about a command. `) default: fmt.Fprintf(os.Stderr, "gopherjs: unknown subcommand \"%s\"\nRun 'gopherjs help' for usage.\n", cmd) } }
func main() { options := &gbuild.Options{CreateMapFile: true} var pkgObj string pflag.BoolVarP(&options.Verbose, "verbose", "v", false, "print the names of packages as they are compiled") flagVerbose := pflag.Lookup("verbose") pflag.BoolVarP(&options.Watch, "watch", "w", false, "watch for changes to the source files") flagWatch := pflag.Lookup("watch") pflag.BoolVarP(&options.Minify, "minify", "m", false, "minify generated code") flagMinify := pflag.Lookup("minify") pflag.BoolVar(&options.Color, "color", terminal.IsTerminal(int(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb", "colored output") flagColor := pflag.Lookup("color") tags := pflag.String("tags", "", "a list of build tags to consider satisfied during the build") flagTags := pflag.Lookup("tags") cmdBuild := &cobra.Command{ Use: "build [packages]", Short: "compile packages and dependencies", } cmdBuild.Flags().StringVarP(&pkgObj, "output", "o", "", "output file") cmdBuild.Flags().AddFlag(flagVerbose) cmdBuild.Flags().AddFlag(flagWatch) cmdBuild.Flags().AddFlag(flagMinify) cmdBuild.Flags().AddFlag(flagColor) cmdBuild.Flags().AddFlag(flagTags) cmdBuild.Run = func(cmd *cobra.Command, args []string) { options.BuildTags = strings.Fields(*tags) for { s := gbuild.NewSession(options) exitCode := handleError(func() error { if len(args) == 0 { return s.BuildDir(currentDirectory, currentDirectory, pkgObj) } if 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) } } if err := s.BuildFiles(args, pkgObj, currentDirectory); err != nil { return err } return nil } for _, pkgPath := range args { pkgPath = filepath.ToSlash(pkgPath) if s.Watcher != nil { s.Watcher.Add(pkgPath) } buildPkg, err := gbuild.Import(pkgPath, 0, s.InstallSuffix(), options.BuildTags) if err != nil { return err } pkg := &gbuild.PackageData{Package: buildPkg} if err := s.BuildPackage(pkg); err != nil { return err } if pkgObj == "" { pkgObj = filepath.Base(args[0]) + ".js" } if err := s.WriteCommandPackage(pkg, pkgObj); err != nil { return err } } return nil }, 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().AddFlag(flagVerbose) cmdInstall.Flags().AddFlag(flagWatch) cmdInstall.Flags().AddFlag(flagMinify) cmdInstall.Flags().AddFlag(flagColor) cmdInstall.Flags().AddFlag(flagTags) cmdInstall.Run = func(cmd *cobra.Command, args []string) { options.BuildTags = strings.Fields(*tags) for { s := gbuild.NewSession(options) exitCode := handleError(func() error { pkgs := args if len(pkgs) == 0 { firstGopathWorkspace := filepath.SplitList(build.Default.GOPATH)[0] // TODO: The GOPATH workspace that contains the package source should be chosen. srcDir, err := filepath.EvalSymlinks(filepath.Join(firstGopathWorkspace, "src")) if err != nil { return err } if !strings.HasPrefix(currentDirectory, srcDir) { return fmt.Errorf("gopherjs install: no install location for directory %s outside GOPATH", currentDirectory) } pkgPath, err := filepath.Rel(srcDir, currentDirectory) if err != nil { return err } pkgs = []string{pkgPath} } if cmd.Name() == "get" { goGet := exec.Command("go", append([]string{"get", "-d"}, pkgs...)...) goGet.Stdout = os.Stdout goGet.Stderr = os.Stderr if err := goGet.Run(); err != nil { return err } } for _, pkgPath := range pkgs { pkgPath = filepath.ToSlash(pkgPath) if _, err := s.ImportPackage(pkgPath); err != nil { return err } pkg := s.Packages[pkgPath] if err := s.WriteCommandPackage(pkg, pkg.PkgObj); err != nil { return err } } return nil }, options, nil) if s.Watcher == nil { os.Exit(exitCode) } s.WaitForChange() } } cmdGet := &cobra.Command{ Use: "get [packages]", Short: "download and install packages and dependencies", } cmdGet.Flags().AddFlag(flagVerbose) cmdGet.Flags().AddFlag(flagWatch) cmdGet.Flags().AddFlag(flagMinify) cmdGet.Flags().AddFlag(flagColor) cmdGet.Flags().AddFlag(flagTags) cmdGet.Run = cmdInstall.Run cmdRun := &cobra.Command{ Use: "run [gofiles...] [arguments...]", Short: "compile and run Go program", } cmdRun.Run = func(cmd *cobra.Command, args []string) { os.Exit(handleError(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("", filepath.Base(args[0])+".") if err != nil { return err } defer func() { tempfile.Close() os.Remove(tempfile.Name()) os.Remove(tempfile.Name() + ".map") }() s := gbuild.NewSession(options) if err := s.BuildFiles(args[:lastSourceArg], tempfile.Name(), currentDirectory); err != nil { return err } if err := runNode(tempfile.Name(), args[lastSourceArg:], ""); err != nil { return err } return nil }, options, nil)) } 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=.'.") 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.") cmdTest.Flags().AddFlag(flagMinify) cmdTest.Flags().AddFlag(flagColor) cmdTest.Run = func(cmd *cobra.Command, args []string) { os.Exit(handleError(func() error { pkgs := make([]*build.Package, len(args)) for i, pkgPath := range args { pkgPath = filepath.ToSlash(pkgPath) var err error pkgs[i], err = gbuild.Import(pkgPath, 0, "", nil) if err != nil { return err } } if len(pkgs) == 0 { firstGopathWorkspace := filepath.SplitList(build.Default.GOPATH)[0] srcDir, err := filepath.EvalSymlinks(filepath.Join(firstGopathWorkspace, "src")) if err != nil { return err } var pkg *build.Package if strings.HasPrefix(currentDirectory, srcDir) { pkgPath, err := filepath.Rel(srcDir, currentDirectory) if err != nil { return err } if pkg, err = gbuild.Import(pkgPath, 0, "", nil); err != nil { return err } } if pkg == nil { if pkg, err = build.ImportDir(currentDirectory, 0); err != nil { return err } pkg.ImportPath = "_" + currentDirectory } pkgs = []*build.Package{pkg} } var exitErr error for _, pkg := range pkgs { if len(pkg.TestGoFiles) == 0 && len(pkg.XTestGoFiles) == 0 { fmt.Printf("? \t%s\t[no test files]\n", pkg.ImportPath) continue } s := gbuild.NewSession(options) tests := &testFuncs{Package: pkg} collectTests := func(buildPkg *build.Package, testPkgName string, needVar *bool) error { testPkg := &gbuild.PackageData{Package: buildPkg} if err := s.BuildPackage(testPkg); err != nil { return err } for _, decl := range testPkg.Archive.Declarations { if strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Test") { tests.Tests = append(tests.Tests, testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]}) *needVar = true } if strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Benchmark") { tests.Benchmarks = append(tests.Benchmarks, testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]}) *needVar = true } } return nil } if err := collectTests(&build.Package{ ImportPath: pkg.ImportPath, Dir: pkg.Dir, GoFiles: append(pkg.GoFiles, pkg.TestGoFiles...), Imports: append(pkg.Imports, pkg.TestImports...), }, "_test", &tests.NeedTest); err != nil { return err } if err := collectTests(&build.Package{ ImportPath: pkg.ImportPath + "_test", Dir: pkg.Dir, GoFiles: pkg.XTestGoFiles, Imports: pkg.XTestImports, }, "_xtest", &tests.NeedXtest); err != nil { return err } buf := bytes.NewBuffer(nil) 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 } mainPkg := &gbuild.PackageData{ Package: &build.Package{ Name: "main", ImportPath: "main", }, } mainPkg.Archive, err = compiler.Compile("main", []*ast.File{mainFile}, fset, s.ImportContext, options.Minify) if err != nil { return err } tempfile, err := ioutil.TempFile("", "test.") if err != nil { return err } defer func() { tempfile.Close() os.Remove(tempfile.Name()) os.Remove(tempfile.Name() + ".map") }() if err := s.WriteCommandPackage(mainPkg, tempfile.Name()); err != nil { return err } var args []string if *bench != "" { args = append(args, "-test.bench", *bench) } if *run != "" { args = append(args, "-test.run", *run) } if *short { args = append(args, "-test.short") } if *verbose { args = append(args, "-test.v") } status := "ok " start := time.Now() if err := runNode(tempfile.Name(), args, pkg.Dir); err != nil { if _, ok := err.(*exec.ExitError); !ok { return err } exitErr = err status = "FAIL" } fmt.Printf("%s\t%s\t%.3fs\n", status, pkg.ImportPath, time.Now().Sub(start).Seconds()) } return exitErr }, options, nil)) } cmdTool := &cobra.Command{ Use: "tool [command] [args...]", Short: "run specified go tool", } cmdTool.Flags().BoolP("e", "e", false, "") cmdTool.Flags().BoolP("l", "l", false, "") cmdTool.Flags().BoolP("m", "m", false, "") cmdTool.Flags().StringP("o", "o", "", "") cmdTool.Flags().StringP("D", "D", "", "") cmdTool.Flags().StringP("I", "I", "", "") cmdTool.Run = func(cmd *cobra.Command, args []string) { os.Exit(handleError(func() error { if len(args) == 2 { switch args[0][1] { case 'g': basename := filepath.Base(args[1]) s := gbuild.NewSession(options) if err := s.BuildFiles([]string{args[1]}, basename[:len(basename)-3]+".js", currentDirectory); err != nil { return err } return nil } } cmdTool.Help() return nil }, options, nil)) } cmdServe := &cobra.Command{ Use: "serve", Short: "compile on-the-fly and serve", } cmdServe.Flags().AddFlag(flagVerbose) cmdServe.Flags().AddFlag(flagMinify) cmdServe.Flags().AddFlag(flagColor) cmdServe.Flags().AddFlag(flagTags) var port int cmdServe.Flags().IntVarP(&port, "port", "p", 8080, "HTTP port") cmdServe.Run = func(cmd *cobra.Command, args []string) { options.BuildTags = strings.Fields(*tags) dirs := append(filepath.SplitList(build.Default.GOPATH), build.Default.GOROOT) sourceFiles := http.FileServer(serveCommandFileSystem{options: options, dirs: dirs, sourceMaps: make(map[string][]byte)}) fmt.Printf("serving at http://localhost:%d\n", port) fmt.Println(http.ListenAndServe(fmt.Sprintf(":%d", port), sourceFiles)) } rootCmd := &cobra.Command{ Use: "gopherjs", Long: "GopherJS is a tool for compiling Go source code to JavaScript.", } rootCmd.AddCommand(cmdBuild, cmdGet, cmdInstall, cmdRun, cmdTest, cmdTool, cmdServe) rootCmd.Execute() }
func (s *Session) BuildPackage(pkg *PackageData) error { s.Packages[pkg.ImportPath] = pkg if pkg.ImportPath == "unsafe" { return nil } if pkg.PkgObj != "" { var fileInfo os.FileInfo gopherjsBinary, err := osext.Executable() if err == nil { fileInfo, err = os.Stat(gopherjsBinary) if err == nil { pkg.SrcModTime = fileInfo.ModTime() } } if err != nil { os.Stderr.WriteString("Could not get GopherJS binary's modification timestamp. Please report issue.\n") pkg.SrcModTime = time.Now() } for _, importedPkgPath := range pkg.Imports { ignored := true for _, pos := range pkg.ImportPos[importedPkgPath] { importFile := filepath.Base(pos.Filename) for _, file := range pkg.GoFiles { if importFile == file { ignored = false break } } if !ignored { break } } if importedPkgPath == "unsafe" || ignored { continue } _, err := s.ImportPackage(importedPkgPath) if err != nil { return err } impModeTime := s.Packages[importedPkgPath].SrcModTime if impModeTime.After(pkg.SrcModTime) { pkg.SrcModTime = impModeTime } } for _, name := range pkg.GoFiles { fileInfo, err := os.Stat(filepath.Join(pkg.Dir, name)) if err != nil { return err } if fileInfo.ModTime().After(pkg.SrcModTime) { pkg.SrcModTime = fileInfo.ModTime() } } pkgObjFileInfo, err := os.Stat(pkg.PkgObj) if err == nil && !pkg.SrcModTime.After(pkgObjFileInfo.ModTime()) { // package object is up to date, load from disk if library pkg.UpToDate = true if pkg.IsCommand() { return nil } objFile, err := ioutil.ReadFile(pkg.PkgObj) if err != nil { return err } pkg.Archive, err = compiler.UnmarshalArchive(pkg.PkgObj, pkg.ImportPath, objFile, s.ImportContext) if err != nil { return err } return nil } } fileSet := token.NewFileSet() files, err := Parse(pkg.Package, fileSet) if err != nil { return err } pkg.Archive, err = compiler.Compile(pkg.ImportPath, files, fileSet, s.ImportContext, s.options.Minify) if err != nil { return err } if s.options.Verbose { fmt.Println(pkg.ImportPath) } if pkg.PkgObj == "" || pkg.IsCommand() { return nil } if err := s.writeLibraryPackage(pkg, pkg.PkgObj); err != nil { if strings.HasPrefix(pkg.PkgObj, s.options.GOROOT) { // fall back to GOPATH if err := s.writeLibraryPackage(pkg, s.options.GOPATH+pkg.PkgObj[len(s.options.GOROOT):]); err != nil { return err } return nil } return err } return nil }
func main() { var location = dom.GetWindow().Top().Location() // We might be inside an iframe, but want to use the location of topmost window. codeReady := make(chan struct{}) // Used to synchronize when "code" value is ready. app := angularjs.NewModule("playground", nil, nil) app.NewController("PlaygroundCtrl", func(scope *angularjs.Scope) { if strings.HasPrefix(location.Hash, "#/") { id := location.Hash[2:] req := xhr.NewRequest("GET", "http://"+snippetStoreHost+"/p/"+id) req.ResponseType = xhr.ArrayBuffer go func() { err := req.Send(nil) if err != nil || req.Status != 200 { scope.Apply(func() { scope.Set("output", []Line{Line{"type": "err", "content": `failed to load snippet "` + id + `"`}}) }) return } data := js.Global.Get("Uint8Array").New(req.Response).Interface().([]byte) scope.Apply(func() { scope.Set("code", string(data)) close(codeReady) }) }() } else { scope.Set("code", "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gopherjs/gopherjs/js\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n\tjs.Global.Call(\"alert\", \"Hello, JavaScript\")\n\tprintln(\"Hello, JS console\")\n}\n") close(codeReady) } scope.Set("shareUrl", "") scope.Set("showShareUrl", false) packages := make(map[string]*compiler.Archive) var pkgsToLoad map[string]struct{} importContext := compiler.NewImportContext(func(path string) (*compiler.Archive, error) { if pkg, found := packages[path]; found { return pkg, nil } pkgsToLoad[path] = struct{}{} return &compiler.Archive{}, nil }) fileSet := token.NewFileSet() pkgsReceived := 0 setupEnvironment(scope) codeArea := angularjs.ElementById("code") codeArea.On("input", func(e *angularjs.Event) { scope.Set("showShareUrl", false) location.Hash = "" }) codeArea.On("keydown", func(e *angularjs.Event) { toInsert := "" switch e.KeyCode { case '\t': toInsert = "\t" case '\r': toInsert = "\n" start := codeArea.Prop("selectionStart").Int() code := scope.Get("code").String() i := strings.LastIndex(code[:start], "\n") + 1 for i < start { c := code[i] if c != ' ' && c != '\t' { break } toInsert += string(c) i++ } } if toInsert != "" { scope.Set("showShareUrl", false) location.Hash = "" start := codeArea.Prop("selectionStart").Int() end := codeArea.Prop("selectionEnd").Int() code := scope.Get("code").String() scope.Apply(func() { scope.Set("code", code[:start]+toInsert+code[end:]) }) codeArea.SetProp("selectionStart", start+len(toInsert)) codeArea.SetProp("selectionEnd", start+len(toInsert)) e.PreventDefault() } }) var run func(bool) run = func(loadOnly bool) { output = nil scope.Set("output", output) pkgsToLoad = make(map[string]struct{}) file, err := parser.ParseFile(fileSet, "prog.go", []byte(scope.Get("code").String()), parser.ParseComments) if err != nil { if list, ok := err.(scanner.ErrorList); ok { for _, entry := range list { output = append(output, Line{"type": "err", "content": entry.Error()}) } scope.Set("output", output) return } scope.Set("output", []Line{Line{"type": "err", "content": err.Error()}}) return } mainPkg, err := compiler.Compile("main", []*ast.File{file}, fileSet, importContext, false) packages["main"] = mainPkg if err != nil && len(pkgsToLoad) == 0 { if list, ok := err.(compiler.ErrorList); ok { var output []Line for _, entry := range list { output = append(output, Line{"type": "err", "content": entry.Error()}) } scope.Set("output", output) return } scope.Set("output", []Line{Line{"type": "err", "content": err.Error()}}) return } var allPkgs []*compiler.Archive if len(pkgsToLoad) == 0 { allPkgs, _ = compiler.ImportDependencies(mainPkg, importContext.Import) } if len(pkgsToLoad) != 0 { pkgsReceived = 0 for path := range pkgsToLoad { req := xhr.NewRequest("GET", "pkg/"+path+".a.js") req.ResponseType = xhr.ArrayBuffer go func(path string) { err := req.Send(nil) if err != nil || req.Status != 200 { scope.Apply(func() { scope.Set("output", []Line{Line{"type": "err", "content": `failed to load package "` + path + `"`}}) }) return } data := js.Global.Get("Uint8Array").New(req.Response).Interface().([]byte) packages[path], err = compiler.ReadArchive(path+".a", path, bytes.NewReader(data), importContext.Packages) if err != nil { scope.Apply(func() { scope.Set("output", []Line{Line{"type": "err", "content": err.Error()}}) }) return } pkgsReceived++ if pkgsReceived == len(pkgsToLoad) { run(loadOnly) } }(path) } return } if loadOnly { return } jsCode := bytes.NewBuffer(nil) jsCode.WriteString("try{\n") compiler.WriteProgramCode(allPkgs, &compiler.SourceMapFilter{Writer: jsCode}) jsCode.WriteString("} catch (err) {\ngoPanicHandler(err.message);\n}\n") js.Global.Set("$checkForDeadlock", true) js.Global.Call("eval", js.InternalObject(jsCode.String())) } scope.Set("run", run) go func() { <-codeReady // Wait for "code" value to be ready. run(true) }() scope.Set("format", func() { out, err := format.Source([]byte(scope.Get("code").String())) if err != nil { scope.Set("output", []Line{Line{"type": "err", "content": err.Error()}}) return } scope.Set("code", string(out)) scope.Set("output", []Line{}) }) scope.Set("share", func() { req := xhr.NewRequest("POST", "http://"+snippetStoreHost+"/share") req.ResponseType = xhr.ArrayBuffer go func() { err := req.Send([]byte(scope.Get("code").String())) // Send as binary. if err != nil || req.Status != 200 { scope.Apply(func() { scope.Set("output", []Line{Line{"type": "err", "content": `failed to share snippet`}}) }) return } data := js.Global.Get("Uint8Array").New(req.Response).Interface().([]byte) scope.Apply(func() { id := string(data) location.Hash = "#/" + id scope.Set("shareUrl", location.String()) scope.Set("showShareUrl", true) // TODO: Do this better using AngularJS. // Perhaps using http://stackoverflow.com/questions/14833326/how-to-set-focus-on-input-field/18295416. go func() { time.Sleep(time.Millisecond) dom.GetWindow().Document().GetElementByID("share-url").(*dom.HTMLInputElement).Select() }() }) }() }) // Start watching for hashchange events, and reload snippet if it happens. dom.GetWindow().Top().AddEventListener("hashchange", false, func(event dom.Event) { event.PreventDefault() if strings.HasPrefix(location.Hash, "#/") { id := location.Hash[2:] req := xhr.NewRequest("GET", "http://"+snippetStoreHost+"/p/"+id) req.ResponseType = xhr.ArrayBuffer go func() { err := req.Send(nil) if err != nil || req.Status != 200 { scope.Apply(func() { scope.Set("output", []Line{Line{"type": "err", "content": `failed to load snippet "` + id + `"`}}) }) return } data := js.Global.Get("Uint8Array").New(req.Response).Interface().([]byte) scope.Apply(func() { scope.Set("code", string(data)) }) }() } }) }) }
func control(scope *angularjs.Scope) { scope.Set("code", initCode) // scope.Set("showGenerated", false) // scope.Set("generated", `(generated code will be shown here after clicking "Run")`) packages := make(map[string]*compiler.Archive) var pkgsToLoad []string importContext := compiler.NewImportContext( func(path string) (*compiler.Archive, error) { if pkg, found := packages[path]; found { return pkg, nil } pkgsToLoad = append(pkgsToLoad, path) return &compiler.Archive{}, nil }, ) fileSet := token.NewFileSet() pkgsReceived := 0 setupEnvironment(scope) var run func(bool) run = func(loadOnly bool) { output = nil scope.Set("output", output) pkgsToLoad = nil file, err := parser.ParseFile(fileSet, "prog.go", getCode(), parser.ParseComments, ) if err != nil { if list, ok := err.(scanner.ErrorList); ok { for _, entry := range list { output = append(output, errErrLine(entry)) } scope.Set("output", output) return } scope.Set("output", []Line{errErrLine(err)}) return } mainPkg, err := compiler.Compile("main", []*ast.File{file}, fileSet, importContext, false, ) packages["main"] = mainPkg if err != nil && len(pkgsToLoad) == 0 { if list, ok := err.(compiler.ErrorList); ok { output := make([]Line, 0) for _, entry := range list { output = append(output, errErrLine(entry)) } scope.Set("output", output) return } scope.Set("output", []Line{errErrLine(err)}) return } var allPkgs []*compiler.Archive if len(pkgsToLoad) == 0 { for _, depPath := range mainPkg.Dependencies { dep, _ := importContext.Import(string(depPath)) allPkgs = append(allPkgs, dep) } allPkgs = append(allPkgs, mainPkg) } if len(pkgsToLoad) != 0 { pkgsReceived = 0 for _, p := range pkgsToLoad { path := p req := js.Global.Get("XMLHttpRequest").New() req.Call("open", "GET", "pkg/"+path+".a", true) req.Set("responseType", "arraybuffer") req.Set("onload", func() { if req.Get("status").Int() != 200 { f := func() { emsg := fmt.Sprintf("cannot load package \"%s\"", path) scope.Set("output", []Line{errLine(emsg)}) } scope.Apply(f) return } data := js.Global.Get("Uint8Array").New(req.Get("response")).Interface().([]byte) packages[path], err = compiler.UnmarshalArchive( path+".a", path, []byte(data), importContext, ) if err != nil { scope.Apply(func() { scope.Set("output", []Line{errErrLine(err)}) }) return } pkgsReceived++ if pkgsReceived == len(pkgsToLoad) { run(loadOnly) } }) req.Call("send") } return } if loadOnly { return } mainPkgCode := bytes.NewBuffer(nil) compiler.WritePkgCode(packages["main"], false, &compiler.SourceMapFilter{Writer: mainPkgCode}, ) // scope.Set("generated", mainPkgCode.String()) jsCode := bytes.NewBuffer(nil) jsCode.WriteString("try{\n") compiler.WriteProgramCode(allPkgs, importContext, &compiler.SourceMapFilter{Writer: jsCode}, ) jsCode.WriteString("} catch (err) {\ngoPanicHandler(err.message);\n}\n") js.Global.Call("eval", js.InternalObject(jsCode.String())) } scope.Set("run", run) run(true) scope.Set("format", func() { out, err := format.Source(getCode()) if err != nil { scope.Set("output", []Line{errErrLine(err)}) return } setCode(string(out)) scope.Set("output", []Line{}) }) }