Cross compiling "Hello World" on Mac for Android
Asked Answered
E

4

11

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?
Elver answered 15/8, 2016 at 2:10 Comment(8)
To make things easy, I think you need Android NDK. clang should work too, but it's not offical for Android native programming, need lots of configurations maybe.Kelcy
As stated above, I already successfully compiled the code using Android NDK. The question is why it is needed. When I target "Linux amd64", a cross compiler is never needed. What makes the difference?Elver
I guess something related -D__ARM_ANDROID__ -DHAVE_PTHREADS which we always added in NDK building.Kelcy
I am afraid that you missed the point. I understand very well that the NDK can get the job done. I just wonder why a C compiler is needed. As far as I know, the Golang compiler compiles source to binary without the need of another compiler, unless CGO is used. I have successfully built these on a Mac, all without any external tools: "darwin amd64" (host), "linux 386", "linux amd64", "windows 386", "windows amd64". But why is "android arm" an exception? The point is not the flags used, the point is why a C compiler is needed at the first place!Elver
I know your point. But C-things (compiler/stdlib/macros/headers/#pragma) depends platform badly. Another C compiler hardly work with the android C-things. Still it's my guess, not dig deep.Kelcy
I didn't write a single line of C code here.Elver
some old blog.hashbangbash.com/2014/04/linking-golang-staticallyKelcy
Thanks! But I'm NOT using CGO here, and that's exactly why I wonder why a C compiler is needed. Sorry, but have you read my question?Elver
A
5

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

$go tool dist list
Arbitrament answered 4/8, 2018 at 15:34 Comment(2)
Sure, it is now, in go1.10.3. The question is two years old.Hygrophilous
@MichaelHampton yeah for sure, I add it for the new comers reference.Arbitrament
E
4

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

Embroider answered 15/8, 2016 at 9:35 Comment(0)
S
2

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

Smithereens answered 16/12, 2016 at 5:39 Comment(0)
Q
1

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
Quatrain answered 4/9, 2020 at 8:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.