Do you have an Android smartphone and would like to go run with it?
This post tracks my adventures setting up a Go builder environment in a Samsung Galaxy S3, no rooting required. Despite the environment is not full featured as a computer’s, it can run the go command fine enough to build and run native arm binaries from Go code.
1. Cross Build Go for Android
Here we’ll compile the Go builder for the Android OS , arm architecture, and have it tested in your smartphone.
Setup ADB
Install Android Debug Bridge in your computer, enable usb debugging in your smartphone, connect computer and smartphone via usb, and make sure adb can see the smartphone.
user@computer ~ $ adb devices -l List of devices attached 4df173935a2e5fd7 device product:m0ub model:GT_I9300 device:m0 user@computer ~ $ adb shell shell@m0:/ $ exit user@computer ~ $
Feel free to disconnect your device now if you want; we’ll reconnect it later.
Setup NDK Toolchain
As building Go for Android requires cgo, there are some bits of C code to be cross compiled also. We need a C cross toolchain for Android.
Download Android Native Development Kit for your system and unpack it somewhere. Optionally, if you use Android Studio and have NDK already installed, you’ll find it under directory ~/Android/Sdk/ndk-bundle.
Create a standalone toolchain package for Android by running NDK’s make-standalone-toolchain script:
user@computer ~ $ mkdir ~/src user@computer ~ $ cd ~/Android/Sdk/ndk-bundle user@computer ~/Android/Sdk/ndk-bundle $ ./build/tools/make-standalone-toolchain.sh --arch=arm --install-dir=/home/user/src/ndk-toolchain WARNING: make-standalone-toolchain.sh will be removed in r13. Please try make_standalone_toolchain.py now to make sure it works for your needs. HOST_OS=linux HOST_EXE= HOST_ARCH=x86_64 HOST_TAG=linux-x86_64 HOST_NUM_CPUS=4 BUILD_NUM_CPUS=8 Auto-config: --toolchain=arm-linux-androideabi-4.9 Auto-config: --platform=android-9 Copying prebuilt binaries... Copying sysroot headers and libraries... Copying c++ runtime headers and libraries... Copying files to: /home/user/src/ndk-toolchain Cleaning up... Done. user@computer ~/Android/Sdk/ndk-bundle $ cd ~/src/ndk-toolchain user@computer ~/src/ndk-toolchain $ ./bin/arm-linux-androideabi-gcc --version arm-linux-androideabi-gcc (GCC) 4.9.x 20150123 (prerelease) Copyright (C) 2014 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Setup Go Bootstrap
As Go is required for building Go, we need a Go compiler installed. I assume your computer has a recent version of Go installed already, so you don’t have to download a binary release of it. If so, just mark down where is the root of your Go system.
user@computer ~ $ go version go version go1.6.1 linux/amd64 user@computer ~ $ go env GOROOT /usr/lib/go
Optionally, download and unpack a binary release of Go for your computer, and use it as the Go bootstrap.
Download Go Sources
Download the source package of the latest stable version of Go, and unpack it.
user@computer ~ $ tar -C ~/src -xzf go1.6.1.src.tar.gz user@computer ~ $ ls ~/src/go api AUTHORS CONTRIBUTING.md CONTRIBUTORS doc favicon.ico lib LICENSE misc PATENTS README.md robots.txt src test VERSION
Cross Build & Test Go for Android
The Go creators presented us with a script for automating the build of a Go build system for Android. It also transfers the results of the compilation to a temporary location in the target smartphone using adb, and tests all standard packages of the Go build system in the target.
user@computer ~ $ adb devices List of devices attached 4df173935a2e5fd7 device user@computer ~ $ cd ~/src/go/src user@computer ~/src/go/src $ CC_FOR_TARGET=~/src/ndk-toolchain/bin/arm-linux-androideabi-gcc GOROOT_BOOTSTRAP=/usr/lib/go GOARCH=arm ./androidtest.bash ##### Building Go bootstrap tool. cmd/dist ##### Building Go toolchain using /usr/lib/go. bootstrap/internal/obj bootstrap/compile/internal/big [...] ##### Building go_bootstrap for host, linux/amd64. runtime/internal/sys runtime/internal/atomic [...] ##### Building packages and commands for host, linux/amd64. runtime/internal/sys runtime/internal/atomic [...] ##### Building packages and commands for android/arm. runtime/internal/sys runtime/internal/atomic [...] # Syncing test files to android device real 0m17.281s user 0m0.033s sys 0m0.130s [100%] /data/local/tmp/cleaner 2016/07/12 02:57:04 removing /data/local/tmp/goroot/pkg/android_arm64 ##### Testing packages. ok archive/tar 2.877s ok archive/zip 3.655s [...]
It pays to check what tests are ok and what failed, as these are standard Go packages prone to be imported by Go applications you build in your Android device. (In my smartphone package runtime/pprof failed.)
Finished androidtest script, optionally remove test files from your device to save storage:
user@computer ~ $ adb shell rm -r /data/local/tmp/goroot
Tune Go Root for Android
At this point a Go root for arm coexists in Go sources:
user@computer ~ $ cd ~/src/go/bin user@computer ~/src/go/bin $ file go android_arm/go go: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, not stripped android_arm/go: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/bin/linker, not stripped
As Go by default expects its tools to be found in $GOROOT/bin, let’s tune it for the Android device:
user@computer ~/src/go/bin $ rm * rm: cannot remove ‘android_arm’: Is a directory user@computer ~/src/go/bin $ mv android_arm/* . user@computer ~/src/go/bin $ rmdir android_arm user@computer ~/src/go/bin $ file * go: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/bin/linker, not stripped gofmt: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/bin/linker, not stripped
Later we’ll transfer the arm Go root to the Android device; for now just keep it.
2. Install a Terminal Emulator for Android
I gave Terminal IDE a try. On the first opening, choose Install System button to have the base C / binary system installed.
The Help button shows extensive documentation about this application.
Open a terminal session (Terminal IDE button) to get a prompt where you can run commands. Issuing telnetd in Terminal IDE opens a telnet daemon in port 8080 of your device, so you can access it remotely.
By default Terminal IDE’s prompt shows the full path of the current working directory. I find this cumbersome when I’m deep down into a directory hierarchy, specially in screens with small character width like when running in my smartphone. I prefer adding the following to ~/.bash_aliases,
PS1='\[\033[01;32m\]terminal\[\e[1;31m\]++\[\e[1;33m\]@\[\e[1;35m\]$HOSTNAME\[\033[00m\]:\[\033[01;34m\]\W\[\033[00m\]\$ '
, so the prompt shows only the last piece of my current working directory.
Access Github
Generate a RSA identity for you:
terminal++@192.168.0.102:~$ mkdir -p ~/.ssh terminal++@192.168.0.102:~$ dropbearkey -t rsa -f ~/.ssh/id_rsa Will output 1024 bit rsa secret key to '/data/data/com.spartacusrex.spartacuside/files/.ssh/id_rsa' Generating key, this may take a while... Public key portion is: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgn/D5RlQauJxTz52CyolrUHhysG4rRN1Gj5ZwF+m06EJoqqiiCdsWDYibrUETSZK/iPCgzPSNFFZQY0RFCb30Ovxa5iIIj8lcdBg/9abB26UoFgPsT49IBfrwpboZHfnMStEyZdMVyRvd0LbenxqdfWD14CuX4vcaK2jkGg5GLeet5E= u0_a138@localhost Fingerprint: md5 22:e9:32:5a:d7:35:7b:35:69:6b:66:5f:7c:b8:55:b7
Import the public key portion to your github account to identify ssh connections from this terminal. To retrieve the public key at any time, run dropbearkey -y -f ~/.ssh/id_rsa.
Instruct git client to use this identity when accessing repositories through ssh. You can do this by creating file ~/local/bin/ssh-git with the following content:
#!/data/data/com.spartacusrex.spartacuside/files/system/bin/bash exec ssh -i ~/.ssh/id_rsa "$@"
Make sure the file is executable. Then add the following to ~/.bash_aliases, replacing with your name and email:
export GIT_SSH=~/local/bin/ssh-git export GIT_AUTHOR_NAME="Your Name" export GIT_AUTHOR_EMAIL="youremail@provider.com" export GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL=$GIT_AUTHOR_EMAIL
Restart your terminal session. Cloning from github should work fine now:
terminal++@192.168.0.102:~$ cd tmp terminal++@192.168.0.102:tmp$ git clone git@github.com:coolparadox/go Cloning into 'go'... remote: Counting objects: 989, done. remote: Total 989 (delta 0), reused 0 (delta 0), pack-reused 989 Receiving objects: 100% (989/989), 174.42 KiB | 55 KiB/s, done. Resolving deltas: 100% (559/559), done.
Setup Go Root
Let’s transfer the Android Go root that was cross built in the previous chapter to the device. In terminal:
terminal++@192.168.0.102:~$ mkdir -p ~/opt/go ~/opt/bin terminal++@192.168.0.102:~$ chmod 0777 ~/opt/go ~/opt terminal++@192.168.0.102:~$ mkdir ~/sdcard/go
In computer:
user@computer ~ $ adb push ~/src/go /data/data/com.spartacusrex.spartacuside/files/opt/go/root adb: warning: skipping empty directory '/home/lorandi/src/go/pkg/obj/linux_amd64/' adb: warning: skipping empty directory '/home/lorandi/src/go/pkg/android_arm/' /data/data/com.spartacusrex.spartacuside/files/opt/go/root/: 5832 files pushed. 2 files skipped. 3.4 MB/s (438423127 bytes in 124.141s)
Also add the following to ~/.bash_aliases:
export GOROOT=~/opt/go/root export GOPATH=~/sdcard/go export GOBIN=~/opt/go/bin
And make symlinks to go commands:
terminal++@192.168.0.102:~$ ln -s ~/opt/go/root/bin/go* ~/local/bin/
Restart terminal. Go command should be reachable by now.
terminal++@192.168.0.102:~$ go version go version go1.6.1 android/arm terminal++@192.168.0.102:~$ go doc fmt.Printf func Printf(format string, a ...interface{}) (n int, err error) Printf formats according to a format specifier and writes to standard output. It returns the number of bytes written and any write error encountered.
Try to Build hello.go
For testing, you can follow the official instructions for first timers, or optionally retrieve a hello world sample application from my github account:
terminal++@192.168.0.102:~$ cd $GOPATH terminal++@192.168.0.102:go$ cd src terminal++@192.168.0.102:src$ mkdir -p github.com/coolparadox terminal++@192.168.0.102:src$ cd github.com/coolparadox/ terminal++@192.168.0.102:coolparadox$ git clone git@github.com:coolparadox/go Cloning into 'go'... remote: Counting objects: 994, done. remote: Compressing objects: 100% (4/4), done. remote: Total 994 (delta 1), reused 0 (delta 0), pack-reused 989 Receiving objects: 100% (994/994), 175.08 KiB | 73 KiB/s, done. Resolving deltas: 100% (560/560), done. terminal++@192.168.0.102:coolparadox$ cd go/hello terminal++@192.168.0.102:hello$ ls hello.go terminal++@192.168.0.102:hello$ cat hello.go package main import "fmt" func main() { fmt.Println("Hello world!") }
Let’s build and run it with go run command. If it goes fine,
terminal++@192.168.0.102:hello$ go run hello.go Hello world!
, then we’re all set and you can skip to the next chapter. Chances are however that the compilation fails due to a missing linker:
terminal++@192.168.0.102:hello$ go run hello.go # command-line-arguments /data/data/com.spartacusrex.spartacuside/files/opt/go/root/pkg/tool/android_arm/link: running /home/lorandi/src/ndk-toolchain/bin/arm-linux-androideabi-gcc failed: fork/exec /home/lorandi/src/ndk-toolchain/bin/arm-linux-androideabi-gcc: no such file or directory
If so, keep reading.
Setup an External Linker for Go
Install the GNU compiler,
terminal++@192.168.0.102:~$ install_gcc Extracting GCC.. Please wait.. GCC 4.4.0 Installed
, and make sure it works:
terminal++@192.168.0.102:tmp$ cat hello.c #include <stdio.h> int main() { printf("Hello world!\n"); } terminal++@192.168.0.102:tmp$ arm-eabi-gcc -mandroid -o hello hello.c terminal++@192.168.0.102:tmp$ ./hello Hello world!
Back to hello.go, let’s try again instructing Go to make use of the external linker:
terminal++@192.168.0.102:hello$ CC=arm-eabi-gcc go run -ldflags '-extldflags=-mandroid' hello.go Hello world!
Yay it works!!
Obviously we don’t want to type this ugly long line on each go run invocation. Add the following to ~/.bash_aliases:
export CC=arm-eabi-gcc export CXX=arm-eabi-g++
And restart. Getting rid of -ldflags part is trickier, as I haven’t found a way of doing this using environment variables. I came up creating a wrapper script for the go command that appends this option when building, and replaced ~/local/bin/go with it:
terminal++@192.168.0.102:~$ cat ~/local/bin/go set -e GO=$GOROOT/bin/go test $# -gt 0 || { set -x exec $GO } COMMAND=$1 shift BUILD_OPT='' case $COMMAND in build|clean|get|install|list|run|test) BUILD_OPT=-ldflags\ -extldflags=-mandroid ;; esac set -x exec $GO $COMMAND $BUILD_OPT $*
Testing everything again:
terminal++@192.168.0.102:hello$ go env CC ++ exec /data/data/com.spartacusrex.spartacuside/files/opt/go/root/bin/go env CC arm-eabi-gcc terminal++@192.168.0.102:hello$ go run hello.go ++ exec /data/data/com.spartacusrex.spartacuside/files/opt/go/root/bin/go run -ldflags -extldflags=-mandroid hello.go Hello world!
(Later you’ll probably want to remove both set -x lines from go wrapper script.)
My Own ~/.bash.aliases
terminal++@quark:~$ cat ~/.bash_aliases export HOSTNAME=quark export GIT_SSH=~/local/bin/ssh-git export GIT_AUTHOR_NAME="Rafael Lorandi" export GIT_AUTHOR_EMAIL="coolparadox@gmail.com" export GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL=$GIT_AUTHOR_EMAIL export CC=arm-eabi-gcc export CXX=arm-eabi-g++ export GOROOT=~/opt/go/root export GOPATH=~/sdcard/go export GOBIN=~/opt/go/bin alias gst='git status' alias gbr='git branch' PS1='\[\033[01;32m\]terminal\[\e[1;31m\]++\[\e[1;33m\]@\[\e[1;35m\]$HOSTNAME\[\033[00m\]:\[\033[01;34m\]\W\[\033[00m\]\$ '
3. Pain Spots
Despite doing Go in Android smartphone is awesome and can genuinely aid with your Go developments, the solution showed here is not all roses.
It’s a Small Screen
As you can imagine, a smartphone is not exactly the most enjoyable environment to develop software.
No DNS Resolution
Terminal IDE does not resolve names. You must workaround it by making use of its jping utility and replace IP addresses manually.
Old Git
terminal++@quark:~$ git --version git version 1.7.8.163.g9859a.dirty
Git deployed with Terminal IDE doesn’t support https protocol nor submodules. Although the former can be worked around, the latter renders impossible to go get stuff:
terminal++@quark:~$ go get -d github.com/coolparadox/go # cd /data/data/com.spartacusrex.spartacuside/files/sdcard/go/src/github.com/coolparadox/go; git submodule update --init --recursive fatal: cannot exec 'git-submodule': Permission denied package github.com/coolparadox/go: exit status 255
As a workaround, use -x option with go get to trace internal commands, and manually clone Go repositories.
Moreover I cannot push to my github repos through Terminal IDE’s git:
terminal++@quark:go$ git push Counting objects: 4, done. Delta compression using up to 2 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 274 bytes, done. Total 3 (delta 1), reused 0 (delta 0) fatal: The remote end hung up unexpectedly fatal: The remote end hung up unexpectedly
A workaround is clone Go repositories in a public of your Android device (eg. ~/sdcard/go/src), and use another git app for pushing.
4. Issues to Be Addressed Someday
Despite Terminal IDE is a fantastic app for the developer, it’s not being actively updated anymore (lastest release at the moment of writing was March 3 2013).