Skip to content

support being built and used with newer versions of Go #966

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,22 @@ Give GopherJS a try on the [GopherJS Playground](http://gopherjs.github.io/playg
Nearly everything, including Goroutines ([compatibility table](https://github.com/gopherjs/gopherjs/blob/master/doc/packages.md)). Performance is quite good in most cases, see [HTML5 game engine benchmark](https://ajhager.github.io/engi/demos/botmark.html). Cgo is not supported.

### Installation and Usage
GopherJS requires Go 1.12 or newer.

Get or update GopherJS and dependencies with:

```
go get -u github.com/gopherjs/gopherjs
```

If your local Go distribution as reported by `go version` is newer than Go 1.12, then you need to set the `GOPHERJS_GOROOT` environment variable to a directory that contains a Go 1.12 distribution. For example:

```
go get golang.org/dl/go1.12.16
go1.12.16 download
export GOPHERJS_GOROOT="$(go1.12.16 env GOROOT)" # Also add this line to your .profile or equivalent.
```

Now you can use `gopherjs build [package]`, `gopherjs build [files]` or `gopherjs install [package]` which behave similar to the `go` tool. For `main` packages, these commands create a `.js` file and `.js.map` source map in the current directory or in `$GOPATH/bin`. The generated JavaScript file can be used as usual in a website. Use `gopherjs help [command]` to get a list of possible command line flags, e.g. for minification and automatically watching for changes.

`gopherjs` uses your platform's default `GOOS` value when generating code. Supported `GOOS` values are: `linux`, `darwin`. If you're on a different platform (e.g., Windows or FreeBSD), you'll need to set the `GOOS` environment variable to a supported value. For example, `GOOS=linux gopherjs build [package]`.
Expand All @@ -46,6 +56,15 @@ Refreshing in the browser will rebuild the served files if needed. Compilation e

If you include an argument, it will be the root from which everything is served. For example, if you run `gopherjs serve github.com/user/project` then the generated JavaScript for the package github.com/user/project/mypkg will be served at http://localhost:8080/mypkg/mypkg.js.

#### Environment Variables

There is one GopherJS-specific environment variable:

```
GOPHERJS_GOROOT - if set, GopherJS uses this value as the default GOROOT value,
instead of using the system GOROOT as the default GOROOT value
```

### Performance Tips

- Use the `-m` command line flag to generate minified code.
Expand Down
36 changes: 27 additions & 9 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ import (
"golang.org/x/tools/go/buildutil"
)

// DefaultGOROOT is the default GOROOT value for builds.
//
// It uses the GOPHERJS_GOROOT environment variable if it is set,
// or else the default GOROOT value of the system Go distrubtion.
var DefaultGOROOT = func() string {
if goroot, ok := os.LookupEnv("GOPHERJS_GOROOT"); ok {
// GopherJS-specific GOROOT value takes precedence.
return goroot
}
// The usual default GOROOT.
return build.Default.GOROOT
}()

type ImportCError struct {
pkgPath string
}
Expand All @@ -42,9 +55,9 @@ func (e *ImportCError) Error() string {
// Core GopherJS packages (i.e., "github.com/gopherjs/gopherjs/js", "github.com/gopherjs/gopherjs/nosync")
// are loaded from gopherjspkg.FS virtual filesystem rather than GOPATH.
func NewBuildContext(installSuffix string, buildTags []string) *build.Context {
gopherjsRoot := filepath.Join(build.Default.GOROOT, "src", "github.com", "gopherjs", "gopherjs")
gopherjsRoot := filepath.Join(DefaultGOROOT, "src", "github.com", "gopherjs", "gopherjs")
return &build.Context{
GOROOT: build.Default.GOROOT,
GOROOT: DefaultGOROOT,
GOPATH: build.Default.GOPATH,
GOOS: build.Default.GOOS,
GOARCH: "js",
Expand Down Expand Up @@ -92,7 +105,7 @@ func NewBuildContext(installSuffix string, buildTags []string) *build.Context {
// For files in "$GOROOT/src/github.com/gopherjs/gopherjs" directory,
// gopherjspkg.FS is consulted first.
func statFile(path string) (os.FileInfo, error) {
gopherjsRoot := filepath.Join(build.Default.GOROOT, "src", "github.com", "gopherjs", "gopherjs")
gopherjsRoot := filepath.Join(DefaultGOROOT, "src", "github.com", "gopherjs", "gopherjs")
if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) {
path = filepath.ToSlash(path[len(gopherjsRoot):])
if fi, err := vfsutil.Stat(gopherjspkg.FS, path); err == nil {
Expand Down Expand Up @@ -183,10 +196,10 @@ func importWithSrcDir(bctx build.Context, path string, srcDir string, mode build
pkg.PkgObj = filepath.Join(pkg.BinDir, filepath.Base(pkg.ImportPath)+".js")
}

if _, err := os.Stat(pkg.PkgObj); os.IsNotExist(err) && strings.HasPrefix(pkg.PkgObj, build.Default.GOROOT) {
if _, err := os.Stat(pkg.PkgObj); os.IsNotExist(err) && strings.HasPrefix(pkg.PkgObj, DefaultGOROOT) {
// fall back to GOPATH
firstGopathWorkspace := filepath.SplitList(build.Default.GOPATH)[0] // TODO: Need to check inside all GOPATH workspaces.
gopathPkgObj := filepath.Join(firstGopathWorkspace, pkg.PkgObj[len(build.Default.GOROOT):])
gopathPkgObj := filepath.Join(firstGopathWorkspace, pkg.PkgObj[len(DefaultGOROOT):])
if _, err := os.Stat(gopathPkgObj); err == nil {
pkg.PkgObj = gopathPkgObj
}
Expand Down Expand Up @@ -476,15 +489,20 @@ type Session struct {
Watcher *fsnotify.Watcher
}

func NewSession(options *Options) *Session {
func NewSession(options *Options) (*Session, error) {
if options.GOROOT == "" {
options.GOROOT = build.Default.GOROOT
options.GOROOT = DefaultGOROOT
}
if options.GOPATH == "" {
options.GOPATH = build.Default.GOPATH
}
options.Verbose = options.Verbose || options.Watch

// Go distribution version check.
if err := compiler.CheckGoVersion(options.GOROOT); err != nil {
return nil, err
}

s := &Session{
options: options,
Archives: make(map[string]*compiler.Archive),
Expand All @@ -501,10 +519,10 @@ func NewSession(options *Options) *Session {
var err error
s.Watcher, err = fsnotify.NewWatcher()
if err != nil {
panic(err)
return nil, err
}
}
return s
return s, nil
}

// BuildContext returns the session's build context.
Expand Down
1 change: 0 additions & 1 deletion compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (

var sizes32 = &types.StdSizes{WordSize: 4, MaxAlign: 8}
var reservedKeywords = make(map[string]bool)
var _ = ___GOPHERJS_REQUIRES_GO_VERSION_1_12___ // Compile error on other Go versions, because they're not supported.

func init() {
for _, keyword := range []string{"abstract", "arguments", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "undefined", "var", "void", "volatile", "while", "with", "yield"} {
Expand Down
6 changes: 3 additions & 3 deletions compiler/natives/fs_vfsdata.go

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions compiler/natives/src/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ func GOROOT() string {
if process == js.Undefined {
return "/"
}
goroot := process.Get("env").Get("GOROOT")
if goroot != js.Undefined {
return goroot.String()
if v := process.Get("env").Get("GOPHERJS_GOROOT"); v != js.Undefined {
// GopherJS-specific GOROOT value takes precedence.
return v.String()
} else if v := process.Get("env").Get("GOROOT"); v != js.Undefined {
return v.String()
}
// sys.DefaultGoroot is now gone, can't use it as fallback anymore.
// TODO: See if a better solution is needed.
Expand Down
24 changes: 20 additions & 4 deletions compiler/version_check.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
// +build go1.12
// +build !go1.13

package compiler

const ___GOPHERJS_REQUIRES_GO_VERSION_1_12___ = true
import (
"bytes"
"fmt"
"io/ioutil"
"path/filepath"
)

// Version is the GopherJS compiler version string.
const Version = "1.12-2"

// CheckGoVersion checks the version of the Go distribution
// at goroot, and reports an error if it's not compatible
// with this version of the GopherJS compiler.
func CheckGoVersion(goroot string) error {
v, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION"))
if err != nil {
return fmt.Errorf("GopherJS %s requires a Go 1.12.x distribution, but failed to read its VERSION file: %v", Version, err)
}
if !bytes.HasPrefix(v, []byte("go1.12")) { // TODO(dmitshur): Change this before Go 1.120 comes out.
return fmt.Errorf("GopherJS %s requires a Go 1.12.x distribution, but found version %s", Version, v)
}
return nil
}
12 changes: 3 additions & 9 deletions tests/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import (
"strings"
"time"
"unicode"

gbuild "github.com/gopherjs/gopherjs/build"
)

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -186,7 +188,7 @@ func main() {
flag.Parse()

// GOPHERJS.
err := os.Chdir(filepath.Join(runtime.GOROOT(), "test"))
err := os.Chdir(filepath.Join(gbuild.DefaultGOROOT, "test"))
if err != nil {
log.Fatalln(err)
}
Expand Down Expand Up @@ -311,14 +313,6 @@ func main() {
}
}

func toolPath(name string) string {
p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name)
if _, err := os.Stat(p); err != nil {
log.Fatalf("didn't find binary at %s", p)
}
return p
}

func shardMatch(name string) bool {
if *shards == 0 {
return true
Expand Down
42 changes: 34 additions & 8 deletions tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,13 @@ func main() {
cmdBuild.Run = func(cmd *cobra.Command, args []string) {
options.BuildTags = strings.Fields(tags)
for {
s := gbuild.NewSession(options)
s, err := gbuild.NewSession(options)
if err != nil {
options.PrintError("%s\n", err)
os.Exit(1)
}

err := func() error {
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 {
Expand Down Expand Up @@ -173,9 +177,13 @@ func main() {
cmdInstall.Run = func(cmd *cobra.Command, args []string) {
options.BuildTags = strings.Fields(tags)
for {
s := gbuild.NewSession(options)
s, err := gbuild.NewSession(options)
if err != nil {
options.PrintError("%s\n", err)
os.Exit(1)
}

err := func() error {
err = func() error {
// Expand import path patterns.
patternContext := gbuild.NewBuildContext("", options.BuildTags)
pkgs := (&gotool.Context{BuildContext: *patternContext}).ImportPaths(args)
Expand Down Expand Up @@ -274,7 +282,10 @@ func main() {
os.Remove(tempfile.Name())
os.Remove(tempfile.Name() + ".map")
}()
s := gbuild.NewSession(options)
s, err := gbuild.NewSession(options)
if err != nil {
return err
}
if err := s.BuildFiles(args[:lastSourceArg], tempfile.Name(), currentDirectory); err != nil {
return err
}
Expand Down Expand Up @@ -330,7 +341,10 @@ func main() {
fmt.Printf("? \t%s\t[no test files]\n", pkg.ImportPath)
continue
}
s := gbuild.NewSession(options)
s, err := gbuild.NewSession(options)
if err != nil {
return err
}

tests := &testFuncs{BuildContext: s.BuildContext(), Package: pkg.Package}
collectTests := func(testPkg *gbuild.PackageData, testPkgName string, needVar *bool) error {
Expand Down Expand Up @@ -481,7 +495,7 @@ func main() {
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)
dirs := append(filepath.SplitList(build.Default.GOPATH), gbuild.DefaultGOROOT)
var root string

if len(args) > 1 {
Expand All @@ -493,6 +507,13 @@ func main() {
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,
Expand Down Expand Up @@ -573,8 +594,13 @@ func (fs serveCommandFileSystem) Open(requestName string) (http.File, error) {
isIndex := file == "index.html"

if isPkg || isMap || isIndex {
// 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 we're going to be serving our special files, make sure there's a Go command in this folder.
s := gbuild.NewSession(fs.options)
pkg, err := gbuild.Import(path.Dir(name), 0, s.InstallSuffix(), fs.options.BuildTags)
if err != nil || pkg.Name != "main" {
isPkg = false
Expand Down