Installing sets of modules

In order to quickly install a set of Perl modules for a given task, it is nice to have some kind of meta-package serving no other purpose than pulling in a set of other more specific packages. Traditional ways of doing this with CPAN can be found in the Bundle:: and Task:: namespaces. Recent developments, however, seem to have put meta-package maintainers in a predicament. Technologies are shifting once more. In this article, I explain how I rescued my meta-packages with minimal changes.

Bundles rely on CPAN client's magic to get static lists of modules resolved into individual installation jobs. Tasks are empty modules with all kinds of dependencies that will be handled by regular dependency-resolving mechanisms. This is a more flexible approach as there is a configuration step that can check everything about its environment and possibly adapt to it.

One downside of Task:: based meta-packages is that they need to get updated occasionally in order to upgrade existing modules to their latest versions, as bundles do. Of course, the maintainer can check everything before doing so, and intentionally stay away from new versions he or she doesn't like.

So I am sort of used to look after my Task::Whatever packages from time to time and was rather disappointed when they suddenly broke in strange ways. It turns out that the packaging technology they relied on, namely Module::Install, somehow has fallen into disgrace lately.

Module::Install had been designed to simplify writing installers without sacrificing the ability to cope with difficult conditions, such as outdated pre-installed software. However, it now seems outdated itself.

For one thing, it sometimes relies on an insecure @INC path (containing '.') which is for good reason no longer recommended. To give Module::Install based installers a chance to succeed with a clean @INC path, I prepend these two lines:

use FindBin;
use lib $FindBin::Bin;

before this line:

use inc::Module::Install;

That way, the installer modules included in my package will be found at runtime, now with a path relative to the location of the Makefile.PL script rather than the current working directory.

This was not enough to get me a sane package, however. make distdir would no longer re-generate META.yml and META.json files for me, for example. Sneaking into the source code, I found that ExtUtils::MakeMaker's WriteMakefile is called with a NO_META argument for no good reason.

Moreover, the Module::Install documentation now discourages its use altogether in favour of authoring tools like Dist::Zilla. What a mess.

Thankfully, I found advice in Neil Bowers' very fine article Specifying dependencies for your CPAN distribution. Neil lines out how different ways of installing can be configured with dependency metadata.

I still hesitated to rewrite my meta-packages from scratch as the Module::Installer way of specifying things had led me to build own maintenance tools that would parse and generate Makefile.PL files. My solution was to migrate the packages from Module::Install to plain ExtUtils::MakeMaker, with a little extra boilerplate to keep requires and recommends lines intact. Basically, I just defined own subroutines with those names at the top of the script, like this:

my %requires      = ();
my %recommends    = ();

sub requires ($$) {
    my ($module, $version) = @_;
    $requires{$module} = $version;
}

sub recommends ($$) {
    my ($module, $version) = @_;
    $recommends{$module} = $version;
}

As a result, my Task::Whatever modules and my private maintenance scripts now continue to work with no changes other than modified Makefile.PL boilerplate code and, of course, removal of Module::Install's inc subtree. An example can be found in Task::Devel::Essentials on CPAN.

What I missed most was the all_from directive, which had to be replaced by some more explicit configuration parameters, but this was not a big deal.

3 Comments

You may find mbtiny to your liking. Like many other authoring tools that have come around post-Module::Install, it relies on simply specifying dependencies in a cpanfile (which allows much the same format as Module::Install that you're used to), and has default behavior similar to all_from. A step up from that would be Minilla, which does a lot more opinionated things, and then Dist::Milla which is almost the same but allows you to leverage the full Dist::Zilla stack, a bit of a gateway drug if you will.

Do you know the problem of Module::Install?

The biggest problem is that its main feature, that a specific version of it is bundled with the distribution to install it, means that it is impossible to update all of the bundled versions to cope with new installation requirements, such as the removal of . from @INC. Newer tools solve this by keeping the complex author tools only on the authoring side, and generating installation files that use simpler installers to ship to CPAN, which the user can keep up to date in their own Perl install.

Leave a comment

About martin

user-pic Blogging about Mathematics, Programming Languages, Open-Source, Privacy, Security, Board Games, Bicycles, Classical Music, and more.