Version management

Sönke Ludwig edited this page Aug 25, 2014 · 1 revision

Version management

DUB generally expects version numbers in Semantic Version (SemVer) format to manage selecting the right package versions. To be able to handle exceptional situations, it also supports referencing dependencies based on their file system path or based on their GIT branch. However, using branch based dependencies is discouraged for public packages and has been deprecated starting with DUB 0.9.22 (see also the dedicated section about this topic).

Developers should be aware that the SemVer specification does not only describe how version strings must be formed and how they relate to each other, but also regulates how changes in code affect the version. In particular, no breaking changes are allowed for patch level or minor version changes. Please try to follow these rules as close as possible, as it directly influences how well the package ecosystem will work as a whole. Being able to rely on them makes it possible to use version ranges when specifying dependencies (usually using the ~> operator, see below) and thus increase the chance for different packages to work together and to automatically receive possibly important fixes.

Basic dependency specification

The foundation for all dependency version specifications is the version range syntax. It can have one of the following forms:

  • ==1.2.3: Matches an exact version
  • ~>1.2.3: Matches an exact version or any of the following patch versions (e.g. 1.2.4 or 1.2.13, but not 1.3.0)
  • ~>1.2: Matches an exact minor version or any of the following minor versions (e.g. 1.2.0, 1.2.13 or 1.4.1, but not 2.0.0)
  • >1.2.3: Matches any version larger than the one specified (usually not recommended)
  • >=1.2.3: Matches the specified version or any larger one (usually not recommended)
  • <1.2.3: Matches any version smaller than the one specified (usually not recommended)
  • <=1.2.3: Matches the specified version or any smaller one (usually not recommended)
  • >=1.2.3 <2.0.0: Two relational operators can be used to restrict to a certain version range
  • ~master: Instead of matching a version, matches a certain branch, in this case the "master" branch (deprecated for dependency specifications)

This syntax is used to specify the acceptable versions for the dependencies of a package (see the dependency specification section of the package format specification for more information about the exact format). The package dependencies are the basis for determining a valid dependency selection. A dependency selection describes the set of all direct or indirect dependencies, each having a specific version assigned. It is the prerequisite for most kinds of package operations, in particular for building a package.

Once a valid selection has been found, it is stored in the dub.selections.json file, which is then used on all subsequent invocations. Upgrading to a newer version of the dependencies has to be done either using an explicit dub upgrade command, or by manually editing dub.selections.json. By committing this file to the source repository, you can make sure that everyone else who checks out the source code of the package will get the same versions of the dependencies as you when building. The selection process is thus an important additional measure against build breakage due to changes in dependencies, even when dependencies violate the backwards compatibility rules of SemVer.

Overriding dependency versions locally

Sometimes it may be desirable to override the version of a certain package without modifying dub.selections.json, or to modify them not only for a single project, but system wide. For this use case, there is a version override feature, invoked using dub add-override, dub remove-override and dub list-overrides. The add-override command takes three parameters, the name of a package, a version specification (can be a range), and either a path or a version. When DUB now performs the dependency resolution process to find a valid selection of dependencies, any dependency that would get a version assigned that matches one of the package override specifications gets replaced by the given replacement version or path.

This feature can be especially useful when working on a fork or on a branch of the original package and this code is to be tested in the context of other existing projects that depend in this package. The override feature avoids the need to modify each of these projects.

Deprecation of branch based dependencies

Starting with DUB 0.9.22, the way dependency versions are chosen has changed in a partially fundamental way. All dependencies should now exclusively be specified using version numbers (or version number ranges). Using ~branch style specifications has been deprecated due to some serious theoretical and practical issues that have surfaced over time.

At the time of writing, about one third of the packages in the registry haven't had a single version tag, so all packages depending on one of those are affected by the deprecation process. However, the deprecation phase will last for at least a year and probably more, so that there is no imminent breakage due to this change and all package authors should have enough time to update their code. The rest of this section explains the reasons for this step in more detail, as well as presenting some alternatives for common situations where ~branch dependencies are actually desirable.

Rationale

Without requiring the use of proper versions, it has proven that most packages will end up without any versions whatsoever - more than 60% of the packages on <code.dlang org> have been that way before starting to enforce an existing version tag upon registration. This made it impossible to depend on a certain version of those packages to avoid build breakage over time, and thus resulted in a very fragile package ecosystem. And while making it possible to depend on certain commit hashes would be a possible solution for this issue, it would also tie DUB closer to the version management system and wouldn't allow specifying ranges of commits as versions do.

But worse, branch and version based dependency specifications are fundamentally incompatible with each other. When the same package is referenced once by branch and once by version, there is no theoretically correct and not even a practical way to resolve this conflict to a single specific version or branch. Either choice could be the right one, but since there is no visible relationship between the two (from DUB's perspective), either choice could lead to build failures, even if both dependency specifications on their own have been perfectly valid.

The only valid choice here is to flag this as a conflict and stop the build. But that only leads to an even worse scenario, in which all packages will basically be separated into two mutually exclusive partitions - those that use branches and those that use versions for specifying their dependencies. This was already very evident from the existing packages on the registry. Many packages simply couldn't be used together, although it would have worked perfectly fine when taking only the code into account.

In the end, after evaluating many possible alternatives, removing branch based dependencies has been conceived as the only sane solution.

Migrating to version-only dependencies

There are several common situations where it would be inconvenient to work with tagged versions, so DUB offers a number of features to continue to allow working on branches, even without the use of branch based dependencies. The following list goes through some typical situations and how they can now be approached.

  1. Developing a library alongside an application: As a library developer it is often the case that rather than being developed in isolation, the library is developed as part of a separate application package. Making a new version tag for each new commit, so that the application would get the update, would be impractical, so there should be a way to always depend on the latest commit of a branch instead.

    The solution in DUB 0.9.22 and onwards is simply to let the application depend in the latest version tag of the library on a particular branch and register the local working copy of the library (pointing to the branch) using dub add-local or dub add-path. DUB will the automatically invoke git to look for the latest version tag and treat the working copy accordingly. The library would then for example get assigned a version such as 1.2.0+commit.6.g1234567 if the working copy is six commits ahead of 1.2.0. This version is considered the same as 1.2.0, so that dependency specifications such as ~>1.2.0 or ==1.2.0 will work as expected and the working copy is used.

  2. Forcing an externally controlled library on a branch: Sometimes library features or fixes are developed on separate branches without any version tags. If such a library is now referenced by another dependency using a version, but in your main package you want to use the branch, there will be a conflict. There are two ways to resolve this situation, depending on the context.

    The simplest way is to edit the dub.selections.json file of the root package and manually specify a ~branch style dependency there. This version override will only be used when directly building this specific root package. Note that by publicly committing the dub.selections.json file, overrides done this way can be forwarded to external users of the package.

    A second way is to set up a system (or user account) wide override using dub add-override. Using this command, all versions matching a particular version specification can be remapped to a different version that can be picked freely. All dependency specifications of the involved packages will be ignored in this case. This can be useful if you have many packages that need to use the overridden version.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.