Gradle/Android: includeBuild sets build variant to null - android

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?

Related

Android Gradle declare a Variant Build Flavor dependency using API and Exclude in KTS

I'm trying to build a flavor of my app that includes a very heavy dependency and is only going to be used in certain builds for testing and offline development (dependency is Wiremock for Android). However I can't seem to find any flavor variant dependency declarations that also use api() and exclude.
Before I decided to move the dependency to a build variant, I could declare the dependencies like so:
dependencies {
//WireMock - Do not put in release builds bc of large size
api("com.github.tomakehurst:wiremock:2.18.0") {
exclude("org.apache.httpcomponents", "httpclient")
exclude("org.ow2.asm", "asm")
exclude("org.json", "json")
}
api("org.apache.httpcomponents:httpclient-android:4.3.5.1")
}
I would love to restrict this dependency to my build flavor, which I have simply called "mock", something like:
dependencies: {
"mockImplementation"(
api("com.github.tomakehurst:wiremock:2.18.0") {
exclude("org.apache.httpcomponents", "httpclient")
exclude("org.ow2.asm", "asm")
exclude("org.json", "json")
}
api("org.apache.httpcomponents:httpclient-android:4.3.5.1")
})
}
This is obviously very wrong but I am unsure of how to go about formatting with the api and exclude dependency notations as I cannot find very many examples when it comes to also combining these with a build flavor.
After a lot of playing around I ended up with:
// WireMock - Do not put in release builds bc of large size, restrict to mock flavors
"mockImplementation"(mockApi("com.github.tomakehurst:wiremock:2.18.0") {
// Using Android Version Instead
exclude("org.apache.httpcomponents", "httpclient")
//Was getting a classpath conflict for org.objectweb.asm.AnnotationVisitor which is a part of 'net.minidev:asm'
exclude("org.ow2.asm", "asm")
//Was getting this warning, so decided to ignore this version included by WireMock.
//Warning:Dependency org.json:json:20090211 is ignored as it may be conflicting with the internal version provided by Android.
//In case of problem, please repackage with jar to change the class packages
exclude("org.json", "json")
})
"mockImplementation"(mockApi("org.apache.httpcomponents:httpclient-android:4.3.5.1") {})
Note that the "mockApi" was necessary rather than just using "api" to actually constrain the variant.

Android - create uber/fat aar, error on implementation

I want to create a single uber/fat aar. I have a library (libA) which is dependant on another library (libB) and I would like to bundle them together, preferably using gradle. This uber/fat aar will then be used in an app.
Initially I looked at 2 similar gradle plugins, shadowJar and fataar but due to version incompatibilities they were a no go. So to do this, I've created a custom configuration in my gradle file and added the dependencies:
configurations { privateLibs }
privateLibs ('libB.1.0.0#aar') {
transitive=true
}
compile configurations.privateLibs.asFileTree
I can then run gradle install to produce libA-1.0.0.aar.
When I inspect libA.aar and look at it's contents using
jar tvf libA-1.0.0.aar
I can see LibB listed under the libs folder:
BST 2018 libs/libB-1.0.0.aar
I've now created an app inclduing my new library libA-1.0.0.aar as a local dependency. Later it will be resolved through an external repository.
implementation files('libs/libA-1.0.0.#aar')
The app compiles and appears to be ready to run. However when I try to run the app in the android simulator it throws up an error message:
error: cannot access ExampleClass class file for com.sample.ExampleClass
ExampleClass is a class created in libB and implemented in libA. It appears as though libA has no knowledge of libB despite it being available in the libs folder of the libA aar. Is the build steps here wrong? Should this be done another way? Thanks in advance!

Gradle Android project dependency not working

I'm trying to migrate from maven to Gradle for an Android project and I am facing the following issue:
I have three projects i.e
root
- projlib
build.gradle
- projA
build.gradle
- projB
build.gradle
build.gradle
settings.gradle
Basically what I want to achieve is that projB depends on projA and projlib. projlib is a lib folder that compiles and generates a lib(jar) file. projA is an Android Application and projB is another Android Application that needs to reference code in projA. Right now what I have added in the projB build.gradle file is
dependencies {
compile project(':projlib')
compile project(':projA')
}
So say if there's a class
FooProjLib in projlib and
FooProjA in projA
Then In projB I can do
FooProjLib foo = new FooProjLib
which works fine
but when I do
FooProjA foo = new FooProjA
Gradle gives me package projA does not exist, what I have observed is that both dependency is resolved but only the lib can be reference and not the apk.
Does anyone have an idea how to solve this?
You can't do exactly what you want. projA can't build an application (i.e. an APK) and also have other things depend on it. projB can only depend on projA if projA is an Android library, meaning in its build file you have this declaration:
apply plugin: 'android-library'
instead of
apply plugin: 'android'
Of course, this means that projA won't build an APK, but will build an AAR instead.
If projA needs to also be an APK, you'll have to restructure things such that the common code that's in projA that projB also needs is moved out to a shared library. If both projects are similar, perhaps you could have just one module for them and use project flavors to differentiate them; it's hard to say whether this is a good approach without a lot more information.

