Image by Manu Cornet
Version compatibility is a core concept in Savant. Even before Semantic Versioning was first published, Savant was built on the concept of major, minor, patch compatibility. This concept was formalized into the Semantic Versioning specification. This specification is quickly becoming the industry standard for versioning software.
The quick overview of Semantic Versioning goes like this. Versions are broken up into 4 parts:
Compatibility between versions is determined as follows:
Major versions are not compatible. That means that 1.0.0 and 2.0.0 are not compatible.
Minor versions must be compatible. This means that 1.0.0 and 1.1.0 must be compatible.
Patch versions must be compatible. This means that 1.0.0 and 1.0.1 must be compatible.
Pre-release versions must be compatible for Minor and Patch versions, but can be incompatible for Major version. This means that 1.0.0-Beta.1 and 1.0.0-Beta.2 don’t need to be compatible, but 1.1.0-Beta.1 and 1.1.0-Beta.2 must be compatible.
The only time these rules don’t apply are for initial releases under 0.x. In this case, minor versions do not need to be compatible. Therefore, 0.1.0 and 0.2.0 are not required to be compatible.
Version Compatibility is complex when you start to introduce transitive dependencies. Let’s look at a simple dependency graph:
This graph is simple but shows that both A and B have a dependency on C. A has a transitive dependency on C while B has a direct dependency on C. Let’s add some versions to this graph:
Now we have dependencies on two different versions of C, 1.1 and 1.3. In this case, Savant uses Semantic Versioning to determine that it should only include version 1.3 of C. But how was Savant able to confidently make this decision?
We know that our project only uses features and APIs from C that are in version 1.1. However, B might be using features and APIs that were added in version 1.3. Therefore, if Savant were to include version 1.1 of C, it might break code inside B that depends on version 1.3 of C. Because only the minor versions are different and minor versions must provide API compatibility Savant can upgrade to version 1.3 of C with confidence that will not break A.
Let’s make a minor change to our dependency graph:
Now we have two dependencies on C, but they are version 1.3 and version 3.0. According to Semantic Versioning, these two versions aren’t compatible. In this case, Savant will produce an error that looks like this:
The artifact [C] has incompatible versions in your dependencies. The versions are [1.1, 3.0]
This causes the build to fail until the version compatibility can be fixed. This feature is crucial to understand how project dependencies impact compile and runtime behavior. While failing the build may seem heavy handed, finding an API incompatibility at runtime may be catastrophic and debugging a classpath bug outside of a development environment is difficult.
In some cases, you have full control over these versions and can simply upgrade the projects to all use the same version. Sometimes you don’t have access to the project that needs to be upgraded. In this case, you have a few options: 1) fork the other project and upgrade it yourself, 2) ask the maintainers of the other project to upgrade and wait, 3) tell Savant to skip the compatibility check.
If you have to resort to using solution #3, Savant provides the ability to ignore the version compatibility issue for that single dependency. You can consult the Savant documentation to learn more.
Savant is one of the first build tools to use and enforce Semantic Versioning. Since this is a relatively new concept in the industry, there are a large number of libraries whose versions are not semantic. As the industry switches to building with Savant and embraces Semantic Versioning these problems will be greatly reduced.
For more about Inversoft Open Source projects click on the image below.