How can a debug flavor variant depend on a release code - android

I'm publishing a multi-module Android library. The use case is that I want to run a sample app (in debug) against the release AARs (that are proguarded) of my library. The ultimate goal is to be able to test the exact artifacts what would be published.
My modules and their desired build types are:
:my-library (release)
:mock-server (debug)
:sample (debug)
:sample depends on :my-library and :mock-server. :mock-server also depends on :my-library.
To achieve this, I set up a flavor dimension:
android {
flavorDimensions "SOURCE_OR_BINARY"
productFlavors {
source {
// indicates that project dependencies are used directly
}
binary {
// dependencies added via here will be AARs
}
}
dependencies{
binaryImplementation(name: ':my-library', ext: 'aar')
sourceImplementation project(':my-library')
implementation project(':mock-server')
}
task prepareBinaryDependencies(dependsOn: [
":my-library:assembleRelease" // to generate AARs
]) {}
//
tasks.whenTaskAdded { task ->
def taskName = task.name.toLowerCase()
if (taskName.toLowerCase().contains("binary")) {
// Prepare libs as binaries
task.dependsOn prepareBinaryDependencies
task.doFirst {
prepareBinaryDependencies
}
}
}
This works fine with ./gradlew on the command line, but Android Studio often reports a Failed to resolve: :my-library-release: during gradle sync. If I do a ./gradlew assemble on the command line, then sync Android Studio, the the AS Gradle sync succeeds.
It seems that I am doing something wrong here. Is there a way to do this better that will avoid the Failed to resolve Android Studio Gradle sync error?

Related

Android multi module libraries

How can I build all Android modules in project and expose those to maven repository without creating huge gradle script? There are no clear instructions about modularity in Android projects with artefacts generated by gradle maven plugin. It would be awesome to use built in task.
In few steps:
Create a common gradle file in root directory, such as config.gradle and put this fragment:
ext.packageName = "com.abc.example"
ext.mavenPublisher = {
task androidSourcesJar(type: Jar) {
archiveClassifier.set('sources')
from android.sourceSets.main.java.srcDirs
}
afterEvaluate {
publishing {
publications {
maven(MavenPublication) {
from components.release
artifact androidSourcesJar
groupId = packageName
artifactId = findProperty('moduleName')
version = getVersion()
}
}
}
}
}
In root gradle.properties add property version=1.0.0.
In library module build.gradle add:
apply from: "${rootProject.projectDir}/config.gradle"
with mavenPublisher
Also in library module add gradle.properties with:
moduleName=name
Type in console from root project directory: gradle clean build publishToMavenLocal.
In section from components.release you can change release type to debug or another one build type. Also you can add version to each module if you want, but probably some release plugin configuration is required.
Working example at this link.

Android Gradle Plugin 7.2 ignores added proguard file in Android App build process

I have a Gradle plugin I wrote for an Android app project. Amongst other things - this plugin adds a custom ProGuard rules file to all ApplicationVariants being built.
the has worked fine until Android Gradle plugin 7.2 was introduced. Since I started using AGP 7.2 to compile my app - the ProGuard file that's added by the plugin is ignored.
Code:
project.android.buildTypes[<variant.buildType.name>].proguardFile = new File(<custom Proguard rules file path>)
This worked in AGP <= 7.0 without any problems. There are no exceptions in the logs of the build process.
I tried another approach and got the same results :
I tried to add a ProGuard file with a script (not using a plugin at all) - but the results were the same - this file is ignored.
This is the code I added in build.gradle :
afterEvaluate {
for (def buildType : project.android.buildTypes) {
buildType.proguardFile file(< full path>)
}
}
Any ideas?
In Groovy the value can be set for android.defaultConfig:
// this sets one file
android.defaultConfig { config ->
proguardFile new File("../general.pro")
}
// this adds an additional file
android.defaultConfig { config ->
proguardFiles.add(new File("../general.pro"))
}
// proof of concept
android.buildTypes { buildType ->
println buildType
}
This happens before :prepareKotlinBuildScriptModel, which means early on.

Android studio / Gradle javadoc task

I've been struggling to setup a gradle task to generate Javadocs for my Android library, but when there are external dependencies to other libraries, doc generation fails. This seems to be a common task, but somehow there doesn't seem to be an easy solution, as for example this answer will reveal (re-generating exploded-aar manually is a bit absurd, and also, on Android Studio 3.0 even that doesn't work anymore due to the new dependency directives).
However, I have noticed that generating Javadoc through the Android Studio GUI (Tools menu) works just fine - dependencies to other libraries are resolved etc. So how does this work - does this menu not utilize a gradle task for generating Javadoc?
Since I need to generate Javadoc using gradle as part of CI I find it very frustrating that there is no documented way of getting it to work, while there is a way that works through the menues. Doesn't the Android Studio Tools -> Generate Javadoc menu in turn use a gradle task? Since dependencies are listed with gradle files, and the Javadoc tools menu apparently is able to resolve those dependencies - how is it implemented? How does it source the jars embedded in the dependant aar libraries, etc? How can it be used stand-alone and not though the Android Studio GUI?
Maybe you have got the solution to this. Just in case not, below is how I generate API doc for my Jenkins CI.
task generateApiDoc() {
group "reporting"
description "Generates Javadoc."
}
android.libraryVariants.all { variant ->
// Only consider release
if (variant.buildType.name == "release") {
def task = project.tasks.create("generate${variant.name.capitalize()}Javadoc", Javadoc) {
group "ApiDoc"
description "Generates Javadoc for $variant.name."
// Source files from the variant
source = variant.javaCompiler.source
// Classpath from the variant + android.jar
classpath = variant.javaCompiler.classpath + files(prj.android.getBootClasspath()) + files("$buildDir/intermediates/classes/release")
/* add the excluded packages */
exclude "**/R**"
exclude "**/BuildConfig*"
options.windowTitle = "My Library"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.linkSource false
options.author = true
//options.links("http://docs.oracle.com/javase/7/docs/api/", "http://d.android.com/reference");
failOnError false
}
task.dependsOn assemble
generateApiDoc.dependsOn task
}
}
Then run below gradle commands to get your api doc in place of "$buildDir/docs".
./gradlew assembleRelease
./gradlew generateApiDoc
Edit for Gradle Plugin 3.4.1
android.libraryVariants.all { variant ->
def task = project.tasks.create("generate${variant.name.capitalize()}Javadoc", Javadoc) {
title "API Documentation (${project.android.defaultConfig.versionName})"
group "ApiDoc"
description "Generates Javadoc for $variant.name."
// Source files from the variant
source = variant.sourceSets.collect { it.java.sourceFiles }.inject { m, i -> m + i }
// To fix issue: Error: Can not create variant 'android-lint' after configuration ': library: debugRuntimeElements' has been resolved
doFirst {
classpath = project.files(variant.javaCompileProvider.get().classpath.files,
project.android.getBootClasspath())
}
if (JavaVersion.current().isJava8Compatible()) {
options.addStringOption('Xdoclint:none', '-quiet')
}
exclude "**/R"
exclude "**/R.**"
exclude "**/R\$**"
exclude "**/BuildConfig*"
if (JavaVersion.current().isJava8Compatible()) {
options.addStringOption('Xdoclint:none', '-quiet')
}
options.windowTitle = "API Documentation (${project.android.defaultConfig.versionName})"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.linkSource false
options.author = false
failOnError true
}
task.dependsOn "assemble${variant.name.capitalize()}"
generateApiDoc.dependsOn task
}
I use a gradle task that just executes a bash script file, with a single (pretty long) javadoc command.
What you can do is run the Javadoc generation from Android Studio once, then copy the executed javadoc command from the Studio log, with all the right parameters, and automate the execution of the same command in your gradle.
The tool to generate java style documentation is called javadoc and it comes installed in every JDK. You can configure which classes or packages you want to be included, which ones should be excluded and many other options. Type javadoc in a terminal where a JDK is available and you'll get an idea. See also https://docs.oracle.com/javase/9/javadoc/javadoc.htm#JSJAV-GUID-7A344353-3BBF-45C4-8B28-15025DDCC643
After you get to your optimal configuration, you can include a javadoc step in your CI.

Gradle multi-project conditional dependencry

How would one create an Android Studio (Gradle) multi-project configuration such that projB depends on project(':projA') if projA is defined, but uses a file in libs/ otherwise?
Since it may be asked, in this case projA is an SDK; projB is a test application designed to demonstrate the SDK. If the SDK team gets a bug report, it often includes reproduction steps using projB.
When projB team does work, they do so on RC builds of projA, whereas the SDK team uses projB, with a dependency on project(':projA') so that a debug session can be run.
projB has no specific definition of its dependency on projA; that team takes the projA output from the build server and drops it in the libs/ folder, and has a wildcard dependency.
EDIT
I finally went with this code in the dependencies closure, and it works like a charm:
def sdkRef
project.getRootProject().allprojects.each { proj ->
if (proj.name.equals("Sdk")) {
sdkRef = proj;
return true;
}
}
if (sdkRef) {
println "SDK present in project; using project reference as dependency"
compile sdkRef
} else {
println "SDK is not present in project; using libs/"
}
I wonder if that's something you can do with flavors and build variants.
Through code you might try in your build file :
dependencies {
if (project.getRootProject().findProject(":projectA")) {
compile project(":projectA")
} else {
compile files("libs/projectA.jar")
}
}
One thing you have to consider is that your settings.gradle defines what modules are included in your project. So your two teams might end up with different files anyway for the project.
You can achieve that with productFlavors.
You just have to define:
2 product flavors in projB/build.gradle
a specific dependency for each flavor
android {
productFlavors {
demo{}
sdkdev{}
}
...
}
dependencies{
demoCompile files("libs/projectA.jar")
sdkdevCompile project(":projectA")
...
}
The build will produce 2 apks.
In Android studio, someone from the demo team can run the demo flavor by selecting the "demoDebug" (or "demoRelease") variant (in Build Variant tab) and someone from sdk team will select the "sdkdevDebug" variant.
The gradle.settings must contains references for projA and projB, but a user from demo team will never have to compile projA because the demo flavor have no dependencies on it.

Conditional dependencies with Gradle, is it possible?

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.

Categories

Resources