Android library - how to assemble jar with dependencies using gradle? - android

I am trying to migrate my android library project from ant to gradle and I am totally stuck with including project dependencies in final jar + excluding some other resources (mostly android auto generated classes like BuildConfig, etc) from it.
My ant jar task looks like this:
<jar destfile="${dist}/lib/LIB_NAME.jar"
basedir="${build.dir}/classes"
includes="com/**"
excludes="**/R.class, **/R$*.class, **/Manifest*.class">
<manifest>
<attribute name="Main-Class" value="PACKAGE"/>
</manifest>
</jar>
I would like to reach similar effect using gradle + include subset of dependencies in final jar. My current approach is based on Jake Wharton solution, which does not bundle dependencies:
android.libraryVariants.all { variant ->
def name = variant.buildType.name
if (name.equals(com.android.builder.BuilderConstants.DEBUG)) {
return; // Skip debug builds.
}
def task = project.tasks.create "jar${name.capitalize()}", Jar
task.dependsOn variant.javaCompile
task.from variant.javaCompile.destinationDir
artifacts.add('archives', task);
}
Any easy solutions how to modify it to include dependencies + exclude other content? Or maybe there is some more standard way of achieving my goals - I expected it more easy in gradle.

Related

Lazy Android dependencies. How to build AARs libraries before gradle dependency resolution

I have this simplified project schema of a closed source group of libraries:
- app
> references library1, library2
- library1
- library2
> references library3
- library3
All 3 libraries produce aar files which can be referenced in the app dependencies as usual.
dependencies {
implementation project(":library1")
implementation project(":library2")
}
The problem start when I want to test my app with obfuscated aars (builded in release mode). Currently the Android Plugin ignores this so the only way i've found is to do this with pre-built aars.
dependencies {
//for debug build just use the local project
debugImplementation project(":library1")
debugImplementation project(":library2")
//for release build use manually added aar in libs folder
releaseImplementation (name: 'library1-release', ext: 'aar')
releaseImplementation (name: 'library2-release', ext: 'aar')
//i need to add library3 too otherwise it will not find
//method referenced there because aar are not bundled togheter
//by default
releaseImplementation (name: 'library3-release', ext: 'aar')
}
And this works fine.
To do this I've created a small script like this (in app/scripts/build-aar.sh)
//move from scripts folder to root project folder
cd "$(dirname "$BASH_SOURCE")/../../"
//assembleRelease all the aar (which enables Proguard obfuscation)
./gradlew clean assembleRelease --info &&
//copy them in the app libs folder
cp -rf library1/build/outputs/aar/library1-release.aar app/libs/library1-release.aar
cp -rf library2/build/outputs/aar/library2-release.aar app/libs/library2-release.aar
cp -rf library3/build/outputs/aar/library3-release.aar app/libs/library3-release.aar
The problem with this approach is that I need to version all the aar in git, otherwise the app doesn't compile when i select "Release" as build variant.
While I would to gitignore all the aar in the libs folder and build them on the fly before app search for it's dependencies.
I've tried with something like this:
applicationVariants.all { variant ->
if (variant.buildType.name == "release") {
variant.preBuildProvider.configure {
dependsOn(buildReleaseAar)
}
}
}
task buildReleaseAar {
dependsOn (
':library1:assembleRelease',
':library2:assembleRelease',
':library3:assembleRelease'
)
doLast {
//copy the aars from build folders in app/libs folder
}
}
But the dependencies are checked before so I can't even sync the project anymore without having the aars in the libs folder.
The pseudo-logic for solve this problem should be:
1) Having a new task for the dependencies like (obfuscatedAar)
2) Having this task to check if there are aars in the libs folder, if not to run assembleRelease on all 3 libraries and copy resulting AARs there
Something like this:
configurations {
releaseObfuscatedAar
}
dependencies {
//other implementations libs
//for debug build just use the local project
debugImplementation project(":library1")
debugImplementation project(":library2")
releaseObfuscatedAar('library1')
releaseObfuscatedAar('library2')
releaseObfuscatedAar('library3')
}
//and some other task that build the aar before checking if
//dependency is present
Questions:
Is this possible?
Is this a good approach to test my proguard-rules added in libraries projects (and be sure I didn't broke public API when
obfuscation is on)?
Reference to same question in Gradle Forum
I am aware my answer comes a bit late.
I faced a similar issue and I solved it by relying on the local maven repository, being the default one located in ~/.m2.
My approach at the beginning was basically making my :app:assemble${variant.name} task depend on :my-library:publishToMavenLocal.
This approach kind of works, but the dependencies {...} block is still evaluated before the library puts its aar under the local maven repository. Also, the assemble tasks are not run if you just sync the project in Android Studio/IntelliJ.
Therefore this is what I did in the end:
tasks.getByPath(":prepareKotlinBuildScriptModel").dependsOn(":library:publishToMavenLocal")
which makes the local maven repo be populated with your aar when syncing the project, so the dependency on those aars is satisfied in a later step.
Don't forget to add mavenLocal() in your repositories {...} closure, if you want to give it a try.
Of course, this solution comes with a cost, which is the local maven deploy on every sync. In my case, it's relatively fast (due to the gradle cache), but just saying :)

Android studio / Gradle javadoc task

