Skip to content
Permalink
Browse files

support being built and used with newer versions of Go

This change removes the build-time check that GopherJS was built
with Go version 1.12, and replaces it with a runtime check instead.

Add and document a new 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

The main reason that GopherJS required Go 1.12 to be built in the
past was as a way of ensuring that the Go 1.12 standard library
code is available on disk, which GopherJS 1.12-2 needs to build
Go code. Now that the Go distribution can provided to GopherJS
with convenience via the GOPHERJS_GOROOT environment variable,
we can allow GopherJS to be built with a newer version of Go,
as long as a Go 1.12 distribution is provided via GOPHERJS_GOROOT.

Update GopherJS installation instructions to support being installed
with Go 1.12 and newer versions of Go.

The Go distribution version check is added to build.NewSession.
That check may fail, and so it was necessary to add an error return
value to the signature of build.NewSession. The build API is deemed
semi-internal, so a breaking API change is unfortunate but acceptable.

Regenerate ./compiler/natives with:

	go generate ./...

Fixes #941.

GitHub-Pull-Request: #966
  • Loading branch information
dmitshur committed Feb 9, 2020
1 parent c273dfd commit f9cef593def5494318cb50c86cddd459e492531f
Showing with 111 additions and 37 deletions.
  1. +19 −0 README.md
  2. +27 −9 build/build.go
  3. +0 −1 compiler/compiler.go
  4. +3 −3 compiler/natives/fs_vfsdata.go
  5. +5 −3 compiler/natives/src/runtime/runtime.go
  6. +20 −4 compiler/version_check.go
  7. +3 −9 tests/run.go
  8. +34 −8 tool.go
@@ -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]`.
@@ -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.
@@ -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
}
@@ -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",
@@ -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 {
@@ -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
}
@@ -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),
@@ -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.
@@ -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"} {

Large diffs are not rendered by default.

@@ -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.
@@ -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
}
@@ -36,6 +36,8 @@ import (
"strings"
"time"
"unicode"

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

// -----------------------------------------------------------------------------
@@ -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)
}
@@ -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
42 tool.go
@@ -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 {
@@ -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)
@@ -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
}
@@ -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 {
@@ -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 {
@@ -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,
@@ -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

0 comments on commit f9cef59

Please sign in to comment.
You can’t perform that action at this time.