Skip to content

Instantly share code, notes, and snippets.

@miyagawa
Created November 1, 2011 11:17
Show Gist options
  • Save miyagawa/1330342 to your computer and use it in GitHub Desktop.
Save miyagawa/1330342 to your computer and use it in GitHub Desktop.

NAME

cpanfile - A format for describing CPAN dependencies for Perl applications

SYNOPSIS

requires 'Catalyst', '5.8000';
requires 'CatalystX::Singleton', '>= 1.1000, < 2.000';

recommends 'JSON::XS', '2.0';
conflicts 'JSON', '< 1.0';

osname 'MSWin32', sub {
    requires 'Win32::File';
};

phase 'test', sub {
    requires 'Test::More', '>= 0.96 < 2.0';
    recommends 'Test::TCP', '1.12';
};

VERSION

v0.9.0

DESCRIPTION

cpanfile describes CPAN dependencies required to execute associated Perl code.

Place the cpanfile in the root of the directory containing the associated code. For instance, in a Catalyst application, place the cpanfile in the same directory as myapp.conf.

Tools supporting cpanfile format (e.g. cpanm and carton) will automatically detect the file and install dependencies for the code to run.

AUTHOR

Tatsuhiko Miyagawa

ACKNOWLEDGES

The format (DSL syntax) is inspired by Module::Install and Module::Build::Functions.

cpanfile specification (this document) is based on Ruby's Gemfile specification.

SEE ALSO

CPAN::Meta::Spec Module::Install Carton

NAME

cpanfile-faq - cpanfile FAQ

QUESTIONS

Does cpanfile replace Makefile.PL or Build.PL?

No, it doesn't. cpanfile is a simpler way to declare CPAN dependencies, mainly to your application rather than CPAN distributions.

In fact, most CPAN distributions do not need to switch to cpanfile unless they absolutely want to take advantage of some of the features (see below). This is considered a new extension for applications and installers.

Why do we need yet another format?

Here are some of the reasons that motivates the new cpanfile format.

Not everything is a CPAN distribution

First of all, it is annoying to write Makefile.PL when what you develop is not a CPAN distirbution.

It gets more painful when you develop a web application that you want to deploy on a different environment (such as cloud infrastructure), because it requires you to often commit the META file to a repository when your build script uses non-core modules (e.g. File::ShareDir) or you have to push inc/ directory to bootstrap the build file.

Many web application frameworks generate a boiler-plate Makefile.PL for dependency declaration and to let you install dependencies with cpanm --installdeps ., but that doesn't always mean they are meant to be installed.

With cpanfile, dependencies can be installed either globally or locally using supported tools such as cpanm or carton. Because cpanfile lists all the dependencies of your entire application and will be updated over time, it makes perfect sense to commit the file to a version control system, and push the file for a deployment.

More control for the dependencies analysis

One of the limitation when I tried to implement a self-contained local::lib library path feature for cpanminus was that the configuration phase runs the build file as a separate perl process, i.e. perl Makefile.PL.

This makes it so hard for the script to not accidentally load any modules installed in the local site_perl directory when determining the dynamic dependencies.

For example, JSON module has the following code (simplified):

if (!eval { require JSON::XS } && has_cc()) {
    $args{PREREQ_PM}{'JSON::XS'} = 2;
}

Similary, Any::Moose has:

if (!eval { require Moose }) {
    $args{PREREQ_PM}{Mouse} = '0.40';
}

Because it uses eval require and the build script runs as a separate perl process, whether you have the modules in question in your site_perl directory affects the dependencies configuration, and the installer (cpanm) has no control over it.

This eventually results in a library path that is not self-contained and gives runtime errors because of missing dependencies.

With cpanfile format, dynamic dependencies based on the list of the modules can be declared like:

# FIXME
unless (has 'Moose') {
    requires 'Mouse', '0.40';
}

and installers can decide whether the module in question is installed in the target library path.

Familiar DSL syntax

Module::Install's DSL is a great way to easily declare module metadata such as name, author and dependencies. cpanfile format is simply to extract the dependencies into a separate file, which means most of the developers are familiar with the syntax, although there's quite a few differences.

Complete CPAN Meta Spec v2 support

cpanfile basically allows you to declare CPAN::Meta::Spec prerequisite specification using an easy Perl DSL syntax. This makes it easy to declare per-phase dependencies and newer version 2 features such as conflicts and version ranges.

How can I start using cpanfile?

First of all, most distributions on CPAN are not required to update to this format.

If your application currently uses Makefile.PL etc. for dependency declaration because of the current toolchain implementation (e.g. cpanm --installdeps .), you can upgrade to cpanfile while keeping the build file based installation working for the backward compatibility.

Module::Install

Use Module::Install::Cpanfile and replace requires, test_requires etc. with cpanfile.

ExtUtils::MakeMaker

XXX

Module::Build

XXX Module::Build::Cpanfile?

Module::Build::Tiny

XXX

Dist::Zilla

Dist::Zilla::Plugin::Cpanfile

@gugod
Copy link

gugod commented Nov 2, 2011

@miyagawa looks very cool. 'conflict' seems a bit difficult to understand at first glance, but OK.

There's a small inconsistency in these two lines, the comma is missing on the second line, but they both are easily understandable:

requires 'CatalystX::Singleton', '>= 1.1000, < 2.000';
requires 'Test::More', '>= 0.96 < 2.0';

Personally I prefer a simple version spec syntax like /\A([><]?=? +)?(.+)\Z/ , and using multiple version spec args to specify a range:

requires 'CatalystX::Singleton', '>= 1.1000', '< 2.000';
requires 'Test::More', '>= 0.96', '< 2.0';

While the range operator can be handy here, for it is also simple to understand (you bet I dislike the "~>" arrow in Gemfile)

requires 'CatalystX::Singleton', '1.1000..2.000';
requires 'Test::More', '0.96..2.0';

Notice that the range operator is inclusive thus the version ranges are different in the two code snippets above.

I'm sure you'll agree that it is a good idea to use this simple line to state "any version of Foo":

requires 'Foo';

But event without all those, it still looks wonderful :)

@miyagawa
Copy link
Author

miyagawa commented Nov 2, 2011

Yes, the Test::More one was a typo - the correct one is to use comma (,).

Actually all of these DSL and version formats will be a thin wrapper for CPAN Meta spec equivalent. http://search.cpan.org/~dagolden/CPAN-Meta-2.112621/lib/CPAN/Meta/Spec.pm#Prereq_Spec and it uses Version::Requirements for the version ranges.
http://search.cpan.org/~dagolden/CPAN-Meta-2.112621/lib/CPAN/Meta/Spec.pm#Version_Ranges

@gugod
Copy link

gugod commented Nov 2, 2011

Nice, I guess I never took a good look a those spec :P. Just read them again, they looks very good to me. I am not going to make a "mine is better" argument because it is really not.....

@miyagawa
Copy link
Author

miyagawa commented Nov 2, 2011

And yes, the simple requires 'Foo' means any version of Foo is good.

I think we can also support build_requires, test_requires, configure_requires etc. as a wrapper for phase 'build', sub { requires ... } for compatibility with Module::Install - that way most Module::Install require bits can be just copied into cpanfile and do 'cpanfile' would just work ;)

@lorn
Copy link

lorn commented Nov 2, 2011

Looks great, you could try to post at http://prepan.org/ too :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment