天天看点

Mastering Android NDK Build System - Part 2: Standalone toolchainTable of Contents1. Before we start2. The ndk-build Workflow3. Use customized toolchain for your projects4. Summary

This is the part 2 of “Mastering NDK” article. In the previous part (part 1), we have introduced how to use 

ndk-build

 to build Android native projects, and we also covered several advanced techniques to manage and customize the build script for bigger projects.

Although 

ndk-build

 is probably more than enough for most of the Android native projects, there might still be demand for the standalone toolchain in some cases. For example, if you already have a C/C++ project which might be quite complex and has a complicated makefile. In that case, you may not want to convert everything to 

Android.mk

 and 

Application.mk

. Using standalone toolchain makes more sense in this case, and it allows you to keep your original makefile or reuse most of the original makefile. Therefore, in this part, I will briefly cover the usage of the standalone toolchain, and give a few code examples.

The source code of all examples can be found here: https://github.com/robertwgh/mastering-ndk.

Table of Contents

  1. Before we start
  2. The 

    ndk-build

     Workflow
  3. Use customized toolchain for your projects
  4. Summary

    Comments

1. Before we start

The Android NDK official document contain a chapter “STANDALONE-TOOLCHAIN”, which gives useful information about the standalone toolchain. However, that document is short of details, and there is no example to demonstrate the usage. Therefore, it might be hard to follow the official document. But it is still a good reference to have anyways.

2. The 

ndk-build

 Workflow

Actually, when we use 

ndk-build

, if we enable the debug option 

V=1

 as follows:

We will see what is actually done by 

ndk-build

. The following console print comes from the compilation of example 1.

$ ndk-build V=1
rm -f ./libs/arm64-v8a/lib*.so ./libs/armeabi/lib*.so ./libs/armeabi-v7a/lib*.so ./libs/armeabi-v7a-hard/lib*.so ./libs/mips/lib*.so ./libs/mips64/lib*.so ./libs/x86/lib*.so ./libs/x86_64/lib*.so

rm -f ./libs/arm64-v8a/gdbserver ./libs/armeabi/gdbserver ./libs/armeabi-v7a/gdbserver ./libs/armeabi-v7a-hard/gdbserver ./libs/mips/gdbserver ./libs/mips64/gdbserver ./libs/x86/gdbserver ./libs/x86_64/gdbserver

rm -f ./libs/arm64-v8a/gdb.setup ./libs/armeabi/gdb.setup ./libs/armeabi-v7a/gdb.setup ./libs/armeabi-v7a-hard/gdb.setup ./libs/mips/gdb.setup ./libs/mips64/gdb.setup ./libs/x86/gdb.setup ./libs/x86_64/gdb.setup

[armeabi-v7a] Compile++ thumb: hello <= hello.cpp
/cygdrive/d/development/android-ndk-r10d/toolchains/arm-linux-androideabi-4.8/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ -MMD -MP -MF ./obj/local/armeabi-v7a/objs-debug/hello/hello.o.d.org -fpic -ffunction-sections -funwind-tables -fstack-protector -no-canonical-prefixes -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp -fno-exceptions -fno-rtti -mthumb -Os -g -DNDEBUG -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -O0 -UNDEBUG -marm -fno-omit-frame-pointer -ID:/development/android-ndk-r10d/sources/cxx-stl/stlport/stlport -ID:/development/android-ndk-r10d/sources/cxx-stl//gabi++/include -Ijni -DANDROID  -Wa,--noexecstack -Wformat -Werror=format-security -fPIE  -frtti   -frtti -fexceptions  -ID:/development/android-ndk-r10d/platforms/android-19/arch-arm/usr/include -c  jni/hello.cpp -o ./obj/local/armeabi-v7a/objs-debug/hello/hello.o && ./obj/convert-dependencies.sh ./obj/local/armeabi-v7a/objs-debug/hello/hello.o.d

