A Beginner's Guide to Semantic Versioning

A Beginner's Guide to Semantic Versioning

July 9, 2023

An image showing the breakdown of each Semantic Version number

Semantic Versioning (SemVer) is a versioning scheme used in software development to provide a systematic approach for managing version numbers. It’s common for developers to initially find SemVer confusing, especially regarding version increments and release candidates. Understanding SemVer begins with grasping the problem it solves.

Purpose of Semantic Versioning

Semantic Versioning primarily aims to communicate meaningful information about software releases. By adopting a structured versioning scheme, developers understand the implications of new versions, such as breaking changes, new features, or bug fixes. SemVer ensures compatibility and predictability in dependent software components by defining clear rules for version number increments.

That’s to say, each version increment is trying to tell the users of the API something: does this new release contain breaking changes, new back-wards compatible features and enhancements, or bug fixes? Should I update my dependency on it?

When and Why to Use Semantic Versioning

Semantic Versioning is particularly useful when developing and distributing software libraries, services or frameworks where other developers’ dependencies matter. It clearly communicates the nature and impact of changes, enabling informed decisions about integrating new releases. SemVer also enhances efficiency in change tracking, version dependencies, and release management in team collaborations and large-scale projects.

How to Use Semantic Versioning

Semantic Versioning employs a three-part version number in the format MAJOR.MINOR.PATCH. with each part signifying a different impact of the release:

  • MAJOR: Incremented when making incompatible changes or introducing breaking API changes.
  • MINOR: Incremented when adding new features or functionality in a backward-compatible manner.
  • PATCH: Incremented for backward-compatible bug fixes or patches.

When assigning version numbers, follow these guidelines:

  1. Start with version 1.0.0 for the initial release. Versions below 1.0.0 in Semantic Versioning are considered unstable and may undergo frequent changes.
  2. Are there any breaking changes? If yes, increment the MAJOR version +1.0.0.
  3. Are there any new features that are backward-compatible and doesn’t require code for systems that depend on your API? If yes, increment the MINOR version *.+1.0.
  4. Are they bug fixes that are backward-compatible and doesn’t require code for systems that depend on your API? If yes, increment the PATCH version *.*.+1.

Creating Release Candidates

ℹ️
Release Candidates (RCs) depend on your branching and release strategies. This is just an example of a branching/release strategy, by no means is this the only way to do it and it probably doesn’t make sense for your own use case. The important thing is to understand the concepts and apply them to your own needs. If you do not need release candidates, you can skip this section.

An image representing a simplified view of the release process, from test, to staging, and to production.

Your use of Release candidate needs will vary depending on your branching and release strategies. But to make it easier to understand, let us assume the following branching strategy:

  • main branch is the mainline branch where all changes are merged into.
  • Work is done on feature branches, and merged into main via Pull Requests.
  • As soon as code gets merged to main, it is deployed to a test environment. At this point they are not tagged with a version number, and we will refer to them by their commit hash.
  • Release Candidates are created from main and deployed to a staging environment. This is done after it passes all the checks on test.
  • Release Candidates are tested and validated in the staging environment. If any issues are found, they are fixed on the main branch and a new Release Candidate is created, otherwise it is promoted to a Release.

Before a version is officially released, it often goes through a testing and validation phase. This is where release candidates come into play. Release candidates (often referred to as RCs) are pre-release versions that undergo testing to identify and fix any critical issues before the final release. This is not a requirement for Semantic Versioning, and may not apply to every release use case, more more on that later.

To create a release candidate, I typically follow these steps:

  1. Create a new branch or use an existing branch dedicated to the release.
  2. Apply the necessary changes or bug fixes on the release branch.
  3. Assign a unique identifier to the release candidate, such as rc.1, rc.2, and so on.
  4. Publish the release candidate for testing and gather feedback from users or testers.
  5. Continuously address any issues or bugs discovered during the testing phase.
  6. Iterate the process until the release candidate is stable and free of critical issues.

