Skip to content

Commit 24a7748

Browse files
committed
Add README, samples.
1 parent 4507a42 commit 24a7748

File tree

4 files changed

+212
-0
lines changed

4 files changed

+212
-0
lines changed

README.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# cmd-go-js
2+
3+
cmd-go-js is a "feature branch" of cmd/go command with experimental changes. The goal was to explore adding support for additional Go compilers and environments by making use of existing -compiler and -exec flags:
4+
5+
> -compiler name - name of compiler to use, as in runtime.Compiler (gccgo or gc).
6+
>
7+
> -exec xprog - Run the test binary using xprog. The behavior is the same as in 'go run'. See 'go help run' for details.
8+
9+
Specifically, I wanted to try adding support for [GopherJS compiler](https://github.com/gopherjs/gopherjs) which targets `GOARCH=js` architecture. It compiles Go code to JavaScript which can be executed by a JavaScript engine (such as V8 JavaScript Engine, Node.js, or any browser with JavaScript support).
10+
11+
# Results
12+
13+
![](Screenshot.png)
14+
15+
#### go build
16+
17+
You can now use `go` to build for `GOARCH=js` architecture! That is the only architecture that all browsers can execute natively, without any plugins.
18+
19+
```bash
20+
# Normal build for GOARCH=amd64.
21+
$ go build ./samples/hello-world
22+
# Note that above implicitly means:
23+
# GOARCH=amd64 go build -compiler=gc ./samples/hello-world
24+
$ ./hello-world
25+
Hello brave new world! It is working on go1.5.3 darwin/amd64!
26+
27+
# Newly supported build for GOARCH=js.
28+
$ GOARCH=js go build -compiler=gopherjs ./samples/hello-world
29+
$ node ./hello-world
30+
Hello brave new world! It is working on go1.5.3 darwin/js! That means you can execute it in browsers.
31+
```
32+
33+
#### go run
34+
35+
`go run` also works. You can use the `-exec` flag to have `node` execute the compiled JavaScript output.
36+
37+
```bash
38+
$ go run ./samples/hello-world/main.go
39+
Hello brave new world! It is working on go1.5.3 darwin/amd64!
40+
41+
$ GOARCH=js go run -compiler=gopherjs -exec=node ./samples/hello-world/main.go
42+
Hello brave new world! It is working on go1.5.3 darwin/js! That means you can execute it in browsers.
43+
```
44+
45+
From https://golang.org/cmd/go/#hdr-Compile_and_run_Go_program:
46+
47+
> If the -exec flag is not given, GOOS or GOARCH is different from the system default, and a program named go_$GOOS_$GOARCH_exec can be found on the current search path, 'go run' invokes the binary using that program, for example 'go_nacl_386_exec a.out arguments...'. This allows execution of cross-compiled programs when a simulator or other execution method is available.
48+
49+
That means if you create a symlink to `node` binary named `go_darwin_js_exec`, then you can just:
50+
51+
```bash
52+
$ GOARCH=js go run -compiler=gopherjs ./samples/hello-world/main.go
53+
Hello brave new world! It is working on go1.5.3 darwin/js! That means you can execute it in browsers.
54+
```
55+
56+
# Implementation details, lessons learned, other notes
57+
58+
### go test
59+
60+
`go test` can be made to support `-compiler=gopherjs` with `-exec=node`, but additional work needs to be done; it currently doesn't work.
61+
62+
```bash
63+
$ GOARCH=js go test -v -compiler=gopherjs -exec=node ./samples/hello-world
64+
=== RUN TestBasic
65+
--- PASS: TestBasic (0.00s)
66+
PASS
67+
ok github.com/gophergala2016/cmd-go-js/samples/hello-world 0.015s
68+
```
69+
70+
That means all of go support for [testing](https://godoc.org/testing) would be availble. Including tests, executable examples, and benchmarks.
71+
72+
### Toolchains
73+
74+
`cmd/go` defines a very clean `toolchain` interface internally:
75+
76+
```Go
77+
type toolchain interface {
78+
// gc runs the compiler in a specific directory on a set of files
79+
// and returns the name of the generated output file.
80+
// The compiler runs in the directory dir.
81+
gc(b *builder, p *Package, archive, obj string, asmhdr bool, importArgs []string, gofiles []string) (ofile string, out []byte, err error)
82+
// cc runs the toolchain's C compiler in a directory on a C file
83+
// to produce an output file.
84+
cc(b *builder, p *Package, objdir, ofile, cfile string) error
85+
// asm runs the assembler in a specific directory on a specific file
86+
// to generate the named output file.
87+
asm(b *builder, p *Package, obj, ofile, sfile string) error
88+
// pkgpath builds an appropriate path for a temporary package file.
89+
pkgpath(basedir string, p *Package) string
90+
// pack runs the archive packer in a specific directory to create
91+
// an archive from a set of object files.
92+
// typically it is run in the object directory.
93+
pack(b *builder, p *Package, objDir, afile string, ofiles []string) error
94+
// ld runs the linker to create an executable starting at mainpkg.
95+
ld(b *builder, root *action, out string, allactions []*action, mainpkg string, ofiles []string) error
96+
// ldShared runs the linker to create a shared library containing the pkgs built by toplevelactions
97+
ldShared(b *builder, toplevelactions []*action, out string, allactions []*action) error
98+
99+
compiler() string
100+
linker() string
101+
}
102+
```
103+
104+
It currently has two main implementations `gcToolchain`, and `gccgoToolchain` (there's also a 3rd, `noToolchain`, that does nothing). Here you can see how Go currently supports only two values for -compiler flag.
105+
106+
```Go
107+
switch value {
108+
case "gc":
109+
buildToolchain = gcToolchain{}
110+
case "gccgo":
111+
buildToolchain = gccgoToolchain{}
112+
default:
113+
return fmt.Errorf("unknown compiler %q", value)
114+
}
115+
```
116+
117+
It's possible to either add GopherJS specifically:
118+
119+
```Go
120+
switch value {
121+
case "gc":
122+
buildToolchain = gcToolchain{}
123+
case "gccgo":
124+
buildToolchain = gccgoToolchain{}
125+
case "gopherjs":
126+
buildToolchain = gopherjsToolchain{}
127+
default:
128+
return fmt.Errorf("unknown compiler %q", value)
129+
}
130+
```
131+
132+
Or create a general toolchain that can accept any arbitrary -compiler flag value:
133+
134+
```Go
135+
switch value {
136+
case "gc":
137+
buildToolchain = gcToolchain{}
138+
case "gccgo":
139+
buildToolchain = gccgoToolchain{}
140+
default:
141+
switch compilerBin, err := exec.LookPath(value); err {
142+
case nil:
143+
buildToolchain = generalToolchain{compilerBin: compilerBin}
144+
default:
145+
return fmt.Errorf("unknown compiler %q", value)
146+
}
147+
}
148+
```
149+
150+
### Go 1.6 vs 1.5
151+
152+
The code for `cmd/go` has a large volume of changes from 1.5 to tip.
153+
154+
I've chosen to work with Go 1.5 for now, since GopherJS compiler only supports Go 1.5 at this time. There is an issue at [gopherjs/gopherjs#355](https://github.com/gopherjs/gopherjs/issues/355) tracking progress for adding Go 1.6 support, and @neelance says it's close.
155+
156+
I initially tried Go 1.6 (see [`go1.6` branch](https://github.com/gophergala2016/cmd-go-js/commits/go1.6)), but switched to working with Go 1.5 at this time for the above reasons.
157+
158+
### Code changes
159+
160+
The first five commits of [`master` branch](https://github.com/gophergala2016/cmd-go-js/commits/master) simply check in the original `cmd/go` binary source at commit [`go1.5.3`](https://github.com/golang/go/tree/go1.5.3), and make minimal changes to allow it to build on OS X and be go-gettable. It also vendors `go/build` package from standard library, in order to be able to make changes to it.
161+
162+
The rest of the changes are the relevant changes to add `-compiler=gopherjs` support to be able to build for `GOARCH=js` architecture. These changes are relatively minimal (a couple hundred lines) and not disruptive. The diff can be seen here:
163+
164+
https://github.com/gophergala2016/cmd-go-js/compare/c900f90...master
165+
166+
### Installation
167+
168+
**Note:** Due to time limits, this project works on OS X only. Adding support for other other systems is easy, but it hasn't been done yet. See commit message of [`2dae5232`](https://github.com/gophergala2016/cmd-go-js/commit/2dae52322dcef1b91b9b363fa2301da735188370), that is the only blocker.
169+
170+
Go 1.5 is required.
171+
172+
```bash
173+
go get -u github.com/gopherjs/gopherjs
174+
GO15VENDOREXPERIMENT=1 go get -u github.com/gophergala2016/cmd-go-js/cmd/go
175+
```
176+
177+
Since the binary is also called `go`, make sure you run the right one.
178+
179+
```bash
180+
$ $GOPATH/bin/go version
181+
go version go1.5.3 darwin/amd64 (with experimental changes)
182+
```
183+
184+
You may need Node if you want to execute generated JavaScript from the command line rather than inside a browser.
185+
186+
```bash
187+
brew install node
188+
```

Screenshot.png

115 KB
Loading

samples/hello-world/main.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"go/build"
6+
"runtime"
7+
)
8+
9+
func main() {
10+
fmt.Printf("Hello brave new world! It is working on %v %v/%v!", runtime.Version(), build.Default.GOOS, build.Default.GOARCH)
11+
if build.Default.GOARCH == "js" {
12+
fmt.Print(" That means you can execute it in browsers.")
13+
}
14+
fmt.Println()
15+
}

samples/hello-world/main_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package main
2+
3+
import "testing"
4+
5+
func TestBasic(t *testing.T) {
6+
if 1+2 != 3 {
7+
t.Error("failed a basic test")
8+
}
9+
}

0 commit comments

Comments
 (0)