[armeabi-v7a] Executable     : hello
/cygdrive/d/development/android-ndk-r10d/toolchains/arm-linux-androideabi-4.8/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ -Wl,--gc-sections -Wl,-z,nocopyreloc --sysroot=D:/development/android-ndk-r10d/platforms/android-19/arch-arm -Wl,-rpath-link=D:/development/android-ndk-r10d/platforms/android-19/arch-arm/usr/lib -Wl,-rpath-link=./obj/local/armeabi-v7a ./obj/local/armeabi-v7a/objs-debug/hello/hello.o D:/development/android-ndk-r10d/sources/cxx-stl/stlport/libs/armeabi-v7a/thumb/libstlport_static.a -lgcc -no-canonical-prefixes -march=armv7-a -Wl,--fix-cortex-a8  -Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -fPIE -pie   -lc -lm -o ./obj/local/armeabi-v7a/hello

[armeabi-v7a] Install        : hello => libs/armeabi-v7a/hello
install -p ./obj/local/armeabi-v7a/hello ./libs/armeabi-v7a/hello
/cygdrive/d/development/android-ndk-r10d/toolchains/arm-linux-androideabi-4.8/prebuilt/windows-x86_64/bin/arm-linux-androideabi-strip --strip-unneeded ./libs/armeabi-v7a/hello
           

As we can see, 

ndk-build

 actually did the following things:

  1. Remove previously built files in the output folder 

    libs

    .
  2. Build hello.o from the source code.
  3. Build executable from hello.o.
  4. Copy executable file to subfolders 

    libs

     folder according to the ABI.

The above building information shows how the 

ndk-build

 invokes the toolchain to build the project. Basically, 

/cygdrive/d/development/android-ndk-r10d/toolchains/arm-linux-androideabi-4.8/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++

 is the standalone toolchain coming with the NDK installation. And if we read the whole command, we find that the command just invokes the cross-compilation version g++, with building parameters such as include path, library path and so on. The most straightforward way to use standalone toolchain is to mimic what 

ndk-build

 does, and directly invoke the right compilers based on your target architecture and platforms.

Under the 

NDK_ROOT/toolchains

 directory, we can find different toolchains for ARM, X86, X86_64, MIPS and so on. There are also versions with different compilers such as g++ and clang. Therefore, we can basically choose whatever is suitable for our projects.

3. Use customized toolchain for your projects

Apparently, the above method works, but it is very verbose and not suitable for larger projects. What we can do is to create a “customized” toolchain for a specific platform and ABI, with the help of a tool 

$NDK/build/tools/make-standalone-toolchain.sh

 provided with NDK installation.

Let’s look at an example. Assume we have a project with the following configurations:

  • Support android-19 platform.
  • Host system is Windows 7 64bit.
  • Target architecture is ARM.

We can create a script 

generate_standalone_toolchain.sh

 to help us export the toolchain we need:

generate_standalone_toolchain.sh

NDK=/cygdrive/d/development/android-ndk-r10d
SYSROOT=$NDK/platforms/android-19/arch-arm/
mkdir -p /cygdrive/d/development/standalone_toolchain/
$NDK/build/tools/make-standalone-toolchain.sh --arch=arm --platform=android-19 --system=windows-x86_64 --install-dir=/cygdrive/d/development/standalone_toolchain/
chmod -R 755 /cygdrive/d/development/standalone_toolchain
           

The helper script 

$NDK/build/tools/make-standalone-toolchain.sh

 creates a temporary directory under 

/tmp

, copies files to that directory, and finally copies the files to the specified folder. Please make sure you have the enough permission, otherwise you will meet “permission denied” error when accessing the 

/tmp

 directory.

We should see the following console information if the path is configured correctly:

$ ./generate_standalone_toolchain.sh
Auto-config: --toolchain=arm-linux-androideabi-4.8
Copying prebuilt binaries...
Copying sysroot headers and libraries...
Copying c++ runtime headers and libraries...
Copying files to: /cygdrive/d/development/standalone_toolchain/
Cleaning up...
Done.
           

