6

I would like to know whether it is possible to define LOCAL_SRC_FILES in gradle.build ndk {} block.

I am currently using:

dependencies {
    classpath 'com.android.tools.build:gradle:1.3.0'
}

in my top level gradle.build file.

My jni module gradle.build file looks like this:

apply plugin: 'com.android.library'

dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
}

android {
    compileSdkVersion 11
    buildToolsVersion "22.0.1"

    def jniSrc = System.getProperty("user.home") + "/srcs/jni"

    defaultConfig {
        ndk {
            moduleName "core"
            stl "gnustl_shared"
            cFlags "-std=c++11"
        }
    }

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
            jniLibs.srcDirs = ['libs']
            jni.srcDirs = ["${jniSrc}"]
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            jniDebuggable true
        }
    }

    productFlavors {
        x86 {
            ndk {
                abiFilter "x86"
            }
        }
        arm {
            ndk {
                abiFilter "armeabi-v7a"
            }
        }
        mips {
            ndk {
                abiFilter "mips"
            }
        }
    }
}

The reason I am asking is that under my jni sources there is code targeting different platforms, not just Android, but also iOS and WinRT.

I am a bit reluctant to migrate to experimental 'com.android.tools.build:gradle-experimental:0.2.0' but if the aforementioned module solves the problem I could give it a try.

I also wouldn't like to use:

jni.srcDirs = []

and override the creation of Android.mk and thus use my own custom one, given that I am not sure if I could debug C++ natively from Android Studio thereafter (I could be wrong here though, I am definitely not an expert user of Android Studios ndk plugin).

Many thanks in advance,

Manos

mtsahakis
  • 793
  • 1
  • 8
  • 18

1 Answers1

11

With experimental plugin 0.4.0, it is possible to exclude files from NDK build by pattern, e.g.

android.sources {
    main {
       jni.source {
            srcDirs = ["~/srcs/jni"]
            exclude "**/win.cpp"
        }
    }
}

Thanks to Paul Spark!

P.S. (thanks to rajveer): don't miss Build/Clean after you change exclude!

Old answer

Unfortunately this is not supported by current gradle plugins. Even the "experimental" plugin only allows to add directories. I recommend to keep the traditional Android.mk which does this job reliably.

I also recommend not to set jni.srcDirs = [], but rather keep ${jniSrc} to let Android Studio display these files for easy access and syntax highlight. If you set cppFlags and cFlags correctly, you will have full power of cross referencing through headers, too.

The trick is to disable the regular NDK build tasks, and inject a buildNative task instead:

def ndkBuild = android.ndkDirectory
import org.apache.tools.ant.taskdefs.condition.Os
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
    ndkBuild += '.cmd'
}

task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
    commandLine '$ndkBuild', 'NDK_PROJECT_PATH="$jniSrc/..'
}

task cleanNative(type: Exec, description: 'Clean JNI object files') {
    commandLine '$ndkBuild', 'clean', 'NDK_PROJECT_PATH="$jniSrc/..'
}

clean.dependsOn 'cleanNative'

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

tasks.all {
    task -> if (task.name.contains('compileDebugNdk') || task.name.contains('compileReleaseNdk')) task.enabled = false
}

Similar approach work for 'com.android.tools.build:gradle-experimental:0.2.0', but task matching is different:

tasks.all {
    task ->
        if (task.name.startsWith('compile') && task.name.contains('MainC')) {
            task.enabled = false
        }
        if (task.name.startsWith('link')) {
            task.enabled = false
        }
        if (task.name.endsWith("SharedLibrary") ) {
            task.dependsOn buildNative
        }
}

UPDATE

buildNative does not produce a debuggable setup. Specifically, when running Android Native debug configuration, Android Studio complains that it Could not locate folder containing object files with symbols within module app.

I suggest the following workaround, which I only tested in scenario when the native sources are split in (at least) two directories: the Android-specific files (I will call them JNI bridge) are in a separate directory, and the rest are elsewhere. The workaround involves building a static library with ndk-build and linking it with the minimal set of objects that will pull all necessary symbols from that library.

