How we stay up to date with our dependencies
I guess we all know how to add a dependency with Gradle, right? I mean, it’s basically a one-liner:
dependencies {
implementation("x.y.z:abc:1.0-alpha01")
}
Nothing new for us, right?
But what happens with that single line of code if there is a new version of the dependency available? Maybe the authors implemented a new feature, or better, fixed a bug!
Well, if you are like me or most people I know the following happen: Nothing. Or at least nothing will happen as long as you don’t need that new feature or a bug breaks something for you.
There is nothing wrong with doing this. It is totally fine to stay on a specific version as long as you’re happy with it (and the discovered bugs are irrelevant for your business). But wouldn’t it be cool if there is something to remind you about updates regularly? Not saying that you have to update but that there is the ability to update?
Recently my team and I wrote our own DependencyBumper
. This tool regularly looks for updates of our dependencies and creates a pull request when one is found.
Before I go into detail. Yes, we are aware of Dependabot and its sibling tools. But most of them work only for a “standard Gradle setup” (whatever that means). Since we don’t have such a setup (and won’t change to one) we decided to build something on our own.
What does our current setup look like?
We have a file called versions.gradle
where we define a Map
with the “name of the dependency” as key and the Gradle string dependency notation as value. We put the whole map to the ExtraPropertiesExtension
to make it available in all other build scripts.
ext {
deps = [:]
deps.kotlinjdk = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72"
}
We only have to apply this script in our build scripts to have access to the dependencies.
apply from: 'versions.gradle'dependencies {
implementation deps.kotlinjdk
}
How dependencies are updated in that file?
We make use of the Gradle versions plugin to check for updates. The provided task named dependencyUpdates
will produce a JSON
file that contains the dependencies which have an update.
Then we run a custom JavaExec
task which will execute our DependencyBumper
. The code does then the following:
- Parses the
JSON
file to create aMap
for each update - Create a new
branch
for each entry in theMap
- Check if the
branch
name already exists atorigin
and skip this entry if so - Replaces (with the magic of Regex) the currently version in the
versions.gradle
with the new version. - Push the
branch
toorigin
- Create a pull request from this
branch
to our base branchdevelop
With the check if there is already a branch
at origin
we can simply close the pull request — if we are not interested in this update — but don’t delete the branch. Next time the DependencyBumper
runs it will detect that the branch
is already available and skip the part where it’s created the pull request.
A still open challenge for us is to update dependencies together which belongs together. As you see in the JSON
example above, the dagger
and dagger-compiler
dependencies should be updated together. It doesn’t make any sense to have version 2.23
for dagger
but version 2.24
for the dagger-compiler
in a single code base. Currently, our tool creates a pull request for each dependency — which makes no sense.
That’s basically it. But — to be honest — when this topic raised the first time I was totally against this. I had the fear that it will force us to blindly merge these pull requests as long as our CI is green. Without taking a look at a changelog or release notes. Surprisingly, after the first round of updates (there were a lot ) I noticed the exactly opposite. We looked for all changes for each dependency and commented on mentionable changes in the pull request! This made the changes visible to the whole team which is not the case if a dependency is updated while working on a new feature or fixing a bug.