The Special Case of the “dev” Stability Flag: Funtimes with Composer!
Share
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 than1.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:
dev
alpha
beta
**rc
stable
But in the order of most recent version/most current work, dev
moves from the front of the list to the end:
alpha
beta
**rc
stable
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 release1.x-dev
/dev-1.x
← Highest 1.* precedence, least stable 1.* version2.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:
-
Exclude the
prefer-stable
setting from yourcomposer.json
(or explicitly set it tofalse
, the default value) and then make sure thedev
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"
dev
release by setting the per-project stability flag:"prefer-stable": false, "minimum-stability": "stable", "require": { "somevendor/someproject": "~1.0@beta", "anothervendor/anotherproject": "~1.0@dev" }
-
When
prefer-stable
istrue
, your only option is to explicitly target a branch, using thedev
prefix/suffix discussed earlier. This, in addition to one of the steps outlined in option (A) to grant permission fordev
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.