Unless you keep your programs only for yourself, you would want to give them a version. Versioning is a popular technique where a unique version number is assigned to a program to denote a specific state of the code of that program at a specific time. A version number is used as a reference to what the program, as a deliverable, includes in terms of functionality. This reference is expressed in words that your users would understand. Furthermore, if your users experience a problem, you could try to reproduce it on that same state of the code.
There are many versioning schemes that dictate how to generate new version numbers. When trying to implement continuous integration (and delivery), though, you may face some challenges. In this blog post I will try to explain some of these challenges and what options there are.
Build once, deploy many
At some point you would want to deploy your program to another environment, so that other people can access it. Many developers create a separate build (and hence a separate package) for each environment. Although, it may be necessary in some situations, this approach could possibly introduce some issues. If an error occurs on some of the environments, you would have to check both your code and the environment. When you move your code across environments you could get many of these issues as it is different code you deploy every time. A better approach is to generate one deployment package only, which you deploy to each environment. The only difference usually is your configuration, which is prepared by a deploy script that is unique for each environment. In this case you decrease the possible errors you should check as you deploy exactly the same code.
Semantic versioning
Semantic versioning (semver) is a versioning scheme which provides some rules for you to guide you when creating new version numbers. Version numbers are in the form Major.Minor.Build, where one increments:
- Major when there are incompatible changes
- Minor when there are new compatible features
- Build when there are compatible bug fixes or partial features
In the latest version of the scheme, you can also append optional string to describe the version number, f.x. 1.2.3-master. I often use Semantic versioning for different projects as it makes it easy to structure my versions. However, when used together with continuous integration (and deployment later), it introduces some challenges.
Continuous integration
Continuous integration (CI) provides a mechanism to ensure your code is always deployable. Every time a developer commits changes to the version control system (VCS), a series of steps are performed: building the code, running unit tests, performing code analysis, etc. The result is a code that is ready to be packaged. I have been using TeamCity for applying CI and I am happy with it due to its broad integration options with other systems. When using semantic versioning with CI you have to generate a version on every change you commit. TeamCity (and other similar CI tools) generates an auto-incremented value for your builds, i.e. every time a build is triggered, you can get versions like 1.2.45, 1.2.46, etc. The problem is these versions don’t comply with semver’s model, as the CI server is not aware of what kind of changes you have made. To generate valid semver version numbers I use GitVersion – a simple tool that works out the semantic version of the commit being built. It has also very neat integration with TeamCity. Using GitVersion you get version numbers like 1.0.4-unstable.1, 1.0.6-beta.2, etc.
I have been successfully using this tool with gitflow – a branching model for git repositories, where you work with two primary branches: develop is your working branch and master is your production branch. When I want to create a package to be deployed to my test environment, I would create a tag in my repository with the desired version number, f.x. release-1.4.5. This will trigger build in TeamCity and a package with this version will be created. In this scenario I have full control over the generated version number as I, as a developer, know what changes I have made. Although GitVersion supports beta version numbers, I don’t use them. I use only “normal” version numbers in form of x.y.z, as this allows me to promote my package from one environment to the other without creating a new package with a new version. I cannot predict whether there will be issues with my code on test, so if I have a package with version 1.3.4-beta.1 and there are no issues, I don’t want to deploy such a version to production – I want just 1.3.4.
Continuous delivery
Continuous delivery is the step after continuous integration where after going through the entire process of ensuring your code is deployable, a deployment package is created and deployed to your test environment (some people have a development environment before test where, for example, integration tests are run). If we follow the same procedure like before, though, we would get a package with a version like 1.4.5-unstable.34. But again if this package contains no errors, it could possibly be promoted to production. With continuous delivery version number generation relies even more on developers and requires a stronger discipline. Before a change it pushed to the repository, a developer should manually set the correct version number based on the changes he has made. If you are using C#, you could introduce GlobalAssemblyInfo file as a central place for versioning your DLLs. GitVersion supports a text file where you can specify the next version number manually.
Release notes
When you create a deployment package you may be interested in knowing what changes it includes. This is especially useful when you have a designated release manager who keeps an eye on everything that moves between your environments. One way to do so is to parse all commit messages from your VCS since the last built package. These messages will be very technical, though, so not everyone in your organization will be able to understand them. If you want to share your release notes with not-so-technical people, you need a separate task-management system. I have good experience with JIRA where we keep our tasks written on a more or less business-understandable language. When performing commits, we have established a convention with the other developers to start every commit with the ID of the JIRA task it is related to. You can then associate your VCS commit messages with your JIRA tasks. I have created a meta-runner for TeamCity which parses VCS commit messages and creates release notes based on the JIRA tasks they are associated with. The generated notes look like this: