Gradle create multi project Jar - android

So I've been playing in Gradle and Android Studio now since the early days of their inception. However, I find myself banging my head on the walls far more times then it is worth at times :(.
I have spent a day and a half trying to resolve my current dilemma.
Where I work we use a lot of shared library projects. This means that unlike Gradle's native assumption, my projects are NOT all nested under one parent project. (BUT this is NOT my question) I have gotten this working.
After our projects have become complete and ready to go, it has been asked to create an SDK for the current project infrastructure for external use. Now in the old IntelliJ I would simply generate some JavaDocs and create an Artifact that includes all dependencies, then another on that does not include the dependency jars and name them respectfully.
However, in Gradle this appears to be very difficult, maybe even unsupported, and I can't find anyone else that has done it after more then 10 hours of Googling and trial and error, I finally decided to make a demo project to show exactly what I'm doing and what I'm trying to accomplish.
I need to do a few things.
Generate a Jar file that includes ALL module dependency code and dependent Jar files
Generate a Jar file that includes ALL module dependency code and ZERO dependent Jar files
Generate an AAR file that includes All module dependency code and dependent Jar files as well as resources for launching our Activity if they want to use it.
Generate an AAR file that includes All module dependency code and ZERO Jar files as well as resources for launching our Activity if they want to use it.
So there in lies my problem. Each module when running a Task with (type: Jar) simply generates it's own code. I'm managed to get the dependent Jar files to compile in once, but then there is no regular source code, but I have NEVER been able to get a Module's source code to be included in the Jar file which is my biggest hurdle right now.
Here is a list of Tasks that I have tried without accomplishing this simple task yet.
evaluationDependsOn(':dependencyModule')
task myJar(type: Jar){
appendix = 'myJar'
from android.sourceSets.main.allSource.files
}
task uberJar (type: Jar){
from(configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }) {
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
}
task fatJar(type: Jar, dependsOn: 'compileJava') {
from {
sourceSets.main.output.classesDir
}
// Add all dependencies except for android.jar to the fat jar
from {
configurations.compile.findAll {
it.getName() != 'android.jar'
}.collect {
it.isDirectory() ? it : zipTree(it)
}
}
archiveName = 'fatJar.jar'
}
task jarFat(type: Jar) {
appendix = "fat"
from android.sourceSets.main.java
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.allSource
classifier = 'sources'
}
task clearJar(type: Delete) {
delete 'build/libs/myCompiledLibrary.jar'
}
task makeJar(type: Copy) {
from('build/bundles/release/')
into('build/libs/')
include('classes.jar')
rename ('classes.jar', 'myCompiledLibrary.jar')
}
makeJar.dependsOn(clearJar, build)
task jar(type: Jar) {
from android.sourceSets.main.allJava
}
task deployJar(type: Jar, dependsOn: jar) {
baseName = project.name + '-deploy'
deps = configurations.runtime + configurations.archives.allArtifactFiles
depClasses = { deps.collect { it.isDirectory() ? it : zipTree(it) } }
from(depClasses) {
exclude 'META-INF/MANIFEST.MF'
}
}
task modelJar(type: Jar) {
from sourceSets.main.output
}
task jarWithDependency(type: Jar){
from android.sourceSets.main.classesDir
from {configurations.compile.collect {zipTree(it)}}
}
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.allJava
}
Nothing has quite done the job yet. Any help would be greatly appreciated!!
Thanks in advance for taking the time to look at this.
I have the full functioning Sample Project if anyone would like it, but I don't see an option to upload here so is the link to the Demo project I built. It is very small and very easy to follow. One class or method basically per Project.
Demo Project

Using the application plugin, you can just call "distZip" to get a zip with all the libraries. By default you get a bin directory with a batch file and a shell script to run the program and a lib directory with all the jar files.
You probably want to update the Manifest to include all the necessary libraries like the following (I have additional stuff in mine).
EDIT I removed the references to "project" because it should not be necessary in this instance.
jar.doFirst
{
// aggregate all the jars needed at runtime into a local variable (array)
def manifestClasspath = configurations.runtime.collect { it.name }
// remove duplicate jar names, and join the array into a single string
manifestClasspath = manifestClasspath.unique().join(" ")
// set manifest attributes - mainClassName must be set before it is used here
manifest.attributes.put("Main-Class", mainClassName)
manifest.attributes.put("Class-Path", manifestClasspath)
}
I'm not an Android developer, so you would have to add some additional code for the AAR stuff.

