75

I'm trying to add native code to my app. I have everything in ../main/jni as it was in my Eclipse project. I have added ndk.dir=... to my local.properties. I haven't done anything else yet (I'm not sure what else is actually required, so if I've missed something let me know). When I try and build I get this error:

Execution failed for task ':app:compileDebugNdk'.
> com.android.ide.common.internal.LoggedErrorException: Failed to run command:
    /Users/me/android-ndk-r8e/ndk-build NDK_PROJECT_PATH=null 
APP_BUILD_SCRIPT=/Users/me/Project/app/build/ndk/debug/Android.mk APP_PLATFORM=android-19 
NDK_OUT=/Users/me/Project/app/build/ndk/debug/obj 
NDK_LIBS_OUT=/Users/me/Project/app/build/ndk/debug/lib APP_ABI=all

  Error Code:
    2
  Output:
    make: *** No rule to make target `/Users/me/Project/webapp/build/ndk/debug//Users/me/Project/app/src/main/jni/jni_part.cpp',
 needed by `/Users/me/Project/app/build/ndk/debug/obj/local/armeabi-v7a/objs/webapp//Users/me/Project/app/src/main/jni/jni_part.o'.  
Stop.

What do I need to do?

Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# OpenCV
OPENCV_CAMERA_MODULES:=on
OPENCV_INSTALL_MODULES:=on
include .../OpenCV-2.4.5-android-sdk/sdk/native/jni/OpenCV.mk

LOCAL_MODULE    := native_part
LOCAL_SRC_FILES := jni_part.cpp
LOCAL_LDLIBS +=  -llog -ldl

include $(BUILD_SHARED_LIBRARY)

Application.mk:

APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi armeabi-v7a
APP_PLATFORM := android-8
JonasVautherin
  • 6,088
  • 6
  • 44
  • 72
fredley
  • 29,323
  • 39
  • 131
  • 223
  • There's a thread here which may give you a tiny bit of insight: https://groups.google.com/forum/#!topic/adt-dev/-GbnrQA8f7M – Scott Barta Jan 13 '14 at 17:51

6 Answers6

123

Gradle Build Tools 2.2.0+ - The closest the NDK has ever come to being called 'magic'

In trying to avoid experimental and frankly fed up with the NDK and all its hackery I am happy that 2.2.x of the Gradle Build Tools came out and now it just works. The key is the externalNativeBuild and pointing ndkBuild path argument at an Android.mk or change ndkBuild to cmake and point the path argument at a CMakeLists.txt build script.

android {
    compileSdkVersion 19
    buildToolsVersion "25.0.2"

    defaultConfig {
        minSdkVersion 19
        targetSdkVersion 19

        ndk {
            abiFilters 'armeabi', 'armeabi-v7a', 'x86'
        }

        externalNativeBuild {
            cmake {
                cppFlags '-std=c++11'
                arguments '-DANDROID_TOOLCHAIN=clang',
                        '-DANDROID_PLATFORM=android-19',
                        '-DANDROID_STL=gnustl_static',
                        '-DANDROID_ARM_NEON=TRUE',
                        '-DANDROID_CPP_FEATURES=exceptions rtti'
            }
        }
    }

    externalNativeBuild {
        cmake {
             path 'src/main/jni/CMakeLists.txt'
        }
        //ndkBuild {
        //   path 'src/main/jni/Android.mk'
        //}
    }
}

For much more detail check Google's page on adding native code.

After this is setup correctly you can ./gradlew installDebug and off you go. You will also need to be aware that the NDK is moving to clang since gcc is now deprecated in the Android NDK.

Android Studio Clean and Build Integration - DEPRECATED

The other answers do point out the correct way to prevent the automatic creation of Android.mk files, but they fail to go the extra step of integrating better with Android Studio. I have added the ability to actually clean and build from source without needing to go to the command-line. Your local.properties file will need to have ndk.dir=/path/to/ndk

apply plugin: 'com.android.application'

android {
    compileSdkVersion 14
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "com.example.application"
        minSdkVersion 14
        targetSdkVersion 14

        ndk {
            moduleName "YourModuleName"
        }
    }

    sourceSets.main {
        jni.srcDirs = [] // This prevents the auto generation of Android.mk
        jniLibs.srcDir 'src/main/libs' // This is not necessary unless you have precompiled libraries in your project.
    }

    task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
        def ndkDir = android.ndkDirectory
        commandLine "$ndkDir/ndk-build",
                '-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
                '-j', Runtime.runtime.availableProcessors(),
                'all',
                'NDK_DEBUG=1'
    }

    task cleanNative(type: Exec, description: 'Clean JNI object files') {
        def ndkDir = android.ndkDirectory
        commandLine "$ndkDir/ndk-build",
                '-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
                'clean'
    }

    clean.dependsOn 'cleanNative'

    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn buildNative
    }
}

