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
- Before we start
- The
Workflowndk-build
- Use customized toolchain for your projects
-
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
ndk-build
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:
- Remove previously built files in the output folder
.libs
- Build hello.o from the source code.
- Build executable from hello.o.
- Copy executable file to subfolders
folder according to the ABI.libs
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