The Special Case of the “dev” Stability Flag: Funtimes with Composer!

By Derek DeRaps,
Man in orchestra conducting musical performance

The Special Case of the “dev” Stability Flag: Funtimes with Composer! (Man in orchestra conducting musical performance)

There are plenty of other posts out there, including some mighty fine documentation, explaining stability flags. What tripped me up recently and what I want to share here is how to get the dev version of a module brought into your project via Composer, the package manager of choice for PHP projects.

Waiver of Liability

(My lawyer said I had to.)

Now, I don’t recommend using dev versions of modules; not only might they be unstable or buggy, they also aren’t covered by Drupal.org’s security advisory policy. But sometimes you just can’t get around it, and for those of us rolling with the new-fangled Composer approach to managing our projects, figuring out the version tagging system can be a real PITA. But before we jump into that, let’s take a quick pre-req course on Semantic Versioning.

Semantic Versioning (SemVer)

The Drupal community didn’t invent SemVer, but we follow it (mostly*) in order to make our projects compatible with Composer. SemVer codifies a set of rules that determine precedence, i.e., which version is newer or more stable (e.g., 2.0 is newer than 1.0, duh!). SemVer also describes the use of “pre-release” version names: alpha, beta, RC (release candidate), etc. SemVer doesn’t actually specify which pre-release version names are allowed (anything goes), but they must follow in alphabetical order; i.e., developers may skip from 1.0-alpha to 1.0-rc but cannot go back to 1.0-beta once the release candidate is announced (read more about the Drupal community’s standards for alpha, beta, and RC releases here). Also note that developers may append numbers to a pre-release version to indicate iterations on it (e.g., 1.0-alpha3). The most important thing to remember is that precedence of pre-release versions is determined by comparing them alphabetically.

Fun fact: in this way, 1.0.0-STABLE is actually less stable than 1.0.0-UNSTABLE, because U > S

Composer stability flags vs. SemVer

Now let’s bring Composer into the mix and confuse things a bit with “stability flags”, which are used in your composer.json to indicate the general level of stability you’d like to target. The allowed values are alpha, beta, RC, and dev. At first glance, stability flags appear the same as pre-release version names, but they are not! Here’s an example list of composer.json version constraints, listed in order of increasing stability:

  • 1.0.x-dev
  • 1.0-alpha
  • 1.0-alpha1
  • 1.0-beta
  • 1.0-rc1
  • 1.0 (1.0-stable)

Can you see what's wrong there? I said before that SemVer compares pre-release version names alphabetically, so what the heck is dev doing up there before (i.e., less stable than) alpha and beta? See, that’s what I mean: If you take one thing away from this post, let it be that the dev stability flag is not a pre-release version name and behaves according to different rules. Let's break it down, shall we?

Getting your hands on the dev release (it’s a branch)

Unlike alpha, beta, and the others, we use dev to target git branches instead of specific git tags. Let's say a maintainer of the Redirect module committed some new code to the development branch that we need on our project, but she hasn’t cut a new (pre-)release tag for it yet. The only way to bring it into our project is by targeting the development branch. In Drupal, branch names always follow the format 7.x-2.x, 8.x-1.x, etc. That is, Drupal core version (i.e., major version) followed by project version (i.e., minor version). The branch name in Redirect's drupal.org code repository is 7.x-1.x. Strip off the 7.x (because of our funky pseudo-semantic versioning*), and you're left with 1.x. In Composer, to target a branch name, you simply prefix it with dev-, so dev-1.x works just fine and dandy. However, Composer also automatically creates dev releases for any branch with a “version-like” name (1, 1.0, etc.) by appending an arbitrary .x-dev onto the end. E.g., you can reference the 1.0 branch by targeting 1.0.x-dev in your version constraint. And, apparently, it does the very same thing for branches that already come with .x appended, like 1.x. So, 1.x-dev it is!

I tend to follow the latter format because it jives more closely with what you see on drupal.org project pages (7x.-1.x-dev). Remember, however, that this syntax does not work in targeting branches that don't have version-like names (e.g., for the master branch, you must use the version constraint dev-master).

TL;DR—Here’s the confusing bit

