I’ve limited love to Android tools provided by Google and never understood why Google tries to make it so complicated to run native code on the device. In the end Android is some form of Linux and some parts of Android framework are implemented in C/C++. I also have limited love (and knowledge) to Java and don’t really like to use it.

Anyways, here below I present 2 methods of compiling C programs with Android NDK.

Let’s use standard “hello world” as an application that we want to run on Android dev board (main.c):

#include <stdio.h>

int main()
{
  printf("Hello World\n");
  return 0;
}

Method 1: Using ndk-build

This method follows Android’ic way of doing things:

  • Create required directories

    mkdir -p hello_world/jni
    mkdir -p hello_world/libs
    

In the jni directory create

  • Android.mk

        LOCAL_PATH := $(call my-dir)
        include $(CLEAR_VARS}
        # give module name
        LOCAL_MODULE := hello_world
        # list your C files to compile
        LOCAL_SRC_FILES := main.c
        include $(BUILD_EXECUTABLE)
        
    
  • Copy/create main.c to jni directory

  • Go to jni directory, call ndk-build. Compilation result should be in hello_world/libs/armeabi/hello_world

Method 2: Makefile

With the second (and my prefered) way you have better control over files being compiled, compiler settings, etc. There is also no “magic” that ndk-build provides.

Following Makefile uses clang from NDK 16b in order to compile a file for Android with API version 27 and for ARMv8 CPU. The makefile can be used a template.

# Change this to whereever you keep NDK
NDK            = /opt/android-ndk
SRCDIR         = .
OBJDIR         = .
DBG           ?= 0

# Debug/Release configuration
ifeq ($(DBG),1)
MODE_FLAGS     = -DDEBUG -g -O0
else
MODE_FLAGS     = -Os -fdata-sections -ffunction-sections
endif

## NDK configuration (clang)

# NDK Version
NDK_TARGETVER  = 27

# Target arch - here aarch64 for android
NDK_TARGETARCH = aarch64-linux-android

# Target CPU (ARMv8)
NDK_TARGETSHORTARCH = arm64

# Toolchain version
NDK_TOOLVER  = 4.9

# Architecture of a machine that does cross compilation
NDK_HOSTARCH = linux-x86_64

# Set needed preprocessor symbols
NDK_TOOLS    = $(NDK)/toolchains/llvm/prebuilt/$(NDK_HOSTARCH)/bin
NDK_SYSROOT  = $(NDK)/sysroot
NDK_TOOL     = $(NDK_TOOLS)/clang
NDK_LIBS     = $(NDK)/toolchains/$(NDK_TARGETARCH)-$(NDK_TOOLVER)/prebuilt/linux-x86_64/lib/gcc/$(NDK_TARGETARCH)/4.9.x
NDK_INCLUDES = -I$(NDK)/sysroot/usr/include \
               -I$(NDK)/sysroot/usr/include/$(NDK_TARGETARCH)
NDK_SYSROOT  = $(NDK)/platforms/android-$(NDK_TARGETVER)/arch-$(NDK_TARGETSHORTARCH)

# Options common to compiler and linker
OPT          = $(MODE_FLAGS) \
               -std=c99 \
               -fPIE \
               -Wall \
               -target $(NDK_TARGETARCH)

# Compiler options
CFLAGS       = $(OPT) \
               $(NDK_INCLUDES)

# Linker options
LDFLAGS      = $(OPT) \
               $(MODE_FLAGS) \
               -pie \
               --sysroot=$(NDK_SYSROOT) \
               -B $(ANDROID_NDK)/toolchains/$(NDK_TARGETARCH)-$(NDK_TOOLVER)/prebuilt/linux-x86_64/$(NDK_TARGETARCH)/bin \
               -L$(NDK_LIBS)

all:
    $(NDK_TOOL) -c $(SRCDIR)/main.c -o $(OBJDIR)/main.o $(CFLAGS)
    $(NDK_TOOL) -o main $(OBJDIR)/main.o $(LDFLAGS)

adb-prepare:
    adb root
    adb remount

push: adb-prepare
    adb push main /data/app/

run: adb-prepare push
    adb shell /data/app/main

Copy this file to same directory as main.c and try

make all
make run

This should compile the file, push it to target and run (if target is connected).

hdc@cryptoden 23:49 > ~/example 
> make run   
adb root
adb remount
remount succeeded
adb push main /data/app/
main: 1 file pushed. 0.7 MB/s (6000 bytes in 0.008s)
adb shell /data/app/main
Hello World