Automated release notes for Android

Alessandro Mautone
6 min readJan 16, 2020

--

We always struggled a bit in finding a nice way of automatically generating release notes for our Android project: we first started writing them manually, then using the first commit message of every PR, but it wasn’t really working.

Finally, I found a way to use PR titles, which are usually way more descriptive and useful to an eventual tester than, for example, commit messages.

I am going to share here our final setup.

Note: We are using Bitrise, but this should work with any similar tool since the main logic is done in bash scripts :)

Setup “Release Drafter”

Release Drafter is a Github app that allows you to keep a release draft with a body containing merged PR titles since the last published release.

Configuring it is very easy, you can just follow the steps at the official page https://github.com/marketplace/actions/release-drafter

⚠️ Note: there is a Github App and a Github Action for this tool, it is highly recommended to use the Github Action since the app is going to be deprecated soon.

Our current setup runs every time we push something on our develop branch, so we have something like:

name: Release Drafter

on:
push:
# branches to consider in the event; optional, defaults to all
branches:
- develop

jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
# Drafts your next Release notes as Pull Requests are merged into "master"
- uses: release-drafter/release-drafter@v5.6.0
with:
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
config-name: develop-config.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Note: if you are not going to specify a different file for the config name, then completely remove the with block, otherwise, it will fail.

GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

^This doesn’t need to be replaced, it’s a token GitHub provides by default for actions.

Nice to have: In the Release Drafter template configurator I’ve put a few labels to categorise features and bug fixes. I’ve also added a label for excluding some PR titles in the changelog, pretty useful for some PRs that are not really meaningful in the changelog.

CI setup

Now that we are done with the setup of the GitHub action, we can now focus on setting up our CI. As mentioned earlier, in our project we use Bitrise, but that shouldn’t change that much on other CIs since we are going to focus on executing some bash scripts.

A bit of context here: we have an automated weekly workflow that takes care of fetching the latest develop branch, making a beta build and delivering it (you can choose how: alpha play store, firebase, etc).

❌ At every beta build, we used to create a tag on GitHub through Bitrise with the release notes in the relative body. We were getting the first commit message of every PR and then putting them together to build our release notes, but it wasn’t really working well.

✅ With our current setup, we:

  • Get the latest tag.
  • If there are new commits since the latest tag, proceed, otherwise stop the process (we don’t want to deliver the same build twice)
  • Assuming there are new commits -> take the latest tag and increase its version
  • Take the latest draft release from GitHub, add the tag name and change its status to published.

Let’s see how can we achieve that.

Getting latest tag version and commits since it

We are going to end with two bash scripts, the first one is going to get the latest tag, see if there are new commits since it and increase the tag version if so.

We can simply get the latest tag by doing

latestTag=$(git describe --tags --abbrev=0)

and then get the commits since this tag doing

commitsSinceLastTag=$(git log $latestTag..HEAD)

Now, if the variable commitsSinceLastTag is empty, it means no commits were done since the latest tag, and we can end our CI workflow here. Otherwise, we can take the tag and increase its version.
Here is our full script

#!/usr/bin/env bash

#fetch the history
git pull --rebase
latestTag=$(git describe --tags --abbrev=0)
commitsSinceLastTag=$(git log $latestTag..HEAD)

# Check if there are commits since latest tag
if [ -n "$commitsSinceLastTag" ]; then
echo "There are commits since last tag"

latestTagNumber=""
# Splits passed var by specified separator
IFS='_'
read -ra ADDR <<<"$latestTag"

# Gets beta number
latestTagNumber=${ADDR[1]}

# Increases beta number
latestTagNumber=$(($latestTagNumber + 1))
echo $latestTagNumber

# Generates new beta name and stores it
newTagToPush="Beta_"${latestTagNumber}
echo $newTagToPush
export GIT_BETA_TAG="$newTagToPush"
envman add --key GIT_BETA_TAG --value "$GIT_BETA_TAG"
else
echo "There are NO commits since last tag"
exit 1
fi
latestTag=$(git describe --tags --abbrev=0)
commitsSinceLastTag=$(git log $latestTag..HEAD)