I've been struggling to setup a gradle task to generate Javadocs for my Android library, but when there are external dependencies to other libraries, doc generation fails. This seems to be a common task, but somehow there doesn't seem to be an easy solution, as for example this answer will reveal (re-generating exploded-aar manually is a bit absurd, and also, on Android Studio 3.0 even that doesn't work anymore due to the new dependency directives).
However, I have noticed that generating Javadoc through the Android Studio GUI (Tools menu) works just fine - dependencies to other libraries are resolved etc. So how does this work - does this menu not utilize a gradle task for generating Javadoc?
Since I need to generate Javadoc using gradle as part of CI I find it very frustrating that there is no documented way of getting it to work, while there is a way that works through the menues. Doesn't the Android Studio Tools -> Generate Javadoc menu in turn use a gradle task? Since dependencies are listed with gradle files, and the Javadoc tools menu apparently is able to resolve those dependencies - how is it implemented? How does it source the jars embedded in the dependant aar libraries, etc? How can it be used stand-alone and not though the Android Studio GUI?
Maybe you have got the solution to this. Just in case not, below is how I generate API doc for my Jenkins CI.
task generateApiDoc() {
group "reporting"
description "Generates Javadoc."
}
android.libraryVariants.all { variant ->
// Only consider release
if (variant.buildType.name == "release") {
def task = project.tasks.create("generate${variant.name.capitalize()}Javadoc", Javadoc) {
group "ApiDoc"
description "Generates Javadoc for $variant.name."
// Source files from the variant
source = variant.javaCompiler.source
// Classpath from the variant + android.jar
classpath = variant.javaCompiler.classpath + files(prj.android.getBootClasspath()) + files("$buildDir/intermediates/classes/release")
/* add the excluded packages */
exclude "**/R**"
exclude "**/BuildConfig*"
options.windowTitle = "My Library"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.linkSource false
options.author = true
//options.links("http://docs.oracle.com/javase/7/docs/api/", "http://d.android.com/reference");
failOnError false
}
task.dependsOn assemble
generateApiDoc.dependsOn task
}
}
Then run below gradle commands to get your api doc in place of "$buildDir/docs".
./gradlew assembleRelease
./gradlew generateApiDoc
Edit for Gradle Plugin 3.4.1
android.libraryVariants.all { variant ->
def task = project.tasks.create("generate${variant.name.capitalize()}Javadoc", Javadoc) {
title "API Documentation (${project.android.defaultConfig.versionName})"
group "ApiDoc"
description "Generates Javadoc for $variant.name."
// Source files from the variant
source = variant.sourceSets.collect { it.java.sourceFiles }.inject { m, i -> m + i }
// To fix issue: Error: Can not create variant 'android-lint' after configuration ': library: debugRuntimeElements' has been resolved
doFirst {
classpath = project.files(variant.javaCompileProvider.get().classpath.files,
project.android.getBootClasspath())
}
if (JavaVersion.current().isJava8Compatible()) {
options.addStringOption('Xdoclint:none', '-quiet')
}
exclude "**/R"
exclude "**/R.**"
exclude "**/R\$**"
exclude "**/BuildConfig*"
if (JavaVersion.current().isJava8Compatible()) {
options.addStringOption('Xdoclint:none', '-quiet')
}
options.windowTitle = "API Documentation (${project.android.defaultConfig.versionName})"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.linkSource false
options.author = false
failOnError true
}
task.dependsOn "assemble${variant.name.capitalize()}"
generateApiDoc.dependsOn task
}
I use a gradle task that just executes a bash script file, with a single (pretty long) javadoc command.
What you can do is run the Javadoc generation from Android Studio once, then copy the executed javadoc command from the Studio log, with all the right parameters, and automate the execution of the same command in your gradle.
The tool to generate java style documentation is called javadoc and it comes installed in every JDK. You can configure which classes or packages you want to be included, which ones should be excluded and many other options. Type javadoc in a terminal where a JDK is available and you'll get an idea. See also https://docs.oracle.com/javase/9/javadoc/javadoc.htm#JSJAV-GUID-7A344353-3BBF-45C4-8B28-15025DDCC643
After you get to your optimal configuration, you can include a javadoc step in your CI.

Android. Create own Jar Library

I created own library in android studio according https://developer.android.com/studio/projects/android-library.html
But when I went through all steps I got AAR file (not JAR).
How to create Jar library?
Can Jar library contain manifest file with receivers, cervices etc?
How to create Jar library?
Create a suitable Gradle task. This snippet defines one task per build variant:
android.libraryVariants.all { variant ->
def name = variant.buildType.name
def task = project.tasks.create "jar${name.capitalize()}", Jar
task.dependsOn variant.javaCompile
task.from variant.javaCompile.destinationDir
task.baseName = "..."
task.version = "..."
}
(where you would need to fill in the baseName and version values to go into the JAR filename)
Can Jar library contain manifest file with receivers, cervices etc?
No. That is what an AAR is for. For Android app development, you generally use AARs, distributed by artifact repositories, so that you can have manifests/resources/assets in addition to Java code.

How to release one single jar for android?

I want to use the gradle to release Jar for android project. The jar may contain other 3rd jars. I can use the following script to release jar by my source code without 3rd jars:
android.libraryVariants.all { variant ->
def name = variant.buildType.name
if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
return; // Skip debug builds.
}
def task = project.tasks.create "jar${name.capitalize()}", Jar
task.dependsOn variant.javaCompile
task.from variant.javaCompile.destinationDir
artifacts.add('archives', task);
}
Then I found a open source code and it seems can do the release task. Unfortunately, when I add it into my project, the following compile error occurred.
The 'java' plugin has been applied, but it is not compatible with the Android plugins.
PS: I also need merge shared library into the jar if possible.

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
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.
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)?
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!
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
}

Categories

Resources