Make an Android Smartphone Build Go Applications

Posted: Jul 9, 2016 in Tech
Tags: ,

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).

5. References

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s