# Check if there are commits since latest tag
if [ -n "$commitsSinceLastTag" ]; then
echo "There are commits since last tag"

latestTagNumber=""
# Splits passed var by specified separator
IFS='_'
read -ra ADDR <<<"$latestTag"

# Gets beta number
latestTagNumber=${ADDR[1]}

# Increases beta number
latestTagNumber=$(($latestTagNumber + 1))
echo $latestTagNumber

# Generates new beta name and stores it
newTagToPush="Beta_"${latestTagNumber}
echo $newTagToPush
export GIT_BETA_TAG="$newTagToPush"
envman add --key GIT_BETA_TAG --value "$GIT_BETA_TAG"
else
echo "There are NO commits since last tag"
exit 1
fi

⚠️ Note: our beta tags are in the format Beta_betaNumber , therefore we increase our version based on that.

In our CI we store the beta tag in an environment variable GIT_BETA_TAG so that we can use it in another step.

Publish the latest release in draft status on GitHub

Now that we have our increased tag, we can add it to our latest release in the draft status. This will be our second script and we are going to achieve this by using the GitHub REST APIs.

In order to edit a release we need its id, so we are going to get all of them with the GET /repos/:owner/:repo/releases endpoint and then filter the one with the valuedraft: true. (this is assuming we never have 2 draft releases at the same time, which is our case)

Since parsing JSON objects in bash is not fun at all, I installed jq , which is a tool that simplifies a lot JSON parsing. You can install it in your CI by doing:

sudo apt-get -y install jq

⚠️ -y will ensure that any do you want to continue? requests when installing jq are going to be accepted.

After that, we can get our releases, filter the one we need and get its id, and finally edit it publishing it and our increased tag version. To do that we use the PATCH /repos/:owner/:repo/releases/:release_id endpoint from GitHub.

Here is our script:

#!/usr/bin/env bash

releaseId=$(curl -u $GITHUB_USERNAME:$GITHUB_TOKEN https://api.github.com/repos/[OWNER]/[REPO]/releases |
jq '.[] | select(.draft == true) | "\(.id)"')

releaseIdWithoutQuotes=$(sed -e 's/^"//' -e 's/"$//' <<<"$releaseId")

curl -u $GITHUB_USERNAME:$GITHUB_TOKEN --request PATCH "https://api.github.com/repos/[OWNER]/[REPO]/releases/$releaseIdWithoutQuotes" --header "Content-Type: application/json" --header "Accept: application/json" --data "{\"tag_name\": \"$GIT_BETA_TAG\", \"target_commitish\": \"develop\", \"name\": \"$GIT_BETA_TAG\", \"draft\": false, \"prerelease\": false}"

The important part here is the body of the request, through which we are going to set up the desired parameters, for example:

{
"tag_name": "Beta 23",
"target_commitish": "develop",
"name": "Release Beta 23",
"draft": false,
"prerelease": false
}

⚠️ Note that we are not including the body parameter since that is already nicely filled by the Release Drafter we set up at the beginning :)

After this step, we will have our latest release published under GitHub releases!

Bonus content:

  • We used to have the scripts directly in our CI, that is a bit dangerous because you can lose them every time we make a change. This time we decided to store them in the project itself, and we invoke them from the CI.
  • If you need to quickly test your bash scripts you can work on them directly in Android Studio, there is a nice plugin that comes bundled in AS called Shell Script that supports running them quickly, styling and code autocompletion.
  • Always keep your secrets out of the scripts

In case you use Bitrise or you are familiar with it and you are wondering how a similar workflow looks like, here is an example:

Bitrise workflow example

As you can see, after getting our repository setup to run, we are just running 3 steps which invoke the related scripts described in this article. After this workflow is done, another one is going to be triggered to build a new release and upload it to the preferred tool.

--

--

Alessandro Mautone

Android Lead Engineer @Canyon 🤖 🇻🇪🇮🇹 Paraglider, Runner, Kayaker.