Update from Author.
Ok as time has gone on, I have learned that the best practice is to allow dependency management tools to do their job and not try to package these files as such.
You can create fat jars and fat aar files, there are plugins to help, but it is hacky and doesn't allow the user to exclude transitives properly if they are nested in the compiled product. the exclude is intended for maven server pom files to include or exclude dependency files.
So using a Maven Repo Server is the best way to manage this instead as the FAT compiler plugins are unreliable and break with each gradle update and more importantly limit your user's ability to exclude "some and not all" when it comes to transitives.
So stay away from doing this if you can avoid it and do it the right way I recommend it. I'll leave this post up in case anyone else was heading down this bad path as well and hope you move to the right path of dependency management servers with transitive dependency managed by pom files.

Related

Find cached files of unpacked aar files in gradle script

I have an android project where a library is included (aar). Gradle will unpack and store the files in the gradle cache (.gradle\caches\transforms-2). I need a gradle script where I can retrieve a file from this cache and move it into my project. Does anybody know how this can be done?
The moving part is working. I just need to get the correct path
tasks.register('moveFile', Copy) {
from "${path_to_build_cache}/path/to/folder/file.conf"
into "${project.rootDir.path}/path/to/folder/"
}
EDIT
I have now tried the solution posted by #tim_yates. The problem is that I am now receiving the error CustomMessageMissingMethodException: Could not find method from() for arguments [ZIP 'C:\Users\nthemmer\.gradle\caches\modules-2\files-2.1\path\to\aar on task ':my-project:movefiles' of type org.gradle.api.DefaultTask It seems that the aar file is read correctly but it has not only one file.
Instead of searching the cache, you should be able to use Gradle to find it for you...
Here's an example:
configurations {
aar
}
dependencies {
aar('com.mikhaellopez:circularimageview:4.3.0#aar')
}
tasks.register('moveFile', Copy) {
from(zipTree(configurations.aar.singleFile)) {
include "res/values/values.xml"
}
into project.layout.buildDirectory.dir('here')
}
That copies the file into ./build/here/res/values/values.xml
Edit
So there's probably multiple ways of doing this, but here's one.
Define a configuration that we will use for the single dependency you want a file from, and make compileClasspath extend from it (so the dependency ends up back in the compile classpath where it was previously)
configurations {
aar
compileClasspath.extendsFrom aar
}
Then in the dependencies where you reference the aar, you should be able to use aar instead of compileClasspath
dependencies {
aar('com.mikhaellopez:circularimageview:4.3.0#aar')
}
Then you can use the moveFile task from above, and there will just be a single file
Not 100% sure what you have currently, so not sure how this fits in, but it should give you a good direction.
Here's the full build file which runs on Gradle 7.2 and uses the circularimageview arr off maven central as the test subject
plugins {
id('java')
}
repositories {
mavenCentral()
}
configurations {
aar
compileClasspath.extendsFrom aar
}
dependencies {
aar('com.mikhaellopez:circularimageview:4.3.0#aar')
}
tasks.register('moveFile', Copy) {
from(zipTree(configurations.aar.singleFile)) {
include "res/values/values.xml"
}
into project.layout.buildDirectory.dir('here')
}

How to Build AAR and Sample Application

I'm running into a collection of gradle problems in setting up a multi-module project. I'm trying to produce an AAR that contains an SDK for our customers use. I'm also trying to produce a sample application that uses that AAR both as a development platform internally and as an example for our customers of how to use the platform.
settings.gradle:
include :sdk
include :SampleApplication
build.gradle:
...
// copy the AAR produced by the SDK into the SampleApplication
task import_aar(type: Copy) {
dependsOn ":sdk:build"
from(new File(project(':sdk').getBuildDir(), 'outputs/aar')) {
include '*-release.aar'
rename '(.*)-release.aar', '$1-v1.0.0.aar'
}
into new File(project(':SampleApplication').projectDir, 'aars')
}
...
SampleApplication/build.gradle:
...
repositories {
...
flatDir {
dirs 'aars'
}
}
...
dependencies {
...
// This causes gradle to fail if the AAR hasn't been copied yet
compile 'com.moxiesoft.netagent:moxieMobileSDK:+#aar'
compile project(':moxieMobileSDK')
...
}
So the biggest problem that I'm having right now is getting the import_aar task to run before the compileDebug/ReleaseSources tasks. I've tried adding explicit dependencies to the compile tasks, but I'm apparently not finding the right way to do it.
I've tried putting this in SampleApplication/settings.gradle:
tasks['compileReleaseSources'].dependsOn(':import_aar')
but gradle fails because there's no compileReleaseSources task, even though gradle :SampleApplication:tasks shows one.
I also tried putting similar stuff in settings.gradle, but it also failed with an error that the task compileReleaseSources didn't exist.
I did have limited success by putting this in my SampleApplication/settings.gradle:
tasks['build'].dependsOn(':import_aar')
But that only has the correct affect if I use "gradle build", which doesn't happen if I'm debugging or running from Android Studio.
I was finally able to get this to work by putting the dependsOn on the preBuild task, but I'm still not particularly happy with the solution, because:
It requires me to have the aar in place before gradle runs, which
means I wind up putting the .aar into git, which isn't a
particularly good idea.
I'd rather not have the AAR generation leaking into the
SampleApplication/build.gradle file, since that's intended for
customer usage.
Is there a better way of handling the problem in general?
I also had problem adding a dependency to compileReleaseSources task and described here a solution that worked for me. In short, the dependency need to be added in tasks.whenTaskAdded closure.

