Skip to content

Commit f9cef59

Browse files
authored
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
1 parent c273dfd commit f9cef59

File tree

8 files changed

+111
-37
lines changed

8 files changed

+111
-37
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,22 @@ Give GopherJS a try on the [GopherJS Playground](http://gopherjs.github.io/playg
1414
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.
1515

1616
### Installation and Usage
17+
GopherJS requires Go 1.12 or newer.
18+
1719
Get or update GopherJS and dependencies with:
1820

1921
```
2022
go get -u github.com/gopherjs/gopherjs
2123
```
2224

25+
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:
26+
27+
```
28+
go get golang.org/dl/go1.12.16
29+
go1.12.16 download
30+
export GOPHERJS_GOROOT="$(go1.12.16 env GOROOT)" # Also add this line to your .profile or equivalent.
31+
```
32+
2333
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.
2434

2535
`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
4656

4757
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.
4858

59+
#### Environment Variables
60+
61+
There is one GopherJS-specific environment variable:
62+
63+
```
64+
GOPHERJS_GOROOT - if set, GopherJS uses this value as the default GOROOT value,
65+
instead of using the system GOROOT as the default GOROOT value
66+
```
67+
4968
### Performance Tips
5069

5170
- Use the `-m` command line flag to generate minified code.

build/build.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ import (
2828
"golang.org/x/tools/go/buildutil"
2929
)
3030

31+
// DefaultGOROOT is the default GOROOT value for builds.
32+
//
33+
// It uses the GOPHERJS_GOROOT environment variable if it is set,
34+
// or else the default GOROOT value of the system Go distrubtion.
35+
var DefaultGOROOT = func() string {
36+
if goroot, ok := os.LookupEnv("GOPHERJS_GOROOT"); ok {
37+
// GopherJS-specific GOROOT value takes precedence.
38+
return goroot
39+
}
40+
// The usual default GOROOT.
41+
return build.Default.GOROOT
42+
}()
43+
3144
type ImportCError struct {
3245
pkgPath string
3346
}
@@ -42,9 +55,9 @@ func (e *ImportCError) Error() string {
4255
// Core GopherJS packages (i.e., "github.com/gopherjs/gopherjs/js", "github.com/gopherjs/gopherjs/nosync")
4356
// are loaded from gopherjspkg.FS virtual filesystem rather than GOPATH.
4457
func NewBuildContext(installSuffix string, buildTags []string) *build.Context {
45-
gopherjsRoot := filepath.Join(build.Default.GOROOT, "src", "github.com", "gopherjs", "gopherjs")
58+
gopherjsRoot := filepath.Join(DefaultGOROOT, "src", "github.com", "gopherjs", "gopherjs")
4659
return &build.Context{
47-
GOROOT: build.Default.GOROOT,
60+
GOROOT: DefaultGOROOT,
4861
GOPATH: build.Default.GOPATH,
4962
GOOS: build.Default.GOOS,
5063
GOARCH: "js",
@@ -92,7 +105,7 @@ func NewBuildContext(installSuffix string, buildTags []string) *build.Context {
92105
// For files in "$GOROOT/src/github.com/gopherjs/gopherjs" directory,
93106
// gopherjspkg.FS is consulted first.
94107
func statFile(path string) (os.FileInfo, error) {
95-
gopherjsRoot := filepath.Join(build.Default.GOROOT, "src", "github.com", "gopherjs", "gopherjs")
108+
gopherjsRoot := filepath.Join(DefaultGOROOT, "src", "github.com", "gopherjs", "gopherjs")
96109
if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) {
97110
path = filepath.ToSlash(path[len(gopherjsRoot):])
98111
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
183196
pkg.PkgObj = filepath.Join(pkg.BinDir, filepath.Base(pkg.ImportPath)+".js")
184197
}
185198

186-
if _, err := os.Stat(pkg.PkgObj); os.IsNotExist(err) && strings.HasPrefix(pkg.PkgObj, build.Default.GOROOT) {
199+
if _, err := os.Stat(pkg.PkgObj); os.IsNotExist(err) && strings.HasPrefix(pkg.PkgObj, DefaultGOROOT) {
187200
// fall back to GOPATH
188201
firstGopathWorkspace := filepath.SplitList(build.Default.GOPATH)[0] // TODO: Need to check inside all GOPATH workspaces.
189-
gopathPkgObj := filepath.Join(firstGopathWorkspace, pkg.PkgObj[len(build.Default.GOROOT):])
202+
gopathPkgObj := filepath.Join(firstGopathWorkspace, pkg.PkgObj[len(DefaultGOROOT):])
190203
if _, err := os.Stat(gopathPkgObj); err == nil {
191204
pkg.PkgObj = gopathPkgObj
192205
}
@@ -476,15 +489,20 @@ type Session struct {
476489
Watcher *fsnotify.Watcher
477490
}
478491

479-
func NewSession(options *Options) *Session {
492+
func NewSession(options *Options) (*Session, error) {
480493
if options.GOROOT == "" {
481-
options.GOROOT = build.Default.GOROOT
494+
options.GOROOT = DefaultGOROOT
482495
}
483496
if options.GOPATH == "" {
484497
options.GOPATH = build.Default.GOPATH
485498
}
486499
options.Verbose = options.Verbose || options.Watch
487500

501+
// Go distribution version check.
502+
if err := compiler.CheckGoVersion(options.GOROOT); err != nil {
503+
return nil, err
504+
}
505+
488506
s := &Session{
489507
options: options,
490508
Archives: make(map[string]*compiler.Archive),
@@ -501,10 +519,10 @@ func NewSession(options *Options) *Session {
501519
var err error
502520
s.Watcher, err = fsnotify.NewWatcher()
503521
if err != nil {
504-
panic(err)
522+
return nil, err
505523
}
506524
}
507-
return s
525+
return s, nil
508526
}
509527

510528
// BuildContext returns the session's build context.

compiler/compiler.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717

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

2221
func init() {
2322
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"} {

compiler/natives/fs_vfsdata.go

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/natives/src/runtime/runtime.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ func GOROOT() string {
4343
if process == js.Undefined {
4444
return "/"
4545
}
46-
goroot := process.Get("env").Get("GOROOT")
47-
if goroot != js.Undefined {
48-
return goroot.String()
46+
if v := process.Get("env").Get("GOPHERJS_GOROOT"); v != js.Undefined {
47+
// GopherJS-specific GOROOT value takes precedence.
48+
return v.String()
49+
} else if v := process.Get("env").Get("GOROOT"); v != js.Undefined {
50+
return v.String()
4951
}
5052
// sys.DefaultGoroot is now gone, can't use it as fallback anymore.
5153
// TODO: See if a better solution is needed.

compiler/version_check.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
1-
// +build go1.12
2-
// +build !go1.13
3-
41
package compiler
52

6-
const ___GOPHERJS_REQUIRES_GO_VERSION_1_12___ = true
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io/ioutil"
7+
"path/filepath"
8+
)
79

810
// Version is the GopherJS compiler version string.
911
const Version = "1.12-2"
12+
13+
// CheckGoVersion checks the version of the Go distribution
14+
// at goroot, and reports an error if it's not compatible
15+
// with this version of the GopherJS compiler.
16+
func CheckGoVersion(goroot string) error {
17+
v, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION"))
18+
if err != nil {
19+
return fmt.Errorf("GopherJS %s requires a Go 1.12.x distribution, but failed to read its VERSION file: %v", Version, err)
20+
}
21+
if !bytes.HasPrefix(v, []byte("go1.12")) { // TODO(dmitshur): Change this before Go 1.120 comes out.
22+
return fmt.Errorf("GopherJS %s requires a Go 1.12.x distribution, but found version %s", Version, v)
23+
}
24+
return nil
25+
}

tests/run.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import (
3636
"strings"
3737
"time"
3838
"unicode"
39+
40+
gbuild "github.com/gopherjs/gopherjs/build"
3941
)
4042

4143
// -----------------------------------------------------------------------------
@@ -186,7 +188,7 @@ func main() {
186188
flag.Parse()
187189

188190
// GOPHERJS.
189-
err := os.Chdir(filepath.Join(runtime.GOROOT(), "test"))
191+
err := os.Chdir(filepath.Join(gbuild.DefaultGOROOT, "test"))
190192
if err != nil {
191193
log.Fatalln(err)
192194
}
@@ -311,14 +313,6 @@ func main() {
311313
}
312314
}
313315

314-
func toolPath(name string) string {
315-
p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name)
316-
if _, err := os.Stat(p); err != nil {
317-
log.Fatalf("didn't find binary at %s", p)
318-
}
319-
return p
320-
}
321-
322316
func shardMatch(name string) bool {
323317
if *shards == 0 {
324318
return true

tool.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,13 @@ func main() {
9494
cmdBuild.Run = func(cmd *cobra.Command, args []string) {
9595
options.BuildTags = strings.Fields(tags)
9696
for {
97-
s := gbuild.NewSession(options)
97+
s, err := gbuild.NewSession(options)
98+
if err != nil {
99+
options.PrintError("%s\n", err)
100+
os.Exit(1)
101+
}
98102

99-
err := func() error {
103+
err = func() error {
100104
// Handle "gopherjs build [files]" ad-hoc package mode.
101105
if len(args) > 0 && (strings.HasSuffix(args[0], ".go") || strings.HasSuffix(args[0], ".inc.js")) {
102106
for _, arg := range args {
@@ -173,9 +177,13 @@ func main() {
173177
cmdInstall.Run = func(cmd *cobra.Command, args []string) {
174178
options.BuildTags = strings.Fields(tags)
175179
for {
176-
s := gbuild.NewSession(options)
180+
s, err := gbuild.NewSession(options)
181+
if err != nil {
182+
options.PrintError("%s\n", err)
183+
os.Exit(1)
184+
}
177185

178-
err := func() error {
186+
err = func() error {
179187
// Expand import path patterns.
180188
patternContext := gbuild.NewBuildContext("", options.BuildTags)
181189
pkgs := (&gotool.Context{BuildContext: *patternContext}).ImportPaths(args)
@@ -274,7 +282,10 @@ func main() {
274282
os.Remove(tempfile.Name())
275283
os.Remove(tempfile.Name() + ".map")
276284
}()
277-
s := gbuild.NewSession(options)
285+
s, err := gbuild.NewSession(options)
286+
if err != nil {
287+
return err
288+
}
278289
if err := s.BuildFiles(args[:lastSourceArg], tempfile.Name(), currentDirectory); err != nil {
279290
return err
280291
}
@@ -330,7 +341,10 @@ func main() {
330341
fmt.Printf("? \t%s\t[no test files]\n", pkg.ImportPath)
331342
continue
332343
}
333-
s := gbuild.NewSession(options)
344+
s, err := gbuild.NewSession(options)
345+
if err != nil {
346+
return err
347+
}
334348

335349
tests := &testFuncs{BuildContext: s.BuildContext(), Package: pkg.Package}
336350
collectTests := func(testPkg *gbuild.PackageData, testPkgName string, needVar *bool) error {
@@ -481,7 +495,7 @@ func main() {
481495
cmdServe.Flags().StringVarP(&addr, "http", "", ":8080", "HTTP bind address to serve")
482496
cmdServe.Run = func(cmd *cobra.Command, args []string) {
483497
options.BuildTags = strings.Fields(tags)
484-
dirs := append(filepath.SplitList(build.Default.GOPATH), build.Default.GOROOT)
498+
dirs := append(filepath.SplitList(build.Default.GOPATH), gbuild.DefaultGOROOT)
485499
var root string
486500

487501
if len(args) > 1 {
@@ -493,6 +507,13 @@ func main() {
493507
root = args[0]
494508
}
495509

510+
// Create a new session eagerly to check if it fails, and report the error right away.
511+
// Otherwise users will see it only after trying to serve a package, which is a bad experience.
512+
_, err := gbuild.NewSession(options)
513+
if err != nil {
514+
options.PrintError("%s\n", err)
515+
os.Exit(1)
516+
}
496517
sourceFiles := http.FileServer(serveCommandFileSystem{
497518
serveRoot: root,
498519
options: options,
@@ -573,8 +594,13 @@ func (fs serveCommandFileSystem) Open(requestName string) (http.File, error) {
573594
isIndex := file == "index.html"
574595

575596
if isPkg || isMap || isIndex {
597+
// Create a new session to pick up changes to source code on disk.
598+
// TODO(dmitshur): might be possible to get a single session to detect changes to source code on disk
599+
s, err := gbuild.NewSession(fs.options)
600+
if err != nil {
601+
return nil, err
602+
}
576603
// If we're going to be serving our special files, make sure there's a Go command in this folder.
577-
s := gbuild.NewSession(fs.options)
578604
pkg, err := gbuild.Import(path.Dir(name), 0, s.InstallSuffix(), fs.options.BuildTags)
579605
if err != nil || pkg.Name != "main" {
580606
isPkg = false

0 commit comments

Comments
 (0)