Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

define LOCAL_SRC_FILES in ndk{} DSL

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

like image 248
mtsahakis Avatar asked Sep 17 '15 17:09

mtsahakis


People also ask

What is .MK file in Android?

Overview. The Android.mk file resides in a subdirectory of your project's jni/ directory, and describes your sources and shared libraries to the build system. It is really a tiny GNU makefile fragment that the build system parses once or more.

What is NDK tools in Android Studio?

The Native Development Kit (NDK) is a set of tools that allows you to use C and C++ code with Android, and provides platform libraries you can use to manage native activities and access physical device components, such as sensors and touch input.

What is Ndk_project_path?

NDK_PROJECT_PATH - the location of your project NDK_APPLICATION_MK - the path of the Application.mk file APP_BUILD_SCRIPT - the path to the Android.mk file. These are needed to override the default values of the build script, which expects things to be in the jni folder.

What is Local_cflags?

LOCAL_CFLAGS is applied to the current module, and is undefined after an include $(CLEAR_VARS) . APP_CFLAGS is applied to all modules.


1 Answers

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"!

like image 71
Alex Cohn Avatar answered Sep 20 '22 12:09

Alex Cohn