Version numbers and dependency specifications are best expressed at the level of the distro (i.e. the atomic unit of installation) rather than at the level of the module.
CPAN authors can emulate distro-level versioning by unifying version numbers across all subcomponents and by treating the removal of a module from a distro as a backwards compatibility break.
Per-module dependency specification
A number of distributions on CPAN provide multiple modules with disparate $VERSION numbers. Take URI 1.60:
URI: 1.60 URI::Escape: 3.31 URI::Heuristic: 4.20 URI::Split: URI::URL: 5.04 ...
The CPAN toolchain supports expressing dependencies at the level of individual modules...
requires: URI::Escape: 3.31
... though it is common to use only the primary module in the distro as a proxy for all the others:
requires: URI: 1.60 # may be a proxy for URI::Escape 3.31
Impracticality of consistent per-module dependency specification
In theory, it is safest for downstream users to express all module dependencies individually, because individual dependency specs will continue to resolve properly if a module moves to a different distribution -- for example, if URI::Escape were to be broken out of URI.
However, there are numerous distributions out there which are structured as large collections of small modules, and for which it would be tedious and error-prone to specify each constituent dependency seperately. The existence of such distributions renders strict per-module dependency specification impractical.
Removing a module from a distro breaks backwards compatibility
The only safe assumption for an author to make is that some fraction of the user base has listed the primary module of a distro as a dependency when they really want a different module within the distro. Users in this category are going to get burnt when their desired module gets removed. Therefore, removing a module from a distro must be classified as a compatibility break.
Hidden major version breaks
Say that URI::Escape makes a major version increment, but URI does not:
URI 1.60 ... 1.61 URI::Escape 3.31 ... 4.00
The distribution's version number only moves from 1.60 to 1.61 because is tied to the URI module -- URI::Escape is only along for the ride.
Unfortunately, this minor version increment fails to communicate to downstream users that a potentially backwards-incompatible major version break has occurred.
The alternative: Per-distro dependency specificationAnother approach is to assign a single $VERSION variable to the primary module in the distro, leaving all other modules without versions. This yields improvements in conceptual clarity.
Authors can still do evil things in the context of per-distro packaging, such as breaking back compat within a subcomponent without incrementing the distro's major version number. However, it is more obvious that you're doing something wrong because the distro's version number unambiguously represents everything together.
Removal of a subcomponent is likewise an unambiguous compatibility break -- that is, unless the subcompoment gets broken out into a new distro which gets added to the original distro's dependency chain as a mandatory prerequisite.The downside is that we must assume that some fraction of the user base will attempt to specify per-module dependencies and will not get their desired behavior.
Fortunately, it is possible to emulate distro-level versioning while still supporting downstream users who make per-module dependency specifications:
- Assign a $VERSION number in every provided package. (Admittedly, this is inconvenient and violates DRY; some projects use scripts to help ease the maintenance burden.)
- Ensure that all $VERSION variables within a distro contain exactly the same value. (Some packaging tools provide validation routines.)
- Treat the removal of a module from a distro as a backwards compatibility break.
ReferencesThis post is a spinoff from an off-topic thread on the cpan-testers-discuss list. The recommendations about $VERSION variables have been repeated many times elsewhere.
1 Update: "Suggestions" just sounds a little more humble the morning after. :)