How to organize common gradle tasks and extract common code blocks - android

I have an android project consisting of 10+ library modules. Each of these modules contains a lot of gradle code common to all modules. For example, I'm using the Javadoc generation task from this answer in all of my build.gradle files.
How would I go about to extract that task creation logic to a function put in a separate gradle file which can be "included" by each module? The code is identical for all my modules, but obviously depends on variant and project. Is it possible to extract a function that takes a project as parameter and returns a task for that project?
I'm probably going around this backwards since I really suck at Gradle but any pointers leading to me avoiding having the same 60 lines of gradle code in 10 different files (lib1/build.gradle, lib2/build.gradle, ...) would be helpful!
In fact, taking it even further, basically my entire build.gradle is identical for all projects except the dependencies section - there's an android block with buildTypes, compileOptions etc, there are some plugins applied (apply plugin: 'com.android.library' etc), and there are some artifact parameters set up. In fact, only my dependencies differ at all. So I'm wondering if it would be possible to extract a common file completely, like this (pseudo code obviously):
<include common.gradle> // includes android block, common tasks, artifact setup etc.
dependencies {
api 'androidx.appcompat:appcompat:1.1.0'
api 'com.google.code.gson:gson:2.8.5'
...
}

You can extract your common settings into a gradle file (lets say common.gradle and then use it as
apply from: '../path/to/common.gradle'
Reference: https://docs.gradle.org/current/userguide/plugins.html#sec:script_plugins

The recommended way of extracting common build logic is to leverage buildSrc and create plugins that are then applied to your projects.
This has a number of benefits of applying a script, as documented.
In addition, I would recommend being explicit through plugin application. While you could develop a single plugin that configures all your projects, with some logic based on project name for example, it is usually better to have different plugins matching the different project types.
That way, when opening a build file you immediately see what kind of project it is.
And of course you can have common code in buildSrc for the common parts.
One more benefit is that moving from a buildSrc plugin to a published plugin is much easier ... just in case your configuration patterns start to appear in different projects.

Related

Some build.gradle files have different syntax's then others?

Started a new job and I can see that the build.gradle files's syntax is a bit different like the one using a = to assign values and the other just without it. Fx. the Google Sunflower demo app has it's implemetation statements without parentheses and my project uses parentheses and will not work otherwise.
I'd like to have all add-on versions in one place as I'm used to, and as it's of-course in the Sunflower app:
ext {
appCompatVersion = '1.1.0'
...
}
but that fails in my project
What is going on?
Gradle is a build automation tool, which can be written in several languages, more importantly when it comes to Android Studio, Gradle files tend to be either written in Gradle's own Groovy DSL language which is heavily based on Apache Groovy's language, which is usually referred to in documentations as just Groovy, or they can be written in Kotlin DSL, which is based on its parent namesake Kotlin language, Gradle's Kotlin DSL support was announced in 2016 which is why you might find legacy projects using Groovy, while others are using Kotlin DSL.
You can tell the difference between Groovy Gradle files and Kotlin DSL Gradle files by looking at the file extension, a settings.gradle is a Groovy script and settings.gradle.kt is a Kotlin DSL script.
Note that Gradle supports interoperability between Groovy and Kotlin DSL files, meaning that, while a settings.gradle must be written in Groovy and a build.gradle.kt must be written in Kotlin DSL, it's OK to have a project that have both settings.gradle and build.gradle.kt, Gradle is smart enough to make them understand each other, although for the sake of consistency, I would recommend sticking with one DSL language for all your Gradle files.
By default when you create an Android project for the first time, the Gradle files generated for you are in Groovy.
For much, much more thorough details on the differences between the two syntaxes I highly recommend reading the Gradle docs, this page in particular which goes into migrating from Groovy to Kotlin DSL, starting from the "Prepare your Groovy scripts" section. It's handy to have it on your side whenever you're copy/pasting a Gradle fix from Stackoverflow which is written in a Gradle syntax that doesn't correspond to your Gradle file extension, although eventually you'll probably end up memorizing the little differences yourself.
Don't confuse build.gradle (Groovy) with build.gradle.kts (Kotlin).

How to apply lintChecks to all dependent modules?

I created a custom Lint check and want the check applied to all dependent modules in my Android project. The custom Lint check lives in the checks module, and I can successfully run it in other modules by adding lintChecks project(":checks") to their respective build.gradle files. What I'd like to do is add something similar to api <dependency>, so all dependent modules also run the check. There is a core module already serving the purpose for other functionalities. Is there an API or configuration to prevent lintChecks redundancy? Such that all I'd have to do is add lintChecks project(":checks") to the core module.
I did not create an IssueRegistry file in the correct directory, resulting in Gradle not showing the task. Found out lintChecks in build.gradle files was never necessary.

Kotlin common library to reuse in multiple MPP

I'm setting up a Kotlin multiplatform project so I can reuse common code in multiple platforms for a single app. While building the common code for the app, I've extracted some base classes that I'd like to be able to reuse as a library in multiple multiplatform projects. I'm trying to add the library as a dependency in commonMain. There are a couple of things I don't understand.
First of all: is this currently possible?
If yes:
The default stdlib-common is a jar file, correct? How come a jar can be referenced as a dependency in commonMain if no Java can be used there? Or is it okay to use a jar compiled from pure Kotlin, as long as it only has Kotlin dependencies?
How do I compile a pure Kotlin jar that can be used in commonMain the same way as stdlib-common is used? Are there any sample build.gradle projects or guides for how this should be packaged?
If no:
What options do I otherwise have to reuse code over multiple multiplatform projects, if I want to avoid duplication? Do I actually need to keep all source within the actual commonMain source folder? Can it be linked from another folder if so? I tried adding additional content roots but it didn't seem to work since Gradle controls the configuration and I'm not sure how to add additional content roots in commonMain through Gradle.
Thanks in advance.
I got it working, mainly from looking through this thread and looking at this example. Although some of it might be dated by now, it helped me understand the following:
MPP1 can have another MPP2 as a dependency. Here is a list of MPP libraries for reference.
MPP2 needs to generate artifacts for the same set of platforms as it is used in by MPP1.
MPP2 generates platform artifacts along with a module file where they are described. MPP1 can then use the below configuration. Thanks to the module file, it's not required to explicitly add each platform's corresponding dependency, the dependency only needs to be declared in commonMain.
commonMain {
dependencies {
implementation kotlin('stdlib-common')
implementation 'com.company:mpp2:1.0'
}
}

Custom Android Library Configurations

I have a library that I've currently broken into two different gradle modules. One of the gradle modules provides a stub/dummy implementation of my library and I have another gradle module that provides the real implementation. The reason for this, is that in certain flavors of my app, I want to only use stub implementation where the library does nothing whereas other flavors, I want to provide the real implementation.
In my app's build.gradle, I use the following "dependencies" block.
dependencies {
flavorOneCompile project(":realLibrary")
flavorTwoCompile project(":stubLibrary")
}
This solution seems to work okay but it bothers me that there are two gradle modules that I have to maintain. It also makes it a bit clunky when I need to decide where to put class files (in real or in stub). I should be able to just have a single library and somehow switch between the two types using gradle "configurations
https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html
However, I haven't been able to learn how to do this yet. I tried creating the "configurations" block in my android library as such
configurations {
real
stub
}
and then in my app's build.gradle, do the following.
dependencies {
flavorOneCompile project(path ":myLib", configuration "real")
flavorTwoCompile project(path ":myLib", configuration "stub")
}
This doesn't seem to work though. Is there a way to achieve what I want? That is, have a single library so that my source code for my library is self-contained, but also provide different variations of my library?
Thanks!

Android Studio Gradle External Library Project

Ok, I realize that Gradle and Android Studio seem to think that all Library Applications are built for one project and one project only, but that is not the case. I have many shared Library Applications with common purposes that are shared throughout the organization. Gradle does not seem to be very accomodating to this desired solution. Can someone offer any insight?
My current Structure at a very rudimentary level is like this:
|--Directory
| |--PROJECT A
| |---Module 1
| |--Project B
| |---Module 2
| |--Project c
| |--Module 3
/////////////////////////////////////////////
My Current dependency structure is like this:
/////////////////////////////////////////////
Project A: (FYI, Builds Just Fine)
Project A's settings.gradle
include ':Module 1', ':Module 2'
project(':Module 2').projectDir = new File('../Project B/Module 2')
Module 1's build.gradle
dependencies {
compile project(':Module 2')
}
Project C: (FYI, BROKEN)
Project C's settings.gradle
include ':Module 3', ':Module 1'
project(':Module 1').projectDir = new File('../Project A/Module 1')
Module 3's build.gradle
dependencies {
compile project(':Module 1')
}
Breaks: Cannot resolve Module 2 inside of Module 1's build.gradle file.
This is because the directory structure for Module 2 is established inside Project A's settings.gradle so Project B has no idea where to render this from.
I understand that I can add
project(':Module 2').projectDir = new File('../Project B/Module 2')
to Project C and everything will work just fine. However Project C doesn't use or know about Module 2. I want other developers to have the freedom to use my common shared library project without having to dig in and see what library projects I used and include those in their settings as well. How can I specify my own dependency directory structure in the build.gradle instead of the settings.gradle to make it accessible to all that use it?
On a second note, but similar topic. I'm having the exact same issue with JAR files. If i specify a REPO in a Library Project's build.gradle like: myRepo1 and have a myJar1. Then when that library project is used in a parent project that doesn't define the repo that contains the jar in the library projects dependeny section, it fails to resolve the jar file from the library project when compile project(':libproject') is used. I have to duplicate the repo pointers in the parent's build.gradle file as well so that the libproject will build from the parent app. Any help on this one would be appreciated as well. As not every repo is used in every app so this can become redundant.
Ok this is a really old post, but still gets traction so let me update 3 years later since I originally wrote it lol.
Shout out to CommonWare who had the right best practice idea right from the start, but didn't provide an answer to mark up.
Let me start by saying that using project references like I was doing above should be limited to development stages only and should only be if the library project is also in development stage at the same time as the main project. Otherwise a dependency management server like Nexus, Apache Archiva, or S3 with Maven directory structure or equivalent would be preferred. I have learned many ways to manage dependencies since this, including transitive dependency management.
My preferred method is to deploy artifacts with POM files to Apache Archiva and then use these dependencies within the parent project instead of using relative paths to reference code projects now. This is the first choice.
However, if you are too new to dependency management and choose not to have a server for this purpose, you may package your AAR files or JAR files and put them in one centralized repo like artifact_repo and have everyone include that repo at the same folder structure and reference them relatively, but this is not good practice so I would steer clear if you can.
You can also take the artifacts and nest them in you libs directory and bring them in that way if you would like, but it becomes more of a manual update process which some people like and others do not.
Now this opens a whole different set of issues that you need to handle.
Transitive Dependencies and Child Repo pointers.
For example, if you wrapped your own Crash Reporting Library around Fabric or Hockey or other hoping to make it easy to trade libraries later, then you have found that the repo pointer has to live in the parent build.gradle files or the transitive dependencies are not found.
You could of course use one of those hacky Fat_AAR or Fat_JAR scripts that works "sometimes" until updated gradle then they break again until someone hacks it back together, but this is also poor practice as you are creating potential mismatch dependencies on support or other important child libraries and the "exclude transitives" only works if you are using pom files to control the transitives and not making the AAR or JAR file fat. So you are limiting your ability to control the dependencies.
So what i have finally come to terms with is that transitive dependencies should be managed through POM files to allow excluding or including without nesting into children libraries. Also libraries that require repo pointers inside of them, should probably not exist as they require parent boiler plate, introduce room for human error and typically don't save much time on wrapping analytics or crash libraries for example or you start getting into json configs that need to live in parent files for PUSH or other reasons. Just avoid it.
So long story short lol. Stick to dependency management tools they way they were intended to be used and you will be fine. It is when you are new to it or start getting hacky that you run into ugly code and ugly problems. Hope this encourages someone to do it the right way :)
One last thing :). I have recently started writing Gradle Plugins to manage my versions and dependencies as a separate file so that I can use intellisense to pull in dependencies and make sure all support, gms, and tool versions are the same across all projects. You can even copy down live templates with your plugin to enable intellisense for Gradle to work with your stuff. It's not too bad to do. Best of luck and happy Gradling :).

Categories

Resources