Dependency hell is where you are when version lock and/or version promiscuity prevent you from easily and safely moving your project forward.
In short, semver specifies the first number should indicate breaking API changes, the 2nd should indicate added functionality, and the 3rd should indicate backwards-compatible bugfixes. With such a strong focus on API changes, semver is most commonly used for shared libraries. The rules of semver already help dependency management tools like Maven, NuGet, and pip understand when libraries are out of date and when they can be safely updated. Despite how many developers benefit implicitly from semver, many are unaware that semver can be easy to maintain with the right tools.
One tool that I like is bumpversion. Although it’s written in Python, it can be used in any build process to “bump versions”. On the command-line, you can use bumpversion like this:
bumpversion --current-version 0.5.1 minor src/VERSION
That would update the version number
0.6.0 in the file
src/VERSION. However, if you have already wired your build to use only 1 file, then you’ve already made semver easy. Don’t add one more tool just on my say so. The more compelling usage is with a
.bumpversion.cfg file in the repo. It looks something like this:
[bumpversion] current_version = 0.5.1 [bumpversion:file:setup.py] [bumpversion:file:README.rst]
This file plays the same role as the previous
src/VERSION file, in that it records what the current version is. But it also stores options for bumpversion that will propagate the version to other files. In the above example, there are version numbers that need replacing in both
README.rst. I can update both with the simple command:
This time I don’t have to specify the current version, or the files that need updating. When it runs, bumpversion will modify both files, and the
current_version variable. Obviously, now these 3 file changes have to be committed back to Git. Bumpversion can do that with the
--commit switch or the
commit = True option in the
.bumpversion.cfg file. Also, it is convenient to track a release back to the code at that point. Again, bumpversion can do that, using the
--tag switch or the
tag = True configuration option. In either case, don’t forget to push when you’re done. I usually wait for the full build process to complete, including the upload to the appropriate artifact repository, before I push the changes. That way I can roll back the changes if things go wrong. Thanks to my colleage Nicola Paolucci’s treasure trove of git aliases, I can issue the following commands to roll-back the bump:
git reset --hard HEAD~1 # rollback the commit git tag -d `git describe --tags --abbrev=0` # delete the tag
Or, you can save some time when trying out bumpversion by using the
--dry-run option. That will show you the changes it’s going to make without writing them to disk. I’ve only covered the most basic options here, but there are options to account for alternate versioning schemes, or additions to standard semver, like alpha/beta builds or release candidates.
Some parts are still manual, like when you decide to run bumpversion and which kind of release bump to make. Nevertheless, I still find it a big help to make sure multiple files are updated consistently, and in accordance with the rules of semver. I hope you find it useful too. If you do or have your own tips about semantic versioning, tweet me at @devpartisan or my team at @atlassiandev.