Android Pro Tip: Generating your app’s changelog from Git inside build.gradle
Welcome to my Android Pro Tip’s series — where I’ll try to share the most interesting, useful & little known tips & tricks I’ve learned in my 8+ years of being a freelance Android developer.
To start with we’re going to start with one of the more boring but advantageous components of your app — the changelog.
I’ve always shipped changelog’s with my apps as they’re a nice way of keeping users updated of what’s changed in the app. Especially if it’s a bug fix they’ve been waiting for, or a feature that they might not realize has been added unless they’re explicitly informed. Hell I even built a library to reuse across all of my apps so I didn’t have to keep building the front end for it over & over again!
The annoying part of this setup however was always actually writing the changelog. Mostly because I’d forget and ship a build with an old changelog 🙈 but also because when I actually remembered I’d find it pretty tedious to go back through my commits to remember what’s changed, so I could write the damn thing.
I recently discovered a pretty nifty way to automate this process entirely using the app’s own git log.
Git -> Changelog
During a recent project there was a requirement to add a changelog to the app for internal test builds, so testers could see what changed in a build from one build to the next. However this particular project had multiple Android developer’s on it so wasn’t exactly feasible come release time to manually write the changelog without involving all the developer’s for their input on what’s changed & how we express that to the user. So instead, after a little investigation, I came up with this little solution that run’s from inside the build.gradle file - completely automating the process of generating the changelog, building it as a String in the BuildConfig file which I can then access statically from our code base & display directly to the user in our changelog UI.
The benefits of this approach
- It uses commit messages as the changelog item so nothing is ever missed! We adopted a “Squash & Merge” approach to accommodate this setup so instead of this approach listing every single commit in a PR, which could be numerous & very techincal, we instead title our PR’s appropriately when we squash & merge with a user facing title, such as “Fixed crash when clicking on user profile”. That non-technical user readable string is what ends up in the changelog.
- Your changelog will always be up to date. This method builds the changelog from the last tagged commit until now. So as long as you tag your release’s appropriately — your changelog will always be up to date.
- It requires no developer intervention whatsoever. It run’s when you build your APK so every time you commit, a new changelog item will appear in the next build.
- It’s easily customizable to meet your needs or even ignore commits based on some specific string identified. For example, if you don’t want a specific commit listed in the changelog you could prefix it with “**” & then just ignore commit’s prefixed with that when building the changelog.
Sounds easy enough right? Let’s look at the code.
The code
To get started converting our git commits into changelog items, we firstly need to get the all the commits between now & the previous app release. If you’re tagging your builds when you release (and if you’re not why the hell not!) then this is super easy. Firstly you get the last release tag using the git command:
git describe --tags --abbrev=0
Next you can generate a list of commits since that tag, prettified into a format we can use, using the git command: (where $lastTag is the result of the previous command)
git log $lastTag..HEAD --oneline --no-merges --pretty=format:"%s"
The good bit about this being you can run both commands in your terminal, outside of Android Studio, to see exactly what is going to be output without having to keep re-syncing your Gradle file to run the task & see the result.
Once we’ve got that all we need to do is loop each line, format it in a way we want & append it to our changelog string — easy!
We also need to make sure we escape special characters as we’ll be outptting the result of this Gradle task into a String inside our BuildConfig file. As this is a Java class we’ll need to make sure any special characters are escaped so the final string produced is a compliant Java String.
The final Gradle task will look something like this.. (with println used to output useful information into our Gradle build output for debugging purposes)
String generateChangelog() {
println "Generating changelog.."//Get the last tag
def lastTag = "git describe --tags --abbrev=0".execute().text.trim()//Get all the commits since the last tag
def gitLogCmd = "git log $lastTag..HEAD --oneline --no-merges --pretty=format:\"%s\"".execute().text.trim()//Loop each line of the commits to build your changelog
def changelog = "\""
gitLogCmd.eachLine { line ->//Remove surrounding quotation marks generated by the git log comand
def escapedLine = line.substring(1, line.length() - 1) //Escape backslashes
escapedLine = escapedLine.replaceAll(/(\\)/, "\\/") //Escape quotation marks
escapedLine = escapedLine.replaceAll('"', '\\\\"') //Add each item to the changelog as a bullet point
changelog += "• $escapedLine \\n"
}//Close the changelog string
changelog = (changelog + "\"").trim()//Useful log so you can see what was generated in the Gradle output
println "Changelog generated, $changelog, from $lastTag to now." return changelog
}
Nearly done
Now that you can generate the changelog from inside your build.gradle file, you just need a way to make use of it. As I mentioned before, I wanted it to be statically accessible inside the app’s BuildConfig file so to do this, I added the following buildConfigField to the defaultConfig section inside of the app’s build.gradle file like so:
android {
...
defaultConfig {
...
buildConfigField "String","CHANGELOG","${generateGitChangelog()}"
...
}
...
}
Et voila! You end up with a single, bullet pointed Java string, containing every change since your last release, easily accessible by calling BuildConfig.CHANGELOG. Easy.
Side note
As not everyone’s requirements are the same- if, for example, you don’t want a single bulleted string but want each item separate so you can output it into a RecyclerView (like I do in my own app’s using the open source library I made specifically for this) you could use the same approach but use something like BuildConfig.CHANGELOG.split(“\n”), to create an array of your changelog items, split on the newline.
Job done
So there you have it. An auto generated changelog — always up to date, easily customizable, easily accessible.. easy!
If you have any thoughts or feedback on this approach, or know of a better way of doing this I’d love to hear what you think in the comments! Also if you have any requests for little hacks or tip’s like this you might be interested let me know & I’ll see what I can do!
Let me know what you think! 🤓