For simplicity, let us assume that the Android-specific files (Application.mk, Android.mk, and "android-jni.cpp" are in the directory ~/srcs/jni, while the platform-independent files are in ~/srcs and its other subdirectories.

Here is the relevant fragment of build.gradle:

def LOCAL_MODULE = "staticLib"
def appAbi = "armeabi-v7a"
def ndkOut = "build/intermediates/$LOCAL_MODULE"
def staticLibPath = "$ndkOut/local/$appAbi/lib${LOCAL_MODULE}.a"
task buildStaticLib(type: Exec, description: 'Compile Static lib via NDK') {
    commandLine "$ndkBuild", "$staticLibPath", "NDK_PROJECT_PATH=~/srcs", "NDK_OUT=$ndkOut", "APP_ABI=$appAbi", "APP_STL=gnustl_static"
}

tasks.all {
    task ->
        if (task.name.startsWith('link')) {
            task.dependsOn buildStaticLib
        }
}

model {
    android.ndk {
        moduleName = "hello-jni"
        abiFilters += "$appAbi".toString()
        ldFlags += "$staticLib".toString()
        ldLibs += "log"
        cppFlags += "-std=c++11"
    }

    android.sources {
        main.jni.source {
            srcDirs = ["~/srcs/jni"]
        }
}
}

The ~/srcs/Android.mk file may look like this:

LOCAL_PATH := $(call my-dir)/..

include $(CLEAR_VARS)

LOCAL_MODULE    := staticLib
LOCAL_SRC_FILES := HelloJni.cpp

LOCAL_CPPFLAGS += -std=c++11

include $(BUILD_STATIC_LIBRARY)

It is important for LOCAL_MODULE in Android.mk to fit the name you use for LOCAL_MODULE in build.gradle.

UPDATE 2

It may still be possible, thanks to jforce, see "Link individual native source file to Android Studio project"!

Community
  • 1
  • 1
Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
  • Hi Alex, many thanks for the reply. If I use this approach will I be able to debug C++ from Android Studio? – mtsahakis Sep 18 '15 at 13:22
  • Unfortunately, this straightforward approach fails to launch native debug (symbol information is not available). But with just one more step of complication, native debugging can be achieved. I will post an update as soon as I have a clean setup. – Alex Cohn Sep 19 '15 at 12:19
  • @Pankaj: be careful with the DSL: I got message `could not find property "android"` when I tried to write **model.android.sources {…}** instead of **model { android.sources {…} }** – Alex Cohn Sep 23 '15 at 18:57
  • @Alex Cohn: did you do anything else to get the exclude tag to work, and which platform was this on? I don't seem to be able to get it to work on either Windows or OSX; I've tried relative and absolute paths, patterns with a specific file, directories e.t.c. I'm using experimental plugin 0.4.0. – Rajveer Dec 10 '15 at 22:00
  • @Rajveer: I saw it working both on Mac and on Windows with 0.3.0. I am not using this feature in my current project, so I don't know much about its behavior. – Alex Cohn Dec 11 '15 at 13:34
  • @AlexCohn: Hmm, I wonder if something has changed between 0.3.0 and 0.4.0 then. Thank you for the reply! – Rajveer Dec 12 '15 at 19:50
  • @Rajveer: with AS 1.5.1, experimental 0.4.0, and `buildToolsVersion = "23.0.2"`` on Mac. **exclude M*.cpp** does work for me. – Alex Cohn Dec 12 '15 at 22:04
  • @AlexCohn: This is so strange, I just can't get it to work. I've tried with the same settings (AS 1.5.1, exp 0.4.0, buildToolsversion 23.0.2). I've built a test project by importing the native activity sample, could you do me a huge favour and see if the files in the exclude directory are excluded? It would be a big help! https://www.dropbox.com/s/hmthmeez1vgcm1k/nativeactivity.zip?dl=0 – Rajveer Dec 13 '15 at 02:49
  • 1
    @Rajveer: it works for me like charm. Now, maybe you miss the point of *works like charm*: AS still shows and lets me edit the excluded file, but does not compile it to `app/build/intermediates/objectFiles/armeabi-v7aDebugArm7Native-activitySharedLibrary/native-activityMainCpp/dw4leoehkvku3fcud7ljazweo`. Furthermore, if `source_file_1.o` is stuck there from the previous build, the linker fails. **Build/Clean Project** cures this. – Alex Cohn Dec 13 '15 at 07:51
  • @AlexCohn: I thought that the files might be included in the editor even if not compiled and was still not getting the correct behaviour, however I've just checked and it was because I thought that Rebuilt Project would perform a Clean first. Cleaning first then Making/Rebuilding works as you say. I've never used an IDE before that doesn't perform a Clean as part of the Rebuild...either way, thank you for testing! – Rajveer Dec 13 '15 at 20:24
  • 1
    That's strange. Yours truly and some other people believed that in Android Studio Clean and Rebuild [are synonyms](http://stackoverflow.com/questions/24083706/difference-between-clean-project-and-rebuild-project-in-android-studio#comment55248345_24083753). – Alex Cohn Dec 13 '15 at 20:33