Back to stability flags… In Drupal, we are accustomed to the idea that a dev release is the farthest along, most recent, potentially unstable version of a module. So I got confused the other day when trying to install the dev version of Redirect module. “How come 1.*@dev doesn't give me what I want?” In adding the @dev version flag, I was giving Composer permission to override my project's beta minimum stability setting and install a dev version of Redirect, but I wasn't telling it to explicitly install the 1.x-dev version. And if you look back to the list above, you’ll notice that the dev release is the least stable of all the options. And since my composer.json specified prefer-stable: true and there was an RC version of Redirect available—1.0.0-rc3—it preferred that over the less stable dev version.

Pro tip: to list all the available versions of a module, use:

composer info drupal/redirect -a

So! Let's walk through that in more detail. The order of precedence in determining stability goes like this:

  1. dev
  2. alpha
  3. beta**
  4. rc
  5. stable

But in the order of most recent version/most current work, dev moves from the front of the list to the end:

  1. alpha
  2. beta**
  3. rc
  4. stable
  5. dev

Let’s keep going with the Redirect module example. It has 7.x-1.x and 7.x-2.x development branches, and its 7.x releases include:

  • 1.0-alpha1 ← Lowest precedence (oldest code)
  • 1.0-alpha2
  • 1.0-beta1
  • 1.0-beta2
  • 1.0-beta3
  • 1.0-beta4
  • 1.0-rc1
  • 1.0-rc2
  • 1.0-rc3 ← Most stable release
  • 1.x-dev / dev-1.x ← Highest 1.* precedence, least stable 1.* version
  • 2.x-dev / dev-2.x ← Highest precedence overall, dev stability

These releases follow one another in chronological order, from the alphas to the betas to release candidates 1, 2, and 3, and finally the dev releases, pegged to the 7.x-1.x and 7.x-2.x branches, respectively. According to SemVer, alpha1 has the lowest precedence and rc3 the highest. However, Composer’s dev marker sidesteps SemVer altogether and simultaneously represents the least stable of all versions but the highest precedence, containing the most recent code commits.

I hope this write-up helps clarify the use of stability flags in the Composer world in general and the Drupal island (no, we're not off it yet!) in particular. If you have any clarifications to add, please hit me up on d.o and I'll update the post, with proper attribution. Many thanks!


*Because the “major” version (first digit) of any project is always pegged to the version of Drupal core (7, 8, etc.), we still only have two release digits, known as minor and patch, with which to work.

**Thank you, Ben (bdimaggio), for pointing out that ALPHA comes before BETA, as I originally had them switched =P

Bonus material: minimum-stability and prefer-stable

(I don’t even charge extra for this part!)

If minimum-stability is set to beta or higher in your composer.json, you must explicitly whitelist individual projects for which you want to use a dev release by adding the @dev flag to its version constraint. Even then, simply allowing use of the dev release doesn't mean Composer will actually choose it, and this is where prefer-stable comes in. To actually get your hands on a dev version, you have two options:

  1. Exclude the prefer-stable setting from your composer.json (or explicitly set it to false, the default value) and then make sure the dev release is allowed with an @dev per-project whitelist.

    DON’T DO THIS! All projects get dev release by default:

    "prefer-stable": false,
    "minimum-stability": "dev"

    Specific projects get dev release by setting the per-project stability flag:

    "prefer-stable": false,
    "minimum-stability": "stable",
    "require": {
        "somevendor/someproject": "[email protected]",
        "anothervendor/anotherproject": "[email protected]"
    }
  2. When prefer-stable is true, your only option is to explicitly target a branch, using the dev prefix/suffix discussed earlier. This, in addition to one of the steps outlined in option (A) to grant permission for dev releases.

    Specific projects get dev release by targeting the development branch:

    "prefer-stable": true,
    "minimum-stability": "dev",
    "require": {
        "somevendor/someproject": "1.0.x-dev"
    }

I prefer option (B), setting minimum-stability to dev and prefer-stable to true. This way, without any extra work on my part, Composer will always grab the stable version if it’s available but will happily grab the alpha, beta, or RC if there is no stable version yet released. This situation is pretty common for me at this point in Drupal 8’s contrib maturity. The setup also has the possible benefit of making it a little harder for developers to accidentally target a dev release: the special dev branch name must be specified rather than just appending @dev to a version number.

Derek DeRaps

Senior Architect

The Masters of the Internet hath decreed that a champion web weaver must lead the progression of technology and science, and Kalamuna has followed suit by offering two-wheeling Derek DeRaps to the fast-paced arena of ever-changing requirements and challenges. With a fierce gaze and unrelenting laughter, he smashes our client’s worries and sorrows away in heroic fashion.