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.
Related
I am trying to set-up a new project which includes two android library projects (aar). Let's call them foo and bar.
Since this is all new at the time of writing, I am using Android Studio 1.2.2, Android Build Tools 1.1, Gradle Wrapper 2.2.1.
foo depends on bar and in order to have the Gradle build work, I need to specify the dependency from sources like so:
dependencies {
compile project(':bar')
}
I need to publish at least foo to a local maven repository so that it can be picked up by a separate application project baz which depends on the foo. baz is in a completely separate project and Gradle build.
This I have achieved by using the maven plug-in and adding the following to foo's build.gradle:
task uploadArchives(type: Upload) {
repositories.mavenInstaller {
configuration = configurations.getByName(Dependency.DEFAULT_CONFIGURATION)
pom.groupId = "com.example"
pom.artifactId = "foo"
pom.version = "0.0.2-SNAPSHOT"
}
}
And including the maven dependency in the baz build as usual like so:
dependencies {
compile 'com.example:foo:+'
}
Now the problem I'm having is that the the baz application fails at runtime because it is missing the depencencies from bar! (ClassNotFoundException).
Inspecting the foo aar that is published into maven local I can see it doesn't have any of bar in it, neither is bar referenced in the pom for foo.
How does one go about getting Gradle to include the proper dependency information for foo in the pom from the information above?
I tried alternatively to change the build for foo by specifying bar as a maven dependency:
dependencies {
compile 'com.example:bar:+'
}
But in this case the Gradle build for bar and foo both fail at the configuration stage because of course bar can't be found on maven local yet (it hasn't been built!) - remember they are both in a multi-project set-up.
What is the proper way around this?
Are there any pointers for good practices for multi-project and android library project set-ups like this?
I am also confused by the existence of the maven and maven-publishing plug-ins. What is the most appropriate standard to be used for Android Library set-up?
I am sorry for including multiple questions in a single post, I hope as I gain clarity on the right direction I can edit and make this a more helpful question / answer that can help others who are lost as I am.
Thanks!
Well, after a TON of fighting and still not completely solid on the details, I think I found some light on the issue.
From what I gather, specifying bar as a source project dependency of foo is the right thing to do. In theory, the maven plug-in should be able to construct the proper dependencies in the pom file if everything is set-up correctly.
What I was having trouble with is in getting the maven publishing configured properly.
What I was able to conclude so far is that using mavenInstaller things work easily but the pom is incorrectly formed (missing dependencies). If on the other hand I use mavenDeployer, then the pom is generated with dependencies. It only took me much longer to get it to work because it fails with all sorts of exceptions if not set-up properly - and I don't know why.
The one combination that worked for me and hopefully can help others is this:
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
repository(url: mavenLocal().url)
}
}
}
}
I hope this helps other lost souls and helps bring more clarity on this issue
Scenario: We have an Android app with a few different optional components that we would like to be able to include/exclude depending on customer needs and licensing. Is it possible to include specific projects based on a build parameter and without creating all permutations as build flavors?
./gradlew assembleRelease -PincludeFeatureA=true -PincludeFeatureB=false
I thought I could do something like this in dependencies:
dependencies {
if(includeFeatureA){
compile project(':featureAEnabled')
} else {
compile project(':featureADisabled')
}
}
But that doesn't seem to work.
Update: Considering the number of toggle-able features, using explicit build variants for every permutation is cumbersome.
For example, given 3 toggle-able features, I do not want to have to build flavors like this:
Feature1
Feature1-Feature2
Feature1-Feature3
Feature1-Feature2-Feature3
Feature2
Feature2-Feature3
...
The solution for my scenario was to move the if statement out of the dependencies:
Assuming the command line:
gradlew assembleRelease -PincludeFeatureA
At the beginning of the project build.gradle, I include this:
def featureA_Proj=':featureA_NotIncluded'
Then I have a task like this:
task customizeFeatureA(){
if(project.hasProperty('includeFeatureA')){
println 'Including Feature A'
featureA_Proj=':featureA'
}
}
Finally, under dependencies, I just include:
dependencies{
include(featureA_Proj)
}
Use Build Variants. You can enable or disable dependencies on the projects based on them You can even use separate assets or source code with them.
Check out the settings.gradle file, it can be used to indicate which all projects to build, here you might be able to read the settings set and use those.
See
https://docs.gradle.org/current/userguide/build_lifecycle.html
https://docs.gradle.org/current/userguide/multi_project_builds.html
That might help.
When in the root directory of my Android project within the terminal, I expected gradle dependencies to produce a tree with the sub-projects included - but this is not the case.
Is it possible to produce this from the root directory? I don't want to keep going into the sub-projects to get the individual trees.
dependencies is a special built-in task, and it doesn't seem possible to execute it for all projects at once.
EDIT: I found a way:
task allDependencies {
dependsOn allprojects.collect { "$it.path:dependencies" }
}
I have an Android project which (unfortunately) uses Maven as a building manager. The thing is that I have a lot of standard java .jar dependencies.
The first issue I am facing is the following :
1) These jar have other dependencies, and sometimes are redundant. The problem is that the removeDuplicates functionality of the android-maven-plugin does not seem to work as intended and I encounter the classic "multiple dex files define class X". I have resolved this issue by excluding the redundant dependencies one by one, which is very tedious and provides me with a pom which can only be qualified as disgusting. So here is my question : is there any way to easily manage transitive dependencies with android-maven-plugin and avoid the "class defined in multiple dex files" error ? Does Gradle handle this kind of stuff in a better way ? Or am I doomed to use "*" in every dependency exclusions and basically remove the transitive dependency feature from Maven ?
The second issue is about compilation time and the behavior of IDEA when using Maven dependencies in an Android project :
2) First, having to dex that many dependencies is obviously very time consuming. Is there any way to avoid pre-dexing all the jars everytime ? If I understand things correctly, if the jar does not change between 2 builds, then it should not be dexed again. I guess it is because I call mvn clean everytime, however if I do not the "class defined in multiple dex files" error shows up again. The temporary workaround for me was to adequatly declare dependencies in my pom using Maven and use the "Make" function of IDEA, which I guess calls the android tools and build the stuff appropriately. It worked for some time, and it was actually great. The pre-dexing worked as I expected and did not re-dex everything at each compile command. However, I hit the same wall all over again, the "Make" command produces "class defined in multiple dex files", I guess that removing duplicates here does not work either (just in case I was not clear, the same project, with the same pom, builds successfully with Maven but encounters the error when using "Make").
Basically, what I really would like is to declare and manage my dependencies using Maven (actually I do not really have a choice in the matter) and build by calling "Make" in IDEA. The ideal case would be to have the same behavior as "Make" when invoking "mvn clean install". Just in case, I would like to point out the fact that using parallel building is a noticeable improvement but it is quite far from what I would expect.
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.