15

In old traditional android ndk we will specify the static library to be linked in the Android.mk file.

Android.mk

PLATFORM_PREFIX := /opt/android-ext/
LOCAL_PATH := $(PLATFORM_PREFIX)/lib
include $(CLEAR_VARS)
LOCAL_MODULE := library
LOCAL_SRC_FILES := library.a
include $(PREBUILT_STATIC_LIBRARY)

LOCAL_STATIC_LIBRARIES := android_native_app_glue library

Here is my Question

I am little bit Confused when switching to Gradle experimental plugin for NDK. Share your ideas on how to link Static library in App build.gradle file.

I had followed the latest gradle experimental plug-in documentation given here.

bonnyz
  • 13,458
  • 5
  • 46
  • 70
Sankar ganesh
  • 225
  • 1
  • 4
  • 12

4 Answers4

10

Have a look at this sample.

  1. Tell the compiler where the headers are (in android.ndk{}):

    CFlags += "-I${file("path/to/headers")}".toString() cppFlags += CFlags

  2. Tell the linker where the .a file is (in android.ndk{} or where defining the flavors - make sure to add abiFilter - for example abiFilters += "armeabi-v7")

    ldFlags += "-L${file(path/to/library.a)}".toString() ldLibs += ["nameOfLibrary"]

    Note that the name of the library by convention is the string after "lib" in the .a file name. For example, for a file named libNative.a you should add ldLibs += ["native"] to gradle.

  3. Create a new module and use apply plugin: 'java' to apply java plugin. In the build.gradle write the necessary code to get and place the .a file in the appropriate directory (where you will get it from your module which is using it). Don't forget to add a dependency in the module using the library (compile project(':libraryModule') in dependencies{}) and to include it in the project in settings.gradle file with include ':libraryModule'. If you want to place the module in a specified by you folder (for example where currently your Android.mk file is), just add project(':libraryModule').projectDir = new File(settingsDir, 'path/to/module').

That should do it.

Nedko
  • 506
  • 5
  • 16
  • Link updated. However, the sample seems to be deleted from the Google sample repository in git. NDK integration has been updated throughout the past months and probably now there is a better way than the one in the sample. – Nedko May 20 '16 at 19:04
  • It does not work on android 2.2. Error message: Could not find ndk() for arguments [blah] on object of type com.android.build.gradle.AppExtension. – r0n9 Oct 11 '16 at 06:50
3

The above answers work around gradle's prior insufficient NDK integration. This answer illustrates new gradle integration with the NDK.

Take a look at this proposed sample written against gradle 2.9 and the android plugin 0.6.0-alpha1. In constrast to how the question is posed, this answer contains a separate project for the library. This functionality can be explored to allow gradle to build that library before it is used by the app project. The other answers rely on the presumption that the library had already been built.

The :secondlib com.android.model.application builds libsecondlib.so (loaded in Java code with System.loadLibrary("secondlib"). The name 'secondlib' is poorly named. I like to think of it as a .so "wrapper" for all other native libraries linked for use by the app.

That shared library is statically linked against firstlib.a as built by the :firstlib com.android.model.native.

The headers are exported from the :firstlib to any dependent projects (:secondlib in this example) as per the exportedHeaders clause. This way dependent projects know how to link against the .so/.a. This replaces the CFlags+="-I/path/to/headers" syntax in a prior answer.

:secondlib links against :firstlib statically as per the following clause:

android.sources {
    main {
         jni {
             dependencies {
                 project ":firstlib" buildType "debug" linkage "static"
             }
         }
         // TODO(proppy): show jniLibs dependencies on .so
    }
}

As shown by the comment, the example is incomplete. The finishing syntax is shown in the 'NDK Dependencies' section of the experimental android plugin documentation. Within that document the syntax for how to statically link instead of dynamically link should be clear.

There are currently some shortcomings (e.g. the buildType of the dependency shown above is by default 'debug' and not the current buildType being built).

EDIT: Here is a work-in-progress sample of the new dependencies syntax in app/build.gradle pulled out of one of my projects:

  android.sources {
    main {
      jni {
        //for exportedHeaders
        dependencies { project ":libfoo" linkage "shared" }
      }
      jniLibs {
        //Where the swig wrapped library .so is. I use swig to create code to interface with libfoo.so within :app
        source { srcDirs 'libs' }
        //for file in $(model.repositories.libs.libfoo)
        dependencies { library "libfoo" }
      }
    }
  }

  repositories {
    libs(PrebuiltLibraries) {
      libevdev {
        //headers already available from our libfoo project via exportedHeaders
        //headers.srcDir "../libfoo/src/main/jni/"
        binaries.withType(SharedLibraryBinary) {
          sharedLibraryFile = file("../libfoo/build/intermediates/binaries/debug/lib/${targetPlatform.getName()}/libfoo.so")
        }
      }
    }
  }
Andrew Smart
  • 351
  • 5
  • 11
  • Its not working. I am having issues with adding Sourcets – Sankar ganesh Apr 05 '16 at 07:20
  • @Sankar sourceSets? It's no longer a part of the model syntax if you read the error message (they changed a few things like that). The 'sourceSets{ main{ jniLibs{ srcDirs.add('jniLibs') }}}' originally went withen the library's build.gradle, to cause the .so files to be copied over to app project. As far as I can tell that functionality moved to the app's build.gradle as shown in the dependencies section in the link above. I added sample implementation of that in my answer, and clarified the answer a bit in that it presumes you have a separate gradle project to build that static library. – Andrew Smart Apr 19 '16 at 08:02
  • 1
    @Sankar Also my apologies, I'd written the answer presuming dynamic linkage instead of static linkage. In my approach the app project constains interface code to the library, this interface code becomes a .so with name 'lib$(model.android.ndk.moduleName).so'. That .so can be linked statically against the library code as built from the library project. This way I believe you'll get unused code from the static library pruned out (and thus app size minimized). I'll try to change my sample code to statically link when I get time (should be simple changes), and further make a more complete example. – Andrew Smart Apr 19 '16 at 08:09
1

Conceptually, apk is zip of manifest,native files and class library. So if you copy the static library to output it will work. So in gradle you should use the copy task and make these library as part of output. I just used below for my so files and it worked.

task copyNativeLibs2h(type: Copy) {
    from(new File(getProjectDir(), 'libs')) { include '**/*.so' }
    into new File(buildDir, 'native-libs')
}
tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn copyNativeLibs2h }
Ashish Rawat
  • 5,541
  • 1
  • 20
  • 17