Exclude class from resulting dex file

I have a problem. I have a structure of project looking like this:
P
|-SubP1
|-SubP2
There are two stub classes android.media.IRemoteDisplay in both packages. They differ in implementation, but that's not the matter - they will be replaced by system classes when I'll run the app on Android.
However, there is a problem - I can't build the project because dexMerger fails - it says there are two conflicting classes. I can understand that error - after all, there are really two conflicting classes :)
But when I try to exclude these files in build.gradle like this:
sourceSets {
main {
java {
srcDir 'src'
exclude '**/android/media/**'
}
}
}
The compilation fails because it can't find android.media.IRemoteControlDisplay class(and it's nested classes).
How can I still use these classes, but exclude them from resulting DEX file?
Please DON'T question if it's right to exclude the class from compiled project - it's the right thing to do, I already did it, but manually - by pre-compiling SubP1 and SubP2 to jars and then manually removing IRemoteController.class from these jar files and then including those jars in P.
I'll also be satisfied with that solution:
1. Build SubP1
2. Remove IRemoteControlDisplay.class from SubP1.jar
3. Build SubP2
4. Remove IRemoteControlDisplay.class from SubP2.jar
5. Add SubP1.jar and SubP2.jar as dependencies to P
6. Build P
If that's possible, please let me know.
Finally, I've been able to do it.
What I needed was a runtime dependency instead of compilation-time dependency. So, I've created submodule for SubP1 called SubSubP1(for example) moved the android.media.IRemoteDisplay into the SubSubP1, and created submodule SubSubP2 for SubP2, and did the same with stub class. Then I've declared the dependency as following for SubP1 and SubP2:
dependencies {
provided project(':SubP1:SubSubP1')
}
and
dependencies {
provided project(':SubP2:SubSubP2')
}
And that did the trick! Instead of compiling the classes, Gradle assumed that they will be loaded at runtime.

Exclude aidl files from Javadoc task with Gradle

I'm currently discovering Android Studio and Gradle and migrating all the build chain of my project from bash scripts to Gradle configurations. It's probably going to be awesome in the end, but meanwhile I'm struggling.
What I want to do now is quite simple. I have a standard rule to generate Javadoc from my source (I took this snippet from http://snowdream.github.io/blog/android/2013/11/01/how-to-generate-javadocs-with-android-gradle-plugin/):
android.libraryVariants.all { variant ->
task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) {
source = variant.javaCompile.source
def androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar"
classpath = files(variant.javaCompile.classpath.files, androidJar)
options {
links "http://docs.oracle.com/javase/7/docs/api/"
linksOffline "http://d.android.com/reference","${android.sdkDirectory}/docs/reference"
}
exclude '**/BuildConfig.java'
exclude '**/R.java'
}
}
But my project also contains AIDL files and I don't want these aidl files (nor the .java files generated from them) to be included to the Javadoc.
I tried the rule:
exclude "**/$buildDir/**"
... and I tried a thousand others, but none works and my interfaces and Stub are processed into HTML files.
I beg for your help! Thanks a lot.
After hours of research about how Gradle and Groovy work, the best way I found is:
exclude {
it.file.path.contains('aidl')
}
However, I'm still unsatisfied. I do feel something less brutal could be done using variant.aidlCompile.sourceOutputDir that points to the Java files generated by AIDL. But I could not compare the iterated elements in the Closure to this File, or FileTree, or whatever shape I give it...
EDIT:
After further research, another method is to hide the interfaces defined in AIDL using a custom Doclet that supports #hide tag (like Doclava). I preferred this method because it doesn't cause errors during Javadoc generation.

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