Can I get the dependency tree of all sub-projects using Gradle? - android

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" }
}

Related

Gradle/Android: includeBuild sets build variant to null

Had no luck posting through the gradle community, so I thought I would reach out here.
In my root settings.gradle file, I use includeBuild to include the projects of interest. This works and I see that the builds have been included. Each one of these android libraries may contain a dependency on another module in a different project in the monorepo. All of these libraries produce artifacts that are published, so normally I would just target the recently released artifact. I want to use includeBuild with dependency substitution when developing locally. This way, if I make a change in a dependency I can make changes in all dependents immediately without having to release an artifact first.
Let me show you an example of one that I have working:
I have directory called base-implementation, this directory contains two gradle projects: base-api and base-ui-api. Each of these projects contains one singular android library module api and ui-api respectively. To further illustrate, one drilldown from a package structure would go base-implementation → base-api → api where api may be defined as an artifact dependency in other android libraries in this monorepo. For example ui-api defines a dependency on api.
Given this structure, in my base-ui-api project, I define an includeBuild on base-api and substitute out the dependency with the android library api. That looks like this:
def apiDependency = "com.myapp.example:api"
if(isIntegrationBuild.toBoolean()) {
includeBuild('../base-api') {
dependencySubstitution {
substitute module(apiDependency) using project(':api')
}
}
}
The isIntegrationBuild is just a gradle property I have set up as a development flag to use dependency substitution. I can run the gradle task provided by android called androidDependencies which will list all resolved dependencies for a given build. I can verify here that the dependency is indeed subbed out in ui-api by confirming this line “:api (variant: debug)”
It is important to note at this point, every single android library in this mono repo has only two build variants of debug and release there are no inconsistencies here with build variants.
The problem I am running into is this. I have a library that defines a dependency on another library which then defines a dependency on api. When going to build this library, a random nullPointerException is thrown without any message. What I have noticed however, is this; When I run androidDependencies task on the library that it is dependent on (the library that contains a dependency on api), the subbed out api dependency comes back with this “:api (variant: null)” and I cannot for the life of me figure out why. All includeBuilds and dependency substitution definitions are exactly the same. yet one resolves with variant debug and the other does not. They have next to identical build.gradle files at both the project and module level as well. There is nothing special happening to explicitly define a default variant implementation and all variants match across all libraries.
If what I explained above is unclear, let me drive home the issue with the project structure visual that doesn’t work. There is sensitive information in these library names so for sake of censoring, I will replace their names with A and B. Where A depends on B and B depends on api. A and B also share the same root dir but this root dir is just a container. Not a gradle project.
A depends on B so in the project level settings.gradle of A I define an includeBuild with a dependency substitution for B. This looks like the following:
def BDependency = "com.example.b:b"
if (isIntegrationBuild.toBoolean()) {
includeBuild('../project-b') {
dependencySubstitution {
substitute module(BDependency) using project(':b')
}
}
}
This block runs and works, I can see and confirm that the artifact is swapped out with the included build.
Now, B defines a dependency on api so its project level settings.gradle looks almost identical to the first case I stated where ui-api depends on api the only difference is relative pathing for includeBuild. This looks like the following:
def apiDependency = "com.myapp.example:api"
if(isIntegrationBuild.toBoolean()) {
includeBuild('../../base-api') {
dependencySubstitution {
substitute module(apiDependency) using project(':api')
}
}
}
Why is that when I build ui-api it pulls in api of variant debug, but when I build B it pulls in api of variant null? Is there a something I am missing about nested includedBuilds?

Android Studio: Resolving Duplicate Classes

