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.
Related
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?
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.
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.
I have an Android project (already ported to Android Studio and using Gradle) that is made up of different modules.
The project is actually used to create two different apps, where the code is pretty much the same, except for some resources.
Thus the resources have been split into two different modules.
The original author of this project used to work in Eclipse and switch the resource modules included in the dependencies based on which app he wanted to build. And he also used to change by hand the package name in AndroidManifest.xml
I would like to automate all of this and still have a single code base, but have two build targets with specific modules for each target. Is that doable with Gradle?
Update:
To make things even harder, my project has a hierarchy that is pretty much the following:
--+--MainProject
+--LibData
+--LibBase
+--LibResA
+--LibResB
Where:
MainProject depends on LibBase and LibData.
LibData depends on LibBase
LibBase either depends on LibResA or LibResB based on the final APK that I need to build.
As suggested, I've tried implementing this with flavors by adding in the MainProject build.gradle the following:
productFlavors {
producta {
}
productb {
}
}
And then in LibBase I've added the following to its build.gradle:
dependencies {
productaCompile project(':LibResA')
productbCompile project(':LibResB')
}
But then, when I build the project, LibData can't find the classes and resources inherited from LibBase. So now I'm stuck with this error. To me it looks like LibBase isn't being copied to the intermediates of LibData. That way LibData can't resolve the classes in LibBase, but it's just my assumption.
Update 2:
I kept investigating this issue and now I've changed my build.gradle files to look like this:
Main Project build.gradle:
defaultPublishConfig "productaRelease"
publishNonDefault true
productFlavors {
producta {
applicationId "com.producta"
}
productb {
applicationId "com.productb"
}
}
dependencies {
compile project(':LibData')
}
LibData build.gradle (has no product flavors, just the dependencies):
dependencies {
compile project(':LibBase')
}
LibBase build.gradle:
defaultPublishConfig "productaRelease"
publishNonDefault true
productFlavors {
producta {
}
productb {
}
}
dependencies {
productaCompile project(path: ':LibResA')
productbCompile project(path: ':LibResB')
}
This way I get no errors when doing the usual gradle clean build but I can see that the resources included are always those of LibResA just like the defaultPublishConfig is the only one used at all times.
If I open this project in Android Studio (0.8.1 atm) the result is that if I try to switch the build variant of the LibBase module and set it to productbRelease, the following error is being shown: Error:Module 'LibBase' has variant 'productbRelease' selected, but the module ''LibData'' depends on variant 'productaRelease'.
I'm running out of ideas.
Since you already have the product flavors:
productFlavors {
producta {
}
productb {
}
}
Define your dependencies prefixed with flavor name.
Example:
dependencies {
productaImplementation 'com.google.android.gms:play-services:11.0.2'
productbImplementation 'com.google.android.gms:play-services:12.0.1'
}
Common dependencies will be defined normally.
Now build apk for individual flavors.
Not the best way to do it, but if productFlavors is not enough to specify conditional dependencies you can rely on an inline if and evaluate it based on some value that can be injected via external properties.
For example here is how I toggle LeakCanary (no-op is just the empty implementation of the other one):
build.gradle
dependencies {
compile "com.squareup.leakcanary:leakcanary-android"+(project.ext.has("leakCanary")?"":"-no-op")+":1.3.1"
}
To build with com.squareup.leakcanary:leakcanary-android:1.3.1:
$ ./gradlew :app:assembleDebug -PleakCanary
By default it builds with the empty implementation com.squareup.leakcanary:leakcanary-android-no-op:1.3.1:
$ ./gradlew :app:assembleDebug
This provides a quick and more flexible way to toggle things using build command, but too much of it and things will get messy real quick.
Yes, it is. New Android build system based on Gradle supports your use case with its concept of product flavors. http://tools.android.com/tech-docs/new-build-system/user-guide
Note that you will likely want to switch from Eclipse to Android Studio when you do migration to Gradle build.
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.