How to add a dependent project to an android gradle project that builds a debug or release jar approprately

I need a clear example of how to extend an Android Gradle project with an arbitrary project.
By arbitrary I mean that it can't just use the 'java' plugin since it doesn't support buildTypes to my knowledge. I am currently using an 'ant' task for this, which has two targets for debug and release, however I don't see how to tie it into an Android project.
Assume that your dependent project must build pure Java source in two ways:
debug build that produces a debug version in 'purejava.jar'
release build that produces a release version in 'purejava.jar'
The jar 'purejava.jar' is to be placed such that the Android project (could be a multi-project) is able to reference it at compile time, and it must therefore be the correct build to support both the debug and release configurations of the Android project.
How should this be tackled?
Since I am new to Android Studio and Gradle, I don't have a clear idea of how to manipulate extensions generated by the Android plugin, which are not available until after project evaluation.
How should the Android project be made dependent on this pure java project?
If it weren't for the fact that you need debug and release versions of your library, then your Android app could depend on a plain Java module just fine -- you could set up the library with the java plugin and put a compile project statement in the app's dependencies and it would work fine.
However, the Java plugin is never going to understand Android's notion of build types (unless GradleWare adds it at some point), so you can't propagate that to your Java modules. You could set up your plain Java project as an Android library and use the android-library plugin (you'll have to dummy out the manifest and other Android-specific stuff it expects to see in Android libraries), but you'll run into a different problem: https://code.google.com/p/android/issues/detail?id=52962 is a bug that reports that the build type is not propagated to library modules.
Until that bug is fixed, or if you're unwilling to make your plain Java library an android library, I think your only approach is to make two different versions of your library, compile them to different jar files, and selectively pull in dependencies.
This is my answer, with following project structure:
MyProject
-- MyAndroidLib
-- JarProject
This represents the gradle top project 'MyProject' which has a sub-project 'MyAndroidLib' which is dependent on a pure java project 'JarProject' which is built with different code for debug than for release builds.
I'll take advantage of Android's 'debugCompile' and
'releaseCompile' configurations. In the Android sub-project ('MyAndroidLib') that is dependent on the jars, add following lines to the dependencies:
//MyAndroidLib build.gradle
def jarProject = project(':MyProject:MyAndroidLib:JarProject')
def jarPath = pcfProject.projectDir.toString()
dependencies {
....
compile jarProject
debugCompile files(jarPath + '/' + jarProject.debugJarName)
releaseCompile files(jarPath + '/' + jarProject.releaseJarName)
}
The 'jarProject' def is defined to simplify accessing it from the MyAndroidLib project. (If you know a better way ...)
The main point of this is to define a separate debug and release jar path for the 'debugCompile' and 'releaseCompile' configurations. The 'debugJarName' and 'releaseJarName' are defined in a gradle.properties file for the JarProject as follows:
//JarProject gradle.properties
debugJarName=jarproject_d.jar
releaseJarName=jarproject_r.jar
In the gradle file for JarProject define a task that builds BOTH jar files named by this properties file. In my case, they are built right in the project folder by the 'compile' target of an ant build file located in that project.
//JarProject build.gradle
apply plugin: 'java'
project.ext.set("debugJar", file(projectDir.toString() + "/" + debugJarName))
project.ext.set("releaseJar",file(projectDir.toString() + "/" + releaseJarName))
task buildJars(type: Exec) {
description 'Build the debug and release jars for the JarProject'
outputs.files debugJar,releaseJar
commandLine 'ant', 'compile'
}
task compileJava.dependsOn('buildJars')
artifacts {
buildJars
}
clean.dependsOn('cleanBuildJars')
clean << {
exec {
commandLine 'ant', 'clean'
}
}
I took advantage of the 'java' plugin since it defines a 'compile' interface, and I haven't figured out how to build this from scratch, or even from the 'base' plugin. This project takes advantage of the automatic 'cleanBuildJars' task created because I defined the outputs in 'buildJars' task. This is necessary in order to have them built as needed. I probably need to define the 'inputs' too, since if they change ...
If anyone sees how my first stumblings in the gradle/Android world can be improved, pls. add comments as needed.

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