Specifying dependencies for your CPAN distribution
In this article I'm going to show you how to specify dependencies for your CPAN distributions: the other Perl and CPAN modules that your distribution relies on. This is the fourth article in a series. The first article gave a general introduction to distribution metadata. The second article introduced the five phases for which dependencies, or prerequisites, can be specified. The third article presented the types, or relationships, that can be specified for each dependency.
This article is brought to you by cPanel, Inc., a Gold sponsor for the Perl Toolchain Summit. cPanel are a well-known user and supporter of Perl, and we're very grateful for their support. More about cPanel at the end of this article.
Background
This is a summary of the information covered in the second and third articles of this series, to remind you, and so this article can somewhat stand on its own.
There are five phases for which dependencies can be specified: configure, build, runtime, test, and develop.
There are four types of dependencies: requires, recommends, suggests, and conflicts. These are also known as dependency relationships.
Each module that your distribution uses can be specified as a prerequisite, or dependency, which has both a phase and a type. The META.json file that is included with modern CPAN releases has a "requires" hash structure, that details all of the prereqs. For example, if a distribution uses no modules other that ExtUtils::MakeMaker (in the Makefile.PL), then its prereqs might look like this:
"prereqs" : {
"configure" : {
"requires" : {
"ExtUtils::MakeMaker" : "6.3"
}
},
},
In the earliest days of Perl 5, you could only specify runtime requires dependencies: these are the modules that must be present, otherwise your distribution isn't usable.
Specifying dependencies with your build tool
How you specify your dependencies depends on the build and release tool that you're using. We'll cover the main ones below. If I've missed your favourite one, feel free to email me the details for it, and I'll update this article.
ExtUtils::MakeMaker
Here's the skeleton for a Makefile.PL based on ExtUtils::MakeMaker that specifies required dependencies for four of the five phases. These are the easiest to specify. I've missed out the other keys you would normally see.
use ExtUtils::MakeMaker 6.64;
WriteMakefile(
'NAME' => 'Foo::Bar',
CONFIGURE_REQUIRES => {
'ExtUtils::MakeMaker' => 6.64,
},
BUILD_REQUIRES => {
'Template' => 0,
},
PREREQ_PM => {
'Exporter' => 0,
'strict' => 0,
'warnings' => 0,
'vars' => 0,
},
TEST_REQUIRES => {
'Test::More' => 0.88,
'Test::Deep' => 0,
},
);
Notice that this requires version 6.64 of ExtUtils::MakeMaker. The CONFIGURE_REQUIRES key was added in 6.52, BUILD_REQUIRES was added in 6.5503, and TEST_REQUIRES was added in 6.64. Specifying a minimum version of 6.64 means that someone installing your distribution must have 6.64 (released in December 2012) or later.
If you don't want to require a specific version of ExtUtils::MakeMaker (EUMM), then your Makefile.PL can dynamically decide which key to put dependencies in, falling back on PREREQ_PM if running under an old version of EUMM. Have a look at the Makefile.PL for JSON-Typist, for example (this is generated by Dist::Zilla).
If you want to specify dependency relationships other than requires (i.e. "recommends", "suggests", or "conflicts"), or want to specify develop phase dependencies, then you have to use the META_MERGE feature of EUMM. Let's say your distribution has a requires runtime dependency on Text::CSV, but you want to recommend Text::CSV_XS for better performance. And your tests also have an optional dependency on CPAN::Meta. Here's how you'd specify the optional dependencies as recommendations:
use ExtUtils::MakeMaker 6.64;
WriteMakefile(
# other stuff
META_MERGE => {
"meta-spec" => { version => 2 },
prereqs => {
test => {
recommends => {
'CPAN::Meta' => '2.120900',
},
},
runtime => {
recommends => {
'Text::CSV_XS' => 0,
},
},
},
},
);
As of version 7.12, EUMM also lets you specify version ranges for dependencies, rather than just giving a minimum version number.
Module::Build
Here's the skeleton for a Build.PL file based on Module::Build, which you'll see isn't that different from the EUMM skeleton:
use Module::Build 0.4004;
my $builder = Module::Build->new(
module_name => 'Foo::Bar',
configure_requires => {
'Module::Build' => '0.3601',
},
build_requires => {
'Template' => '>= 2.0, != 2.12',
},
requires => {
'Exporter' => 0,
'strict' => 0,
'warnings' => 0,
'vars' => 0,
'perl' => '5.010',
},
test_requires => {
'Test::More' => 0.88,
'Test::Deep' => 0,
},
);
$builder->create_build_script();
The "requires" key should really be runtime_requires, but for historical reasons it's just "requires". You can also give "conflicts" and "recommends" keys, which are treated for the runtime phase. Notice that you can give simple versions, or expressions which specify ranges and possibly a specific version to avoid. Also note that version 0.4004 of Module::Build is required, as this is the version that added support for test_requires.
Similar to the approach with EUMM, if you want to specify other phases and types for your dependencies, you have to use the meta_merge key (support for which was added in 0.28):
use Module::Build 0.38;
my $builder = Module::Build->new(
module_name => 'Foo::Bar',
# ...
meta_merge => {
"meta-spec" => { version => 2 },
prereqs => {
test => {
recommends => {
'CPAN::Meta' => '2.120900',
},
},
develop => {
requires => {
'Test::Pod::Coverage' => 0,
},
recommends => {
'Perl::Critic' => 0,
},
},
},
},
);
$builder->create_build_script();
When you build a release of your distribution, with ./Build dist, it will include a META.json that includes these richer dependencies. I set a minimum version of 0.38 for Module::Build, as that was the version that introduced support for version 2 CPAN Meta Spec.
App::ModuleBuildTiny
This is the authoring tool for Module::Build::Tiny (MBT), which unlike Module::Build is an installation tool and not an authoring tool. App::ModuleBuildTiny provides the functionality that drives the mbtiny command-line tool.
There are two ways you can specify your dependencies for MBT. Like Minilla, you can use a cpanfile, which is described in a separate section below.
Or you can have a file metamerge.json (or .yml) in the top directory of your distribution. When you run mbtiny, this will be merged into your distribution's metadata. Have a look at the metamerge.json for CPAN-Testers-TailLog. Note that this file is in the github repo for the distribution, but doesn't have to be released with it.
Dist::Zilla
There are several ways you can specify your prereqs with Dist::Zilla (DZ). The simplest is with the Prereqs plugin:
[Prereqs]
strict = 0
Exporter = 0
This defaults to specifying "runtime requires" dependencies. You can also specify the phase and relationship for dependencies:
[Prereqs]
-phase = configure
-relationship = requires
ExtUtils::MakeMaker = 0
The trouble with this approach though, is that you can't specify multiple phases and/or relationships, and the [Prereqs] section can only appear once. So you get round this by "renaming" the Prereqs plugin each time you use it. So you could follow the above section with:
[Prereqs / RuntimeRequires]
-phase = runtime
-relationship = requires
Cwd = 0
Exporter = 0
If you use the right name for the plugin, it also serves to specify the phase and relationship. So the previous two sections can be shortened to:
[Prereqs / ConfigureRequires]
ExtUtils::MakeMaker = 0
[Prereqs / RuntimeRequires]
Cwd = 0
Exporter = 0
More commonly, though, you'll see people using the AutoPrereqs plugin. This scans your distribution for modules being used, and automatically builds the prereqs, differentiating between test dependencies and runtime dependencies. They're all assumed to be required dependencies, but you can change that by telling the AutoPrereqs plugin to skip specific modules, and then use the above technique to specify the phase and relationship:
[AutoPrereqs]
skip = Text::CSV_XS
[Prereqs / RuntimeRecommends]
Text::CSV_XS = 0
In addition to specifying a particular version, you can also specify ranges
There are a lot more ways you can specify / identify your distribution's dependencies, but they're beyond the scope of this article.
Minilla
Minilla doesn't handle prerequisites itself, it relies on you having a cpanfile for your distribution, so read on to the next section.
cpanfile
A cpanfile is file included in your distribution that describes the dependencies. It provides a richer format, and can also be used to capture dependencies for Perl things that aren't CPAN distributions.
If your distribution includes a cpanfile, then cpanm will use that if you include the --installdeps command-line option with a directory:
cpanm --installdeps $dir_name
It won't use the cpanfile if you run
cpanm --installdeps Foo::Bar
I had planned to ask Miyagawa-san about this at the summit, but sadly he is unable to attend.
The simplest notation is for specifying runtime dependencies:
requires 'strict';
requires 'Foo::Bar', '1.15';
recommends 'Foo::Bar::XS', '>= 1.01, < 2.00';
If you don't specify a version, it's set to 0, which means "any version", as with specifying CPAN dependencies in general. If you just give a version number that means "this version or later", but you can also specify the range explicitly, as done for the final example above.
If you want a phase other than runtime, you surround the dependencies with a block that specifies the phase:
on 'test' => sub {
requires 'Foo::More, '0.88';
recommends 'Test::FancyPants', '1.00';
};
on 'develop' => sub {
suggests 'Devel::NYTProf';
};
There are also keywords that combine phase and type:
test_requires 'Foo::More, '0.88';
test_recommends 'Test::FancyPants', '1.00';
author_suggests 'Devel::NYTProf';
Note that it's "author_suggests" rather than "develop_suggests".
Module::Install
If you've a distribution that uses Module::Install and you want to specify richer prerequisites, we recommend that you switch to a different module builder. M::I really isn't recommended these days, for a number of reasons. One of them is that it only supports META.yml file, not META.json, which means it can't support all the features of version 2 of the CPAN meta spec, including all the phases and relationships for dependencies.
That said, here's a sample Makefile.PL based on Module::Install
use inc::Module::Install;
name 'My-Module';
all_from 'lib/My/Module.pm';
requires 'DBI' => '1.00';
test_requires 'Test::More' => '0.88';
recommends 'Test::CSV_XS' => '1.00';
Dependencies listed for recommends are assumed to be runtime, and there's no equivalent for suggests or conflicts.
Conclusion
One of the key goals for your CPAN distributions should be to maximise the chances of them being installed successfully. One of the ways you can do this is by making some of your dependencies optional (using Test::Needs, for example), and recording them as such in the metadata. You could also consider making some tests author or release tests, and then recording their dependencies as "develop requires" dependencies, rather than "test requires".
As more and more distributions specify their dependencies with this sort of fidelity, we'll see ever-better support in the toolchain.
Apparently support for double-dotted decimal version numbers in dependency metadata is patchy. For me that's just another reason to not use them.
Writing this article has identified a number of places where we need to improve our documentation, as a result of how things have evolved over the years.
Thank you to Karen Etheridge, Graham Knop, Leon Timmermans, and David Golden for their help with this article.
About cPanel
cPanel® is a web-based control panel for managing a web hosting account. It provides a simple yet powerful interface for managing email accounts, security, domains, databases, and more. cPanel was originally written (in Perl!) by Nick Koston in 1997. cPanel (the company) now employees over 200 people, and Nick is their CEO. They've been using Perl for nearly 20 years now, and have long been supporters of Perl and its community. You may recognise some of their developers who are CPAN authors: Todd Rinaldo (TODDR), Mark Gardener (MJGARDNER), Xan Hilmisdóttir (XAN), and others. Their CEO, Nick, still develops in Perl today.
Todd is attending the summit, along with Nicolas Rochelemagne, another cPanel employee.
Thank you to cPanel for supporting the Toolchain Summit.
Leave a comment