linking against .so files in a .aar file using android gradle experimental

2.5k Views Asked by At

I am using the android gradle experimental plugin to build an app module with some native code. This native code uses a library with pre-built .so files, which I am bundling into a .aar file via an android library module. The .aar file builds fine, but how do I link the native code module in the app module to the pre-built .so files in the .aar module? The gradle experimental documentation doesn't mention this scenario.

Also, I'd like to package up include files in the .aar file if possible (although they shouldn't be packaged with the final application).

In /prebuiltlib:

build.gradle
-src/
--main/
---jniLibs/
----libfoo.so

Here are the gradle files:

/prebuiltlib/build.gradle

apply plugin: "com.android.model.library"
model {
    android {
        compileSdkVersion 25
        buildToolsVersion "25.0.3"

        defaultConfig {
            minSdkVersion.apiLevel = 21
            targetSdkVersion.apiLevel = 21
            versionCode 1
            versionName "1.0"

        }

        buildTypes {
            release {
                minifyEnabled false
                proguardFiles.add(file("proguard-rules.pro"))
            }
        }
    }
}

dependencies {
    compile fileTree(dir: "libs", include: ["*.jar"])
    compile "com.android.support:appcompat-v7:25.3.1"
}

Here is /app/build.gradle, note the ??? where I'm not sure what to put:

import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.model.application'

model {
    repositories {
        libs(PrebuiltLibraries) {
            // ??? is this right, and does this go into app/build.gradle or mylib/build.gradle?
            foo {
                binaries.withType(SharedLibraryBinary) {
                    sharedLibraryFile = file('???/libfoo.so')
                }
            }
        }
    }
    android {
        compileSdkVersion = 25
        buildToolsVersion = '25.0.3'

        defaultConfig {
            applicationId = 'com.jeremy.stackoverflow.sample'
            minSdkVersion.apiLevel = 21
            targetSdkVersion.apiLevel = 21
            versionCode = 1
            versionName = '1.0'
        }

        ndk {
            platformVersion = 21
            moduleName = 'native-activity'
            toolchain = 'gcc'
            toolchainVersion = '4.9'
            stl = 'gnustl_shared'
            abiFilters.add('armeabi-v7a')
            ldLibs.addAll(['log',
                           'android',
                           'EGL',
                           'GLESv2'
            ])
            // ??? How do I add include paths from the .aar module?
            cppFlags.add('-I' + file('???/include'))
            cppFlags.addAll(['-std=c++11', '-fexceptions'])
        }

        sources {
            main {
                jni {
                    dependencies {
                        // ??? Is this right?
                        library 'foo' linkage 'shared'
                    }
                }
                jniLibs {
                    source {
                        // ??? Do I need one of these for the libs in the .aar?
                        srcDir file('???/jniLibs')
                    }
                    dependencies {
                        // ??? Is this right?
                        library 'foo'
                    }
                }
            }
        }
        buildTypes {
            release {
                minifyEnabled = false
                proguardFiles.add(file('proguard-rules.pro'))
            }
        }
    }
}

dependencies {
    println rootProject.getName()
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile project(':prebuiltlib')
}
1

There are 1 best solutions below

2
On

Starting with Android Gradle Plugin 4.0, C/C++ dependencies can be imported from AARs linked in your build.gradle file. Gradle will automatically make these available to the native build system, but your build system must be configured to make use of the imported libraries and headers.

On the build.gradle of the app that imports the module add:

In gradle 4.0: add the following to your project's gradle.properties file:

android.enablePrefab=true

In gradle 4.1: add the following to the android block of your module's build.gradle file:

buildFeatures {
  prefab true
}

Then add the dependency on the module that has to be imported with:

dependencies {
    implementation files('libs/nativelib-release.aar')
}

where in this case the lib aar in release version was placed on the app/libs directory.

On the build.gradle of the module that will be imported add:

buildFeatures {
    prefabPublishing = true
}

prefab {
    create("nativelib") {
      headers = "src/main/cpp/include"
    }

    // if you have other libs to expose...
    create("myotherlibrary") {
        headers = "src/main/cpp/myotherlibrary/include"
    } 
}

note that currently only one include path is allowed (where the .h that contain the public API must be placed) and a bug was raised here: https://issuetracker.google.com/issues/168775349

, if your application defines libapp.so and it uses cURL, your CMakeLists.txt should include the following:

add_library(app SHARED app.cpp)

# Add these two lines.
find_package(nativelib REQUIRED CONFIG)
target_link_libraries(app nativelib::nativelib)

app.cpp is now able to #include "curl/curl.h", libapp.so will be automatically linked against libcurl.so when building, and libcurl.so will be included in the APK.

Source: https://developer.android.com/studio/build/native-dependencies

Extra bits:

It may be needed to build the aar module with a shared stl, you can do that adding on the module gradle file the following on the android defaultConfig:

externalNativeBuild {
    cmake {
        cppFlags ""
        arguments "-DANDROID_STL=c++_shared"
    }
}

For Debugging:

the module library will be exposed to the cmake of the app through the variable CMAKE_FIND_ROOT_PATH. use message(${CMAKE_FIND_ROOT_PATH}) to show that path, and inside there will be a file called nativelibConfig.cmake (or another lib/module name). the file contains the following:

if(NOT TARGET nativelib::nativelib)
add_library(nativelib::nativelib SHARED IMPORTED)
set_target_properties(nativelib::nativelib PROPERTIES
    IMPORTED_LOCATION "/Users/bloom/.gradle/caches/transforms-3/44ca3477885179ff4cb5073527c9d262/transformed/nativelib-debug/prefab/modules/nativelib/libs/android.arm64-v8a/libnativelib.so"
    INTERFACE_INCLUDE_DIRECTORIES "/Users/bloom/.gradle/caches/transforms-3/44ca3477885179ff4cb5073527c9d262/transformed/nativelib-debug/prefab/modules/nativelib/include"
    INTERFACE_LINK_LIBRARIES ""
)
endif()

it's visible the library name with the :: format (pretty weird) in case it's difficult to guess the name to use on CMakelists.txt, and the paths exported.

To refresh the editor etc. try build->Refresh linked C++ projects.