Cross compiling "Hello World" on Mac for Android

I'm trying to build a standard "Hello, World!" command-line executable for Android. The executable is to be run via adb shell.

0. The Go (Golang) Source

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello, world!")
}

1A. The Build Command

$ CGO_ENABLED=0 GOOS=android GOARCH=arm GOARM=7 go build .

1B. The Output (Line Breaks Rearranged to Prevent Scrollbars)

# github.com/asukakenji/cross
warning: unable to find runtime/cgo.a
/usr/local/go/pkg/tool/darwin_amd64/link: running clang failed: exit status 1
ld: warning: ignoring file
    /var/folders/dd/6k6vkzbd6d5803xj9zkjdhmh0000gn/T/go-link-150305609/go.o,
    file was built for unsupported file format
    ( 0x7F 0x45 0x4C 0x46 0x01 0x01 0x01 0x00
      0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 )
    which is not the architecture being linked (x86_64):
    /var/folders/dd/6k6vkzbd6d5803xj9zkjdhmh0000gn/T/go-link-150305609/go.o
Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

1C. The Build Command, Again

The following command gives the same result:

$ env CGO_ENABLED=0 GOOS=android GOARCH=arm GOARM=7 go build .

2. The Build Command (Verbose)

I've tried using "-v" as mentioned like this:

$ CGO_ENABLED=0 GOOS=android GOARCH=arm GOARM=7 go build \
      -x -ldflags "-extldflags -v" .

It gives me more than 100 lines of messages, so I don't post it here unless it's necessary. The go build command seems to try compiling the source with the clang bundled with Xcode.

3A. The Build Command (Successful, but...)

Given the hint that the wrong compiler is found, I tried to set $CC like this:

$ CGO_ENABLED=0 GOOS=android GOARCH=arm GOARM=7 \
      CC=/path/to/arm-linux-androideabi/bin/clang go build .

arm-linux-androideabi is the output from make_standalone_toolchain.py (or make-standalone-toolchain.sh).

3B. The Output

The executable (named cross) is successfully built, with the following messages:

# github.com/asukakenji/cross
warning: unable to find runtime/cgo.a

I tried adb push it and run it with adb shell on Android, it worked fine.

My Questions

  1. Why does it need a C compiler? Doesn't Go cross-compile out-of-the-box?
  2. When building for Linux (instead of Android), the compilation works fine:

    $ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build .
    

    Why?

  3. The go build command keeps looking for runtime/cgo.a, even when I didn't use CGO in the source code, and even when I set CGO_ENABLED=0. How can I get rid of the warning? How is it harmful not having one?

Upvotes: 11

Views: 10837

Answers (4)

Hasan A Yousef
Hasan A Yousef

Reputation: 25008

You need to use Android NDK to compile for android, you can download it from the link, or from Android Studio:

enter image description here

Then you can find the compilerlink in the route as below:

Last login: Fri Sep  4 09:25:16 on console

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
Hasans-Air:~ hajsf$ pwd
/Users/hajsf
Hasans-Air:~ hajsf$ cd Library
Hasans-Air:Library hajsf$ cd android
Hasans-Air:android hajsf$ cd sdk
Hasans-Air:sdk hajsf$ cd ndk
Hasans-Air:ndk hajsf$ ls
21.3.6528147
Hasans-Air:ndk hajsf$ cd 21.3.6528147
Hasans-Air:21.3.6528147 hajsf$ cd toolchains
Hasans-Air:toolchains hajsf$ cd llvm
Hasans-Air:llvm hajsf$ cd prebuilt
Hasans-Air:prebuilt hajsf$ ls
darwin-x86_64
Hasans-Air:prebuilt hajsf$ cd darwin-x86_64
Hasans-Air:darwin-x86_64 hajsf$ cd bin
Hasans-Air:bin hajsf$ pwd
/Users/hajsf/Library/android/sdk/ndk/21.3.6528147/toolchains/llvm/prebuilt/darwin-x86_64/bin

Then you can use the one reflection:

  1. Architecture you want to build your android app for, like aarch64
  2. Android API you want to compile for, like Android 30

You can see full list of available options there, and you can select the one you want like aarch64-linux-android30-clang

If you are at Windows 10 you'll find it at:

"C:\Users\${user}\AppData\Local\Android\Sdk\ndk\${NKD_version}\toolchains\llvm\prebuilt\windows-x86_64\bin\aarch64-linux-android30-clang"

There are 4 available linkers which are:

//CC_FOR_TARGET=/Users/hajsf/Library/android/sdk/ndk/21.3.6528147/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android30-clang
//CC_FOR_TARGET=/Users/hajsf/Library/android/sdk/ndk/21.3.6528147/toolchains/llvm/prebuilt/darwin-x86_64/bin/i686-linux-android30-clang
//CC_FOR_TARGET=/Users/hajsf/Library/android/sdk/ndk/21.3.6528147/toolchains/llvm/prebuilt/darwin-x86_64/bin/x86_64-linux-android30-clang
//CC_FOR_TARGET=/Users/hajsf/Library/android/sdk/ndk/21.3.6528147/toolchains/llvm/prebuilt/darwin-x86_64/bin/armv7a-linux-androideabi30-clang

After that only you can do cross-compiling, as:

$ CGO_ENABLED=1
$ GOOS=android
$ GOARCH=arm64
$ CC_FOR_TARGET=/Users/hajsf/Library/android/sdk/ndk/21.3.6528147/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android30-clang
$ go build -buildmode=c-shared -o lib-aarch64-android30.so lib.go

And a simple cgo file lib.go could be:

package main

import "C"
import "fmt"

//export HelloWorld
func HelloWorld() {
    fmt.Printf("hello world from GO\n")
}

//export GetKey
func GetKey() *C.char {
    theKey := "123-456-789"
    return C.CString(theKey)
}

func main() {}

enter image description here

As shown compilation completed successfully, and both lib-aarch64-android30.so and ib-aarch64-android30.h had been generated without any error.

Quick note, not to go far from the scope of the question, if you return a string, then the return value from this function must be explicitly freed in the C code if if you call it from C code, but as you call it from garbage collector environment, Java/Kotlin you do not want to worry about it.

If freeing the allocated buffer isn't convenient, its common to fill a buffer provided by the caller:

func GetKey(buff *C.char, n int) int

If you can allocate the memory but don't want to handle C strings, you can insert the buffer into a pointer and return the size.

func GetKey(buff **C.char) int

Upvotes: 1

Annymosse
Annymosse

Reputation: 61

if you run that code you find the android as an official target platform listed as GOOS/GOARCH

$go tool dist list

Upvotes: 6

tmm1
tmm1

Reputation: 2125

The cgo requirement might be because go requires libc for DNS lookups on Android: https://github.com/golang/go/issues/8877

Upvotes: 2

Uvelichitel
Uvelichitel

Reputation: 8490

Android isn't official target platform for cross-compilation. If all you need are command-line executables then you can set GOOS=linux because android is a linux under the hood, else take a look at https://github.com/golang/go/wiki/Mobile

Upvotes: 4

Related Questions