This will allow for thorough testing and validation, ensuring a high-quality release that meets the desired standards. Sometimes releasing faster is more desirable then releasing safer, so the release candidate phase would be skipped.

It’s important to note that when a hotfix occurs, any release candidate in staging becomes outdated. In such cases, it is necessary to backmerge the changes from the outdated release candidate and create a new release candidate with an updated version number. This ensures that the hotfix and all other changes are properly incorporated into the production release.

When not to use release candidates

As you can see from the recommended steps above, having Release Candidates requires that new software changes will go through a flow of validation and testing before it can be made available to the public. There are many cases where RCs will not be used. This is highly dependent on your release and branching strategies, for example:

  • Frequent and Continuous Deployment: In a continuous deployment scenario where changes are deployed frequently and automatically, the concept of traditional release candidates may not be applicable. Instead, thorough testing and validation can be integrated into the deployment pipeline, ensuring that changes are properly tested before being rolled out.

  • Experimental Features or Prototypes: When working on experimental features or prototypes where stability and compatibility are not critical factors, it may be more appropriate to use separate branches or tags for experimental versions rather than formal release candidates.

  • Small Projects or Personal Projects: For small projects or personal projects with a limited user base and minimal impact from changes, the overhead of creating and managing release candidates might outweigh the benefits. In such cases, a simpler versioning approach or less formal testing may suffice.

  • Some Internal-Only Releases: If you are developing software solely for internal use within your organization and there are no external dependencies, using release candidates might be unnecessary. Internal releases can be tested and validated within a controlled environment before being deployed without the need for formal release candidate stages.

Transition from Release Candidates to Release Versions

Once a release candidate has passed all necessary tests and validations, it can transition into an official version. Typically, this transition occurs when no critical issues or showstoppers remain in the release candidate.

To promote a release candidate to an official version, it is good practice to follow these steps:

  1. Review the feedback from users or testers and address any non-critical issues or bugs. In some cases, the RC will not be available to users, and the tester is the developer who created the RC in the first place. It is still good practice to validate it before deploying.
  2. Update the version number based on the significance of the changes made since the last release.
  3. Increment the MAJOR version for breaking changes, MINOR version for new features, and PATCH version for bug fixes.
  4. Tag the commit that represents the release candidate with the new version number.
  5. Communicate the release and its associated version number to users and stakeholders.

An image representing the branching strategy exemplified in this post

When multiple changes exist, it is important to identify the highest level of change among them. For example, if a release candidate contains a bug fix and a new feature, the release version should be incremented as a MINOR version. This is because a MINOR version change indicates the addition of new features, which is a higher level of change than a bug fix.

Automating Release Versioning with GitHub Workflows

Automating the release versioning process can streamline the release management workflow and reduce manual errors. GitHub Workflows, a feature provided by the popular code hosting platform GitHub, can be used to automate the process of assigning version numbers based on the changes made.

Here’s an example workflow to automate release versioning:

  1. Define triggers: Configure the workflow to trigger on specific events, such as pull requests or pushes to specific branches.
  2. Determine version changes: Use scripts or tools to analyze the changes made since the last release and determine the appropriate version increments. There are open source actions that can be used for this.
  3. Set version number: Update the version number in the necessary files or configuration files using the determined increments.
  4. Commit and tag: Commit the updated version number and tag the commit as the release point.
  5. Publish release: Notify users and stakeholders about the new release by publishing the release notes, documentation, and associated artifacts.

Conclusion

Semantic Versioning is a helpful practice in software development, providing a standardized approach to versioning that promotes compatibility, communication, and reliable release management. By understanding its purpose, guidelines, and best practices for creating release candidates, managing different levels of changes, and automating versioning with GitHub workflows, we can effectively manage comunicate and facilitate collaboration among teams and users.

It also allows us to maintain a clear and structured release history, enabling developers to make informed decisions about incorporating new releases and ensuring smooth integration and compatibility in complex software ecosystems.