Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Exclude Duplicate C Shared Libraries (.so) in a Multi-Project Android Build?

I get a "duplicate files" conflict when building a parent project with two library modules, which make use of the same libc++_shared.so shared library.

(NOTE: Please do not consider this a "duplicate question". I have read several related posts, which have helped me get this far. However, no posts have provided an answer that works in my case involving NDK artifacts.)

The build was working correctly when I only had 1 such library module. The addition of the second library module is now creating the conflict.

Consider the following project structure: 1 parent project, 2 "child" projects - but each project is located at the same directory level (i.e. Not nested hierarchically)

ProjectA/   (Parent)
    LibraryModuleA1/
        build/exploded-aar/com.package.name/
            LibraryModuleB1/<version>/jni/armeabi-v7a/libc++_shared.so
            LibraryModuleC1/<version>/jni/armeabi-v7a/libc++_shared.so
        build.gradle    (bgA1)
    Test_APK_Module A1T/
        build.gradle    (bgA1T)
    build.gradle    (bgPA)

ProjectB/
    LibraryModuleB1/  (Uses NDK)
        build/lib/armeabi-v7a/libc++_shared.so
        build.gradle    (bgB1)
    build.gradle    (bgPB)

ProjectC/
    LibraryModuleC1/  (Uses NDK)
        build/lib/armeabi-v7a/libc++_shared.so
        build.gradle    (bgC1)
    build.gradle    (bgPC)

Library Module A1 depends on both Library Modules B1 & C1.
A1 -> B1
A1 -> C1

Projects B and C both have NDK-based code and build/test correctly. Both depend on the libc++_shared.so shared library.

However, when building Project A, I get the following error during the :LibraryModuleA1:packageDebugTest task:

Error: duplicate files during packaging of APK   /ProjectA/LibraryModuleA1/build/apk/LibraryModuleA1-debug-test-unaligned.apk
    Path in archive: lib/armeabi-v7a/libc++_shared.so
    Origin 1:  /ProjectA/LibraryModuleA1/build/exploded-aar/com.package.name/LibraryModuleB1/<version>/jni/armeabi-v7a/libc++_shared.so
    Origin 2:  /ProjectA/LibraryModuleA1/build/exploded-aar/com.package.name/LibraryModuleC1/<version>/jni/armeabi-v7a/libc++_shared.so
You can ignore those files in your build.gradle:
    android {
      packagingOptions {
        exclude 'lib/armeabi-v7a/libc++_shared.so'
      }
    }

* What went wrong:
Execution failed for task ':LibraryModuleA1:packageDebugTest'.
> Duplicate files copied in APK lib/armeabi-v7a/libc++_shared.so
    File 1: /ProjectA/LibraryModuleA1/build/exploded-aar/com.package.name/LibraryModuleC1/<version>/jni/armeabi-v7a/libc++_shared.so
    File 2: /ProjectA/LibraryModuleA1/build/exploded-aar/com.package.name/LibraryModuleC1/<version>/jni/armeabi-v7a/libc++_shared.so

:LibraryModuleA1:packageDebugTest FAILED

What I've Tried So Far

  1. I attempted to add the suggested closure to my build.gradle file, but which build.gradle file do I add it to? I have added the closure to bgA1, bgB1, and bgC1 (one at a time), with no success.
  2. The suggested closure says to use exclude 'lib/armeabi-v7a/libc++_shared.so'. Each "child" library module builds the libc++_shared.so file under the build/lib path. However, I noticed that the parent library module copies the libc++_shared.so file under jni/armeabi-v7a/libc++_shared.so inside the build/exploded-aar directory structure. (See above) Should the closure instead read exclude 'jni/armeabi-v7a/libc++_shared.so (i.e. jni vs. lib)?
  3. Since I am using Gradle plugin 0.9.1, I tried using pickFirst in place of exclude, but that wasn't successful either.

Can someone help determine how I should configure the `packagingOptions' closure for my given case?

Thank you for your help!

like image 730
Jack Straw Avatar asked Jun 19 '14 19:06

Jack Straw


1 Answers

I ran into the same problem and had no luck with exclude or pickFirst. So I used a somewhat ugly workaround. The idea is to create a 'native-libs' folder in the build directory of the main project, copy all required *.so files from ndk library projects there and then tell the build system to package those libs in the apk.

In my main project (the app project), I explicitely define the list of modules that contain ndk codes on which I depend

// Ndk stuff. We have to explicitely manage our NDK dependencies
ext.jniProjects = [project(':ndklib1'), project(':ndklib2'), project(':ndklib3')]
apply from: '../depend_ndk.gradle'

And then, 'depend_ndk.gradle' is a gradle external script that contains

// Build helper for projects that depends on a native library with a NDK part
// Define the list of ndk library you depend on in project main file :
//   ext.jniProjects = [project(':ndklib1')]
//   apply from : 'depend_ndk.gradle'
buildscript {
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.12.+'
    }
}
import com.android.build.gradle.tasks.PackageApplication

// As a workaround, we create a new 'native-libs' folder in the current project and
// copy all the .so we depend on into it
def ndkLibsDir = new File(buildDir, 'native-libs')
ndkLibsDir.mkdir()

task copyDependingNativeLibs(type: Copy) {
    // Doc for copy http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.Copy.html
    println 'jniProjects ' + jniProjects
    jniProjects.each {
        from(new File(it.buildDir, 'native-libs')) {
            include '**/*.so'
        }
    }
    into ndkLibsDir
}

tasks.withType(PackageApplication) { pkgTask ->
    pkgTask.jniFolders = new HashSet<File>()
    pkgTask.jniFolders.add(ndkLibsDir)
    pkgTask.dependsOn copyDependingNativeLibs
}
like image 63
Julien Avatar answered Oct 05 '22 20:10

Julien