Once this is done, we can see the whole toolchain is copied from 

NDK_ROOT/toolchains

 to 

/cygdrive/d/development/standalone_toolchain/

.

Example: helloworld

To test the customized toolchain, we create an example project helloworld. The project structure is very simple:

+-- helloworld
|   +-- hello.cpp
|   +-- Makefile
           

hello.cpp

#include <iostream>
int main()
{
    std::cout << "Hello World!" << std::endl;
}
           

Makefile

STANDALONE_TOOLCHAIN=/cygdrive/d/development/standalone_toolchain/bin/
CC=$(STANDALONE_TOOLCHAIN)/arm-linux-androideabi-gcc
CXX=$(STANDALONE_TOOLCHAIN)/arm-linux-androideabi-g++
CFLAGS=-march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -c -Wall
LDFLAGS=-march=armv7-a -Wl,--fix-cortex-a8
SOURCES=hello.cpp
OBJECTS=$(SOURCES:.cpp=.o)
EXECUTABLE=hello

all: $(SOURCES) $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS) 
    $(CXX) $(LDFLAGS) $(OBJECTS) -o [email protected]
.cpp.o:
    $(CXX) $(CFLAGS) $< -o [email protected]

clean: 
    rm *.o hello;
           

We can simply compile the code as follows, and we will get executable files:

$ cd helloworld
$ make
/cygdrive/d/development/standalone_toolchain/bin//arm-linux-androideabi-g++ -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -c -Wall hello.cpp -o hello.o
/cygdrive/d/development/standalone_toolchain/bin//arm-linux-androideabi-g++ -march=armv7-a -Wl,--fix-cortex-a8 hello.o -o hello
           

Another example: clcompute

This is a more complicated example, which does parallel vector addition using OpenCL. Assume we have CL include and library files available:

+-- D:\opencl_lib
|   +-- include
|       +-- CL
|   +-- libs
|       +-- libOpenCL.so
           

The CL include header files can be downloaded from Khronos Group website. And the libOpenCL.so library file can be retrieved from an OpenCL-capable phone. You can use 

adb pull

 to pull it from your phone (or tablet). You can refer to the table in the part 1 of this article for the detailed position of the libOpenCL.so for different SoC chipsets.

The project structure:

+-- clcompute
|   +-- clcompute.cpp
|   +-- Makefile
           

Makefile

STANDALONE_TOOLCHAIN=/cygdrive/d/development/standalone_toolchain/bin/
OPENCL=/cygdrive/d/opencl_lib/
CC=$(STANDALONE_TOOLCHAIN)/arm-linux-androideabi-gcc
CXX=$(STANDALONE_TOOLCHAIN)/arm-linux-androideabi-g++
CFLAGS=-march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -c -I $(OPENCL)/inc/
LDFLAGS=-march=armv7-a -Wl,--fix-cortex-a8 -L$(OPENCL)/libs/ -lOpenCL

SOURCES=clcompute.cpp
OBJECTS=$(SOURCES:.cpp=.o)
EXECUTABLE=clcompute

all: $(SOURCES) $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS) 
    $(CXX) $(LDFLAGS) $(OBJECTS) -o [email protected]
.cpp.o:
    $(CXX) $(CFLAGS) $< -o [email protected]

clean: 
    rm *.o $(EXECUTABLE);
           

As you can see, once you have exported the standalone toolchain, the makefile is basically the same as a normal makefile, once you have specified the path to your toolchain. We can imagine that by using the standalone toolchain, we can build some complicated projects which may require significant amount of effort is 

ndk-build

 was used.

4. Summary

In this technical note (part 1 and part 2), we have covered the usage and techniques related to the compilation of the Android native projects. Hope this article is useful, and if you have some better ideas or any comments, please feel free to leave your word below.

http://web.guohuiwang.com/technical-notes/androidndk2