0

You cannot build a static library with gradle, even with the experimental plugin. You can use a prebuilt library, or build it with ndk-build and link it into the shared object with the gradle plugin.

Here is an example:

Assume that we have this directory structure:

  • project (build.gradle, gradle.properties, etc.)
    • jni_static (Application.mk, Android.mk, cpp files for the static lib)
    • app (build.gradle, src/main, etc.)
      • jni_shared (cpp files for the shared lib)

And here are the relevant pieces of project/app/build.gradle:

// look for NDK directory

import org.apache.tools.ant.taskdefs.condition.Os
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def ndkBuild = properties.getProperty('ndk.dir') + '/ndk-build'
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
    ndkBuild += '.cmd'
}

apply plugin: 'com.android.model.application'

… dependencies, model { compileOptions, etc.

// static lib is built with ndk-build

def LOCAL_MODULE = "static"
def appAbi = "armeabi-v7a"
def ndkOut = "build/intermediates/$LOCAL_MODULE"
def staticLibPath = "$ndkOut/local/$appAbi/lib${LOCAL_MODULE}.a"

// To guarantee that the intermediates shared library is always refreshed,
// we delete it in gradle task rmSO.

task rmSO(type: Delete) {
    delete 'build/intermediates/binaries/debug/lib/armeabi-v7a', 'libshared.so'
    delete 'build/intermediates/binaries/debug/obj/armeabi-v7a', 'libshared.so'
}

// in file jni/Android.mk there is a section for LOCAL_MODULE=static
// which builds the static library

task buildStaticLib(type: Exec, description: 'Compile Static lib via NDK') {
    commandLine "$ndkBuild", "$staticLibPath", "NDK_APPLICATION_MK=../jni_static/Application.mk",
            "NDK_PROJECT_PATH=../jni_static", "NDK_OUT=$ndkOut"
    dependsOn rmSO
}

task cleanNative(type: Exec, description: 'Clean JNI object files') {
    commandLine "$ndkBuild", "clean", "NDK_APPLICATION_MK=../jni_static/Application.mk",
            "NDK_PROJECT_PATH=../jni_static", "NDK_OUT=$ndkOut"
}
clean.dependsOn cleanNative

tasks.all {
    task ->

// link of the shared library depends on build of the static lib
        if (task.name.startsWith('link')) {
            task.dependsOn buildStaticLib
        }

// before build, make sure the intermediate so is not stuck there
        if (task.name.startsWith('package')) {
            task.dependsOn rmSO
        }
}

// build the wrapper shared lib around the static lib using the experimental plugin

model {
    android.ndk {
        moduleName = "shared"
        cppFlags += "-std=c++11"
        ldFlags += "$staticLibPath".toString()
        ldLibs += "log"

        stl = "gnustl_static"
        abiFilters += "$appAbi".toString()
    }

    android.sources {
        main.jni.source {
            srcDirs = ["jni_shared"]
        }
    }
}

Important: the sources for the shared lib should be in a separate directory, so that the sources of the static library are not in it or under it. This because the experimental plugin cannot exclude some files under srcDirs.

Alex Cohn
  • 56,089
  • 9
  • 113
  • 307