github.com/gophergala2016/cmd-go-js@v0.0.0-20160421080227-24a7748a7d62/README.md (about) 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 ```