Generating releases for your project shouldn’t be a chore, yet many times it does prove to be a pain. If you don’t release very often or on a regular schedule, you have to go back and remember how to do it. This can result in inconsistencies in your releases, which makes it harder on your users. Read on to learn how you can define steps in your Bitbucket Pipelines to automate your project releases.
I recently set up automated releases for a C++ project in Bitbucket. This process automates the steps to create a release branch, bump the version per Semantic Versioning, generate a changelog according to Conventional Commits, and push the release back to git, fully tagged and ready to go. Here is how I did it.
Define the common steps in your pipeline
# Default image to use - version 3.x
image: atlassian/default-image:3
definitions:
services:
docker:
memory: 7128
steps:
- step: &Build-Application
name: Build Application
image: rikorose/gcc-cmake
size: 2x
script:
# Update the submodules
- git submodule update --recursive --init
# Install the dependencies
- apt-get update && export DEBIAN_FRONTEND=noninteractive
- apt-get -y install --no-install-recommends uuid-dev libssl-dev libz-dev libzmq5 libzmq3-dev
# Print the Linux version.
- uname -a
# Print the gcc version.
- gcc --version
# Print the CMake version.
- cmake --version
# Setup the build
- mkdir _bld && cd _bld
# Call CMake
- cmake -DCMAKE_BUILD_TYPE=Debug ..
# Build project
- make -j10
- step: &Build-Container
name: Test Container Build
size: 2x
script:
# Update the submodules
- git submodule update --recursive --init
# Build the container
- docker build --file ./Dockerfile .
services:
- docker
Code language: PHP (php)
In this snippet, I define which image to use by default for all the steps. I chose to use the default Atlassian image, but specified version 3. If you do not specify a version here (with the :3
) you wind up with a really old version of the image that is kept around for backwards compatibility.
I also define two common build steps, called Build-Application
and Build-Container
, which I use later on in my pipeline.
Define the pipeline(s)
pipelines:
custom:
generate-release:
- step:
name: Generate release branch
script:
- git checkout master
- git pull --ff-only
- git checkout -b release/next
- git push -u origin release/next
pull-requests:
'**': # all PRs
- step: *Build-Application
- step: *Build-Container
branches:
master:
- step: *Build-Application
- step: *Build-Container
Code language: PHP (php)
This snippet generates a few pipelines, one that runs each time the master branch is updated on the server, one that runs for every pull request created, and one custom pipeline that must be run manually.
The master branch and pull-requests pipelines are identical and simply utilize the defined steps from the previous step. The custom pipeline however has a single role: create a new branch called release/next
and push that back to the server. As you’ll see in the next section, this will trigger another branch pipeline.
Define the release generation pipeline
branches:
# master branch defined here previously
release/next:
- step:
name: Generate Release
script:
# Configure npm to work properly as root user in Ubuntu
- npm config set user 0
- npm config set unsafe-perm true
# Install necessary release packages and generate release, pushing back to repo
- npm install -g release-it @release-it/conventional-changelog @j-ulrich/release-it-regex-bumper --save-dev
- release-it --ci
- parallel:
- step:
name: Publish to External Continuous Delivery System
script:
- export APP="name_of_app"
- git clone --recursive https://url.of.your.cd.com/your-cd-repo.git
- cd containers
- git checkout testing
- git submodule update --recursive --init
- cd ${APP} && git checkout master
- git pull
- export VERSION=$(git tag | sort -V | tail -1)
- >
echo "Updating ${APP} to Release Version: ${VERSION}"
- git checkout ${VERSION}
- cd ../
- git add ${APP}
- >
git -c user.name='Bitbucket Pipeline' -c user.email='bitbucket-pipeline@witricity.com' commit -m "${APP}: update to version ${VERSION}"
- git push
- step:
name: Create Pull Request
caches:
- node
script:
- apt-get update
- apt-get -y install curl jq
- export DESTINATION_BRANCH="master"
- export CLOSE_ME="true"
- >
export BB_TOKEN=$(curl -s -S -f -X POST -u "${BB_AUTH_STRING}" \
https://bitbucket.org/site/oauth2/access_token \
-d grant_type=client_credentials -d scopes="repository" | jq --raw-output '.access_token')
- >
export DEFAULT_REVIEWERS=$(curl https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/default-reviewers \
-s -S -f -X GET \
-H "Authorization: Bearer ${BB_TOKEN}" | jq '.values' | jq 'map({uuid})' )
- >
curl https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/pullrequests \
-s -S -f -X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ${BB_TOKEN}" \
-d '{
"title": "Release '"${BITBUCKET_BRANCH}"'",
"description": "Automated PR release :)",
"source": {
"branch": {
"name": "'"${BITBUCKET_BRANCH}"'"
}
},
"destination": {
"branch": {
"name": "'"${DESTINATION_BRANCH}"'"
}
},
"close_source_branch": '"${CLOSE_ME}"',
"reviewers": '"${DEFAULT_REVIEWERS}"'
}'
Code language: PHP (php)
This is a rather large block, but is pretty straight-forward.
The first step, called “Generate Release”, is where the release magic happens. It uses the NPM tool called release-it to generate the release. Basically, this utilizes a configuration file in the repository named .release-it.json. Based on that file, it will automatically do the following:
- Bump the version, based on how you define it in .release-it.json
- Generate and update a changelog
- Git commit, tag, and push
- And much more if you so choose…
Since this is run in the release/next
branch, the version, changelog and all other changes are made and pushed here. At that point, I wanted to do two things: first, publish this new release to my external continuous delivery system; and second, automatically generate a pull request in Bitbucket to get the release back in the master branch.
Note, that when installing release-it on Ubuntu 22.04 (or older), you may run into issues with older versions of nodejs. To remedy this, run these commands:
# Remove old version of nodejs
sudo apt-get purge nodejs
sudo apt-get autoremove # remove any lingering dependencies
# Install updated nodejs (20 is latest at time of this writing)
curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Finally, install release-it
npm install -g release-it @release-it/conventional-changelog @j-ulrich/release-it-regex-bumper --save-dev
Code language: Bash (bash)
My .release-it.json file looks like this:
{
"git": {
"commitMessage": "[skip ci] ci: release v${version}"
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": {
"name": "conventionalcommits",
"commitUrlFormat": "{{host}}/{{owner}}/{{repository}}/commits/{{hash}}",
"compareUrlFormat": "{{host}}/{{owner}}/{{repository}}/compare/{{currentTag}}..{{previousTag}}",
"types": [
{
"type": "feat",
"section": "Features"
},
{
"type": "fix",
"section": "Bug Fixes"
},
{
"type": "perf",
"section": "Performance Improvements"
}
]
},
"infile": "CHANGELOG.md"
},
"@j-ulrich/release-it-regex-bumper": {
"out": [
{
"file": "CMakeLists.txt",
"search": "VERSION {{semver}}",
"replace": "VERSION {{versionWithoutPrerelease}}"
},
{
"file": "Dockerfile",
"search": "Version={{semver}}",
"replace": "Version={{versionWithoutPrerelease}}"
}
]
}
}
}
Code language: JSON / JSON with Comments (json)
At this point, to generate a release, just go to the pipelines page for your repository and select Run pipeline. Then choose what branch you want to use for the basis of your release (I typically release from master
) and choose the ‘custom: generate-release’ pipeline and off you go!
Conclusion
This process greatly simplifies my life when it comes to release a new version of my projects. Could this be fully automated? Absolutely — I’m just not there quite yet.
I hope you find this useful!
Comments