dependencies {
    compile 'com.android.support:support-v4:20.0.0'
}

The src/main/jni directory assumes a standard layout of the project. It should be the relative from this build.gradle file location to the jni directory.

Gradle - for those having issues

Also check this Stack Overflow answer.

It is really important that your gradle version and general setup are correct. If you have an older project I highly recommend creating a new one with the latest Android Studio and see what Google considers the standard project. Also, use gradlew. This protects the developer from a gradle version mismatch. Finally, the gradle plugin must be configured correctly.

And you ask what is the latest version of the gradle plugin? Check the tools page and edit the version accordingly.

Final product - /build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.

// Running 'gradle wrapper' will generate gradlew - Getting gradle wrapper working and using it will save you a lot of pain.
task wrapper(type: Wrapper) {
    gradleVersion = '2.2'
}

// Look Google doesn't use Maven Central, they use jcenter now.
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

Make sure gradle wrapper generates the gradlew file and gradle/wrapper subdirectory. This is a big gotcha.

ndkDirectory

This has come up a number of times, but android.ndkDirectory is the correct way to get the folder after 1.1. Migrating Gradle Projects to version 1.0.0. If you're using an experimental or ancient version of the plugin your mileage may vary.

Community
  • 1
  • 1
Cameron Lowell Palmer
  • 17,859
  • 4
  • 102
  • 114
  • 2
    One tweak I had to do: define the tasks outside of the android block. Otherwise gradle complained. – Utkarsh Sinha Dec 25 '14 at 13:26
  • 1
    You can use `def ndkDir = android.plugin.ndkFolder` as a simpler way of getting a reference to the android plugin. – Ben Lings Jan 02 '15 at 13:54
  • @UtkarshSinha that seems wrong. Can you verify that you're running a recent gradle? – Cameron Lowell Palmer Jan 02 '15 at 14:41
  • 1
    I am seeing the same problem as @UtkarshSinha or something similar. The tasks are underlined in grey and the following message is shown: "Cannot infer argument types". Gradle is up-to-date (2.2.1), and I believe the problem is elsewhere as in sourceSets.main the symbols jni and jniLibs are underlined with the message "Cannot resolve symbol". When I move the tasks outside the Android blocks no error is shown, but I don't think this can be right. – Oliver Hausler Jan 11 '15 at 19:37
  • Are you sure you're using the gradle you think you are? – Cameron Lowell Palmer Jan 11 '15 at 20:45
  • @CameronLowellPalmer I don't understand what you did here. Which file should be edited, and what should be added? can you please take a look at my library here: https://github.com/AndroidDeveloperLB/AndroidJniBitmapOperations ? I've tried to do what you wrote, but I think some of what you wrote need to be changed in my case. – android developer Jan 16 '15 at 08:10
  • @androiddeveloper Once I corrected the gradle-wrapper.properties to use gradle-2.2-all.zip everything went well. – Cameron Lowell Palmer Jan 16 '15 at 10:04
  • @CameronLowellPalmer I don't understand the things you've written. Can you please post to Github what should be changed? – android developer Jan 16 '15 at 10:05
  • @androiddeveloper check your pull requests. I have made two commits. – Cameron Lowell Palmer Jan 16 '15 at 10:54
  • @UtkarshSinha I would check the updates I made to the answer for the purposes of clarification. This might allow you to correctly configure your project. – Cameron Lowell Palmer Jan 16 '15 at 11:16
  • @GregEnnis I'll be glad just to get the highest score. :) – Cameron Lowell Palmer Jan 23 '15 at 08:15
  • I have an issue: android.plugin.ndkFolder returns 'null'. How can I define my ndk path? Thank you. – jmrodrigg Jan 27 '15 at 11:54
  • Solved: I didn't add the ndk.dir in local.properties. Now I'm dealing with the problem "Current NDK support is deprecated." – jmrodrigg Jan 27 '15 at 12:36
  • That isn't a problem. That is an informational warning. Google may finally improve NDK support. – Cameron Lowell Palmer Jan 27 '15 at 12:44
  • Thank you, you were right, the problem was elsewhere. I activated the stacktrace and realized I need to run 'ndk-build.cmd'. I forgot to put the extension. Now it works perfectly. – jmrodrigg Jan 27 '15 at 13:06
  • Yes. Now I'm looking for something to run different commandLine depending on the OS... any ideas? – jmrodrigg Jan 27 '15 at 21:00
  • @CameronLowellPalmer Thank you. – jmrodrigg Jan 29 '15 at 10:37
  • @jmrodrigg nothing comes to mind. Cross platform is tough. The biggest problem you would face might be the locating of the tools necessary. I think at some point a README and user intervention is required. – Cameron Lowell Palmer Jan 29 '15 at 10:51
  • @jmrodrigg you can use org.apache.tools.ant.taskdefs.condition.Os, like I did in this gist: https://gist.github.com/ph0b/9e59058ac59cac104398. Also, if ndk-build has been added to user's path, there is no need to specify its location. – ph0b Apr 27 '15 at 07:57
  • :) Once you find yourself using ant, haven't you failed? – Cameron Lowell Palmer Apr 28 '15 at 08:59
  • 3
    I had to add `def ndkDir = plugins.getPlugin('com.android.application').sdkHandler.ndkFolder` for it to work – SztupY Jun 15 '15 at 19:45
  • Thanks SztupY! AStudio build 141.2006197 was complaining about not finding the plugins. Your suggestion solved that problem! – Zoccadoum Jun 17 '15 at 20:34
  • +1 to SztupY as well. I'm on android-gradle plugin 1.2.3 & gradle 2.4, `android.plugin.ndkFolder` results in plugin property not found. I was building a library so plugins.getPlugin('com.android.library').sdkHandler.ndkFolder helped. @SztupY, where in the world do you find the documentation for that plugin? How did you know what properties are in there? – Some Noob Student Jun 19 '15 at 21:54
  • @SomeNoobStudent I was just googling around and found a similar thread somewhere else. What they wrote didn't work at all, but this particular line was useful here – SztupY Jun 19 '15 at 23:10
  • @CameronLowellPalmer - Yes command line (ndk_build.sh) worked fine but gradle build was complaining until I added SztupY's suggestion. – Zoccadoum Jun 23 '15 at 00:03
  • @CameronLowellPalmer How do I change this solution to use 'NDK_DEBUG=1' for the debug build type and 'NDK_DEBUG=0' for the release build type? – DeathlessHorsie Jul 26 '15 at 11:06
  • @DeathlessHorsie one way would be to create two separate tasks, you could name them buildNativeRelease and buildNativeDebug for example. – Cameron Lowell Palmer Jul 27 '15 at 11:54
  • @CameronLowellPalmer yes, I tried it already. I tried this code: tasks.whenTaskAdded { task -> if(task.getName().contains("generateReleaseSources")) { task.dependsOn buildNativeRelease } else if(task.getName().contains("generateDebugSources")) { task.dependsOn buildNativeDebug } } I see two problems: 1. when I config the library to Debug, it also runs the release task. 2. when I build after switching build type it doesn't compile the native code again so the debug/release *.so files remain. Sometimes it does rebuild the native code. – DeathlessHorsie Jul 27 '15 at 15:35
  • @DeathlessHorsie Try this: tasks.whenTaskAdded { task -> if (task.name == 'compileDebugNdk') { task.dependsOn buildNativeDebug } } Let me know if it works. – Cameron Lowell Palmer Jul 28 '15 at 13:06
  • @CameronLowellPalmer making compileDebugNdk depend on buildNativeDebug seems like a better choice than generateDebugSources. However, it doesn't solve my problems. 1. Seems to be a limitation of android studio/gradle. There's an option to make the specific module from a context menu in the project view in studio. This option runs only the debug task. 2. This seems to be a limitation of ndk-build. It simply doesn't recognize that the NDK_DEBUG option changed. – DeathlessHorsie Jul 28 '15 at 16:09
  • @DeathlessHorsie as I understand it, you've implemented the above solution for a separate compileDebugNdk and compileReleaseNdk with the debug flag missing for the latter, but the problem is that it doesn't recompile when switching between debug and release, correct? You could make the NDK portion always clean before compile, not ideal, but this is the same issue if you were trying a similar thing at the command line with make. BTW I compile using ./gradlew assembleDebug or assembleRelease – Cameron Lowell Palmer Jul 28 '15 at 17:05
  • @CameronLowellPalmer thanks I updated the build and it appears to work correctly. – Zoccadoum Jul 30 '15 at 17:11
  • How do I define `sourceSets.main` when I am using the experimental plugin? – Shailen Oct 30 '15 at 12:05
  • 2
    You, sir, deserve a cookie! – dbm Dec 20 '15 at 06:30
  • 1
    The `This is not necessary unless you have precompiled libraries in your project.` comment is misleading. I had to added it, or otherwise, the generated shared object would not go into the final apk file. – Torsten Robitzki Jan 29 '16 at 09:37
  • You can find the NDK directory through the environmental variable [`NDK_ROOT_DIRCTORY`](http://groups.google.com/forum/#!msg/android-ndk/qZjhOaynHXc/2ux2ZZdxy2MJ). If Android Studio is ignoring the value then you should file a bug report. – jww Oct 15 '16 at 12:54
  • @jww you should upgrade to 2.2.1 and forget about NDK_ROOT_DIRECTORY. – Cameron Lowell Palmer Oct 15 '16 at 12:55
  • @CameronLowellPalmer - As far as I know, the NDK team still recommends it. If Android Studio is ignoring it, then file a bug report. Its sounds like Android Studio is just making crap up as they go. – jww Oct 15 '16 at 12:58
  • @jww actually, I do most compilation through straight gradle at the command-line. The gradle 2.2.1 android plugin clears up most of the NDK related issues that forced you to manually call ndk-build and adds cmake support. – Cameron Lowell Palmer Oct 15 '16 at 13:00
  • instead of hiding the C++ sources, I prefer to simply disable the complieXxxNdk tasks, by adding the following enchantment in **build.gradle**: `tasks.all { task -> if (task.name.contains('compileDebugNdk') || task.name.contains('compileReleaseNdk')) { task.enabled = false } }` – Alex Cohn Dec 07 '16 at 15:11
  • @AlexCohn missing the context of that comment – Cameron Lowell Palmer Dec 07 '16 at 15:13
  • I disable these tasks instead of writing `sourceSets.main { jni.srcDirs = [] }`, see http://stackoverflow.com/a/32640823/192373 – Alex Cohn Dec 07 '16 at 15:19
  • @AlexCohn ah, yes. However, that isn't needed anymore if you upgrade to build tools to 2.2.x+ – Cameron Lowell Palmer Dec 07 '16 at 15:20
  • There are still situations when `externalNativeBuild` cannot be used – Alex Cohn Dec 07 '16 at 15:23
  • @AlexCohn yes, I'm sure. Just most people probably don't need anything else and it is the way forward! :) – Cameron Lowell Palmer Dec 07 '16 at 15:25