When I try to run my android application on an Android device, the gradle console reports the following error:
Error:Execution failed for task ':app:transformClassesWithJarMergingForDebug'.
> com.android.build.api.transform.TransformException: java.util.zip.ZipException: duplicate entry: com/loopj/android/http/AsyncHttpClient$1.class
When I search for the "AsyncHttpClient" class, I see that it's indeed being found in two separate locations:
/Users/Afflatus/.gradle/caches/modules-2/files-2.1/com.loopj.android/android-async-http/1.4.9/5d171c3cd5343e5997f974561abed21442273fd1/android-async-http-1.4.9-sources.jar!/com/loopj/android/http/AsyncHttpClient.java
/Users/Afflatus/.ideaLibSources/android-async-http-1.4.9-sources.jar!/com/loopj/android/http/AsyncHttpClient.java
The first path seems to suggest it's a "cache" file... so I've tried invalidating & restarting my cache, but both files are still there after the gradle gets rebuilt and I try to run the application. I've read in alternate posts that it can be resolved by deleting one of the files... So I went to the cache location and deleted all the files found in the "1.4.9" folder... unfortunantly after reopening Android Studio, a new cache file gets created and I get the same error.
Other posts (here, here,here, and here) suggest if I add "./gradlew clean" to the root directory it would rebuild the gradle again just for the run (as far as I understand). So I tried doing that as well:
Which made my app's folder look like this:
But unfortunantly, that didn't help things I still get the same error. What am I doing wrong? What should I be doing?
I added this line to my gradle.properties file and my app worked
android.enableJetifier=true
Sometimes duplicate classes exception means that one of your dependencies uses implicitly the older or newer (with +) version of some library you also use in your project,
To resolve this issue you may add such block of code (put your library version after 'force') to your build.gradle file (Module:app):
configurations {
all {
resolutionStrategy {
// do not upgrade above 3.12.0 to support API < 21 while server uses
// COMPATIBLE_TLS, or okhttp3 is used in project
force 'com.squareup.okhttp3:okhttp:3.12.0'
force 'com.squareup.okhttp3:logging-interceptor:3.12.0'
}
}
}
You may also exclude some group from your dependencies.
For a single dependency you way write:
dependencies {
// example
implementation('log4j:log4j:1.2.15') {
exclude group: 'javax.jms', module: 'jms'
}
}
Tested to work on Android Studio with Gradle Plugin version 3.4.2 and Gradle version 5.4.1.
Credits go to Dimitar Dimitrov and Schalk Cronjé from gradle org discussion group
That's because you have added some library two times in libs folder, this could happen sometimes when you have multiple versions of the same library in the libs folder. Check it and remove any duplicate jar files.
And the second option could be you have also added the dependency in gradle.build and also have a jar in libs folder.
So check both places and remove duplicate entries and then clean and build APK again.
Delete files with duplicate jar extensions in the libs folder. However, if there are no duplicate files and there is still a "Duplicate classes" error, look for the name in the rest of the "Duplicate classes ...." clause in the error section. For example, "duplicated classes 'dagger' bla bla". Delete the file named 'dagger' from the libs folder. (Be careful not to delete it with shift.)
In my case, I am using sensorocloud.jar and the compile 'com.loopj.android:android-async-http:1.4.9' in my gradle which caused the same error as yours. Because sensoro cloud SDK used loopj's async-http.
I managed to solve it by manually removing the duplicate .class files in the jar file. (i.e.
changing the extension from jar to zip
extract it
remove the com.loopj.android .class files)
(P.S. I have tried to search through the web to see if I could exclude certain class of a jar in gradle, but not succeed, e.g. I referenced this SO post)
This error can be caused by several things;
misconfigured package name
Activity views that is not well binded. - simply go to your launcher activity view and ensure context is defined well e.g "com.yourdomain.package"
Re-create your BuildConfig and set it up well.
Check if your project build.gradle. There it might be some maven duplicate dependency
Here's another situation that can cause duplicate class during the mergeDexClasses task. This can happen with later versions of android gradle.
If your build.gradle.kts script has a dependency in the form:
implementation(project(":mylib", configuration="default"))
that can cause duplicate classes. The correction is simple. Just change it to:
implemenation(project(:mylib"))
Here's the Android Studio's Team explanation:
Having both project(":lib") and project(path: ":lib", configuration: "default") in the runtime classpath means that AGP gets both build/classes/java/main and build/libs/lib.jar (run ./gradlew :lib:outgoingVariants --all to verify). Because paths differ, we'll get 2 dexing transforms happening: 1 incremental that produces dex per class under build/.transforms (the one processing dir) and another one which produces single dex (the one processing jar). Later on during merging this causes failure.
AGP never publishes to the default configuration, in fact java-library plugin does it only so it does not break older build scripts. Having an explicit configuration name used in the dependency declaration is discouraged and Gradle attributes should be used instead.
In an older version of AGP, I ran into a problem where adding the configuration value "default" fixed some issue I was having. Well that no longer works, and adding the "default" configuration you can get duplicate classes.

Conditionally include project in gradle build

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.

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.

Gradle: How do I actually use a custom dependency configuration OR swap external dependencies for local ones?

Question
In Gradle, given a custom dependency configuration, what's the simplest way to actually use/compile with it in Android? Alternatively, what's the best way to switch between 'local' and 'external' dependencies?
Problem
To make building easier and to make better use of multi-project builds, I've created a top-level "aggregator" project that others may or may not use. So any modifications to subprojects must be self-contained in the aggregator.
In the build process for this aggregator, I want to always use local versions of the subprojects (i.e. the source code I'm editing), rather than the compiled artifacts in our repository. Additionally, this aggregator will be used in our continuous integration environment, such that the unit tests of all subprojects are run on each code commit.
Details : partial solution
Given a flat folder structure similar to the following, where all projects/modules are decoupled such that children don't know anything about parents.
+ Parent Aggregator Project
|---- build.gradle
|---- settings.gradle
| + app
| |---- build.gradle
| + models
| |---- build.gradle
| + networking
| |---- build.gradle
| + utils
| |---- build.gradle
I've created the following build.gradle file in the top-most project:
allprojects {
configurations {
localProjects {
extendsFrom compile
}
}
}
project(':app') {
dependencies {
localProjects project(':models')
localProjects project(':networking')
}
}
project(':networking') {
dependencies {
localProjects project(':utils')
}
}
The localProjects configurations are working properly and each has successfully replaced the existing dependencies (like 'com.mycompany:utils:1.0.0' and 'com.mycompany:models:1.0.0') with local projects. The only problem is I can't figure out how to get Android to use the 'localProjects' dependency set instead of the 'compile' one.
In pure java examples, I've seen people manually replace the classpath in the sourceSet but this doesn't work easily for the Android plugin.
Summary
How do you point an Android build to a custom dependency configuration such that it builds with those dependencies instead of the compile, debugCompile, releaseCompile ones?
Ultimately, I just want incremental builds to work. For example, if I edit the 'models' source code then:
The next time I build the app, it recompiles the 'models' project
It does not recompile the 'networking' or 'utils' code
These changes show up in the app, immediately, after building once (i.e. no more building in 3 or 4 places just to produce an APK with 'the latest')
As I make code changes to any of these projects, all I ever have to do is run the build from the aggregator project and the right things compile and the APK that's produced reflects the latest code on my machine
Similarly, I can run all unit tests just from the aggregator
Last but not least, I cannot edit the existing projects to accomplish this. All changes must be self-contained in the aggregator because there are other engineers on the project who may not choose to use it.
This is a very common use case and the Gradle team has mentioned adding better support for this kind of thing in the future. In the meantime, how do we solve this?
You can't really do it this way - subprojects will be affected by your "aggregator project". What you can do instead is to make it configurable whether a particular dependency is resolved internally or externally (both for subproject builds and the overall build). See https://github.com/pniederw/elastic-deps for a proof-of-concept.

Categories

Resources