Disclaimer: In a part of this post, we will look at how to generate a report of our Gradle project’s dependencies with their available upgrades. My experience with this was in an Android multi-module project but it should apply to just about anything that uses Gradle.
The Backstory 📚
At work as part of a new development cycle, I was asked to make a note of my app’s dependencies, their versions, whether an update to them was available and report these to the dev team and the project owners for review.
Wait, hold on. You want this for ALL of the app’s dependencies? 😬
Given I was working in a multi-module application with only relevant dependencies being used per module, no structured dependency management, and a large number of dependencies, this seemed somewhat challenging and tedious.
My initial thought process to achieve this was along the lines of:
- Find each dependency using
- Note the current version of each dependency
- For each dependency, check mvnrepository.com for the latest version
- Create a report with findings to present to the team
Clearly, this would be time-consuming, would scale poorly in the future, and almost certainly have a more elegant solution 🙇♂️ Spoiler: It does.
The three problems I therefore set out to solve were as follows:
- Allow for a dependency to have the same version across all my modules
- Stop re-defining dependencies in multiple modules and try to have a single source of truth
- Use some sort of “magic” to print out a report to get the out-of-date dependencies
Helpfully, my approach in solving these can be summarised in three simple steps 😌 so let’s get down to business!
For the impatient amongst us who want an example app demonstrating my full solution - check the link at the end!
Step 1: Centralise 🧹
The very first step I took was to organise my dependencies into one centralised place within the application with the view of making cross-module versioning easier to manage.
If you’re no stranger to Android development, you’ll know there are several different ways of achieving this. The approach I took for this is largely based on something I had seen used before in a sample app by BracketCove and within Android’s Architecture Sample. It involves moving the definition of dependencies and their versions into a single
versions.gradle file with maps of dependencies and versions being generated and made globally accessible to the application’s modules.
A small excerpt of this
versions.gradle approach can be seen here:
As you can see, for each group of dependencies we can define a version number and the full dependency name and group. This has the benefit of simply changing one property to change a dependency version throughout the application and scales nicely as the project grows.
I’d thoroughly recommend checking the full sample file as in my application it is the source of truth for plugin versions, SDK versions and various shared build versions too.
Step 2: Generalise 👨👩👧👦
versions.gradle file we have already grouped similar dependencies in the naming conventions, however, we have yet to add these dependencies into our modules.
More importantly, we ideally want an elegant way to add these similar dependencies to a module all at once!
The approach I used for this uses another Gradle file
version-groups.gradle and groups similar dependencies together across all modules.
After grouping these dependencies we can use these in our module-level
build.gradle files as follows:
WOW. 😍 Suddenly our dependencies are managed both centrally and cleanly.
Step 3: Formalize 💻
Admittedly, this step being called ‘formalize’ is a bit of a stretch! A more accurate description might be ‘Create the report’, but that didn’t follow the theme 🙃
Up to now, steps 1 and 2 can be considered ‘nice to haves’ and totally optional in order to generate a human-readable report, so here comes the meat and potatoes 🍖🥔
This excellent little Gradle plugin, once integrated, can be used to show your available updates via the terminal using
./gradlew dependencyUpdates. Using the plugin in that fashion spits out some very useful information such as the example below:
The following dependencies have later milestone versions: - androidx.appcompat:appcompat [1.0.2 -> 1.3.0-alpha01] https://developer.android.com/jetpack/androidx - androidx.core:core-ktx [1.0.2 -> 1.5.0-alpha01] https://developer.android.com/jetpack/androidx [...]
Great! However, whilst this does give us the answers we are looking for, it isn’t exactly easy to read or navigate.
Thankfully, the plugin allows for a custom
outputFormatter to be defined. This allows us to utilise Groovy’s
MarkupBuilder class to create a webpage using an HTML syntax-style DSL.
To implement this, I created a new file,
dependency-update.gradle and added the following:
We also need to change the top-level
build.gradle to ensure we apply the plugins, our versioning code, and our newly created dependency report to all modules within our project.
With all these changes made, what we now get from
./gradlew dependencyUpdates is an HTML report for each module in our app 🙌 Job done.
Here is a link to a sample GitHub project that contains a full example of how to configure your Gradle project to get your dependency sorting and reporting ‘on fleek’.
Thanks as always for reading! I hope you found this post interesting, please feel free to tweet me with any feedback at @Sp4ghettiCode and don’t forget to clap, like, tweet, share, star etc