73

gradle supports ndk compilation by generating another Android.mk file with absolute paths to your sources. NDK supports absolute paths since r9 on OSX, r9c on Windows, so you need to upgrade your NDK to r9+.

You may run into other troubles as NDK support by gradle is preliminary. If so you can deactivate the ndk compilation from gradle by setting:

sourceSets.main {
    jni.srcDirs = []
    jniLibs.srcDir 'src/main/libs'
}

to be able to call ndk-build yourself and integrate libs from libs/.

btw, you have any issue compiling for x86 ? I see you haven't included it in your APP_ABI.

ph0b
  • 13,953
  • 4
  • 39
  • 38
  • 2
    Good answer! You made my day! =) another solution, I just have not found. – Vlad Hudnitsky Feb 02 '14 at 21:20
  • 1
    Experienced the same issue myself. I solved it by adding an empty .c file into the ./app/src/main/jni directory (I named the file "utils.c", but you can call it whatever you like...). Ever since, all works fine. I didn't change anything into the Gradle settings file. – GeertVc Sep 17 '14 at 06:13
  • 4
    What parent should I put this under? I've tried a couple of places and I get "cannot resolve symbol: jni". – Andrew Wyld Jan 19 '15 at 10:48
  • you should put it under `android { }` – ph0b Jan 20 '15 at 13:00
  • That worked for me. Can someone please explain the answer in bit details ? – Amit Gupta Jul 06 '15 at 05:57
  • 1
    You can get more details from my article: http://ph0b.com/android-studio-gradle-and-ndk-integration/ – ph0b Jul 06 '15 at 06:52
8

In my case, I'm on Windows and following the answer by Cameron above only works if you use the full name of the ndk-build which is ndk-build.cmd. I have to clean and rebuild the project, then restart the emulator before getting the app to work (Actually I imported the sample HelloJni from NDK, into Android Studio). However, make sure the path to NDK does not contain space.

Finally, my build.gradle is full listed as below:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        applicationId "com.example.hellojni"
        minSdkVersion 4
        targetSdkVersion 4

        ndk {
            moduleName "hello-jni"
        }

        testApplicationId "com.example.hellojni.tests"
        testInstrumentationRunner "android.test.InstrumentationTestRunner"
    }
    sourceSets.main {
        jni.srcDirs = [] // This prevents the auto generation of Android.mk
//        sourceSets.main.jni.srcDirs = []
        jniLibs.srcDir 'src/main/libs' // This is not necessary unless you have precompiled libraries in your project.
    }

    task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
        def ndkDir = android.plugin.ndkFolder
        commandLine "$ndkDir/ndk-build.cmd",
                '-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
                '-j', Runtime.runtime.availableProcessors(),
                'all',
                'NDK_DEBUG=1'
    }

    task cleanNative(type: Exec, description: 'Clean JNI object files') {
        def ndkDir = android.plugin.ndkFolder
        commandLine "$ndkDir/ndk-build.cmd",
                '-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
                'clean'
    }

    clean.dependsOn 'cleanNative'

    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn buildNative
    }

}


dependencies {
    compile 'com.android.support:support-v4:21.0.3'
}
Brian Ng
  • 854
  • 10
  • 13
7

Android Studio 2.2 came out with the ability to use ndk-build and cMake. Though, we had to wait til 2.2.3 for the Application.mk support. I've tried it, it works...though, my variables aren't showing up in the debugger. I can still query them via command line though.

You need to do something like this:

externalNativeBuild{
   ndkBuild{
        path "Android.mk"
    }
}

defaultConfig {
  externalNativeBuild{
    ndkBuild {
      arguments "NDK_APPLICATION_MK:=Application.mk"
      cFlags "-DTEST_C_FLAG1"  "-DTEST_C_FLAG2"
      cppFlags "-DTEST_CPP_FLAG2"  "-DTEST_CPP_FLAG2"
      abiFilters "armeabi-v7a", "armeabi"
    }
  } 
}

See http://tools.android.com/tech-docs/external-c-builds

NB: The extra nesting of externalNativeBuild inside defaultConfig was a breaking change introduced with Android Studio 2.2 Preview 5 (July 8, 2016). See the release notes at the above link.

0xC0000022L
  • 18,189
  • 7
  • 69
  • 131
Ronnie
  • 121
  • 1
  • 3
  • Could you elaborate on "Though, we had to wait til 2.2.3 for the Application.mk support"? Is that Preview3, or.. ? – Sebastian Roth Jun 30 '16 at 06:24
  • @SebastianRoth : Yes it is preview 3. As of today August 8 2015, Android Studio 2.2 Beta has been released which provides support for external native build. – Vyshakh Amarnath Aug 10 '16 at 09:00
6

My issue on OSX it was gradle version. Gradle was ignoring my Android.mk. So, in order to override this option, and use my make instead, I have entered this line:

sourceSets.main.jni.srcDirs = []

inside of the android tag in build.gradle.

I have wasted lot of time on this!

Paschalis
  • 10,585
  • 9
  • 45
  • 77
  • 1
    I don't understand, because @CameronLowellPalmer had this specified. Or does gradle interprete `sourceSets.main { jni.srcDirs = [] }` in any way different than `sourceSets.main.jni.srcDirs = []`? I believe your issue was somewhere else and you accidentially fixed it. – Oliver Hausler Jan 11 '15 at 19:49
  • @OliverHausler those two statements are equivalent. – Cameron Lowell Palmer Jan 26 '15 at 10:55
2

In the module build.gradle, in the task field, I get an error unless I use:

def ndkDir = plugins.getPlugin('com.android.application').sdkHandler.getNdkFolder()

I see people using

def ndkDir = android.plugin.ndkFolder

and

def ndkDir = plugins.getPlugin('com.android.library').sdkHandler.getNdkFolder()

but neither of those worked until I changed it to the plugin I was actually importing.

  • You can find the NDK directory through the environmental variable [`NDK_ROOT_DIRCTORY`](http://groups.google.com/forum/#!msg/android-ndk/qZjhOaynHXc/2ux2ZZdxy2MJ). If Android Studio is ignoring the value then you should file a bug report. – jww Oct 15 '16 at 12:54