diff options
| author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2014-08-29 04:48:18 +0000 |
|---|---|---|
| committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2014-08-29 04:48:18 +0000 |
| commit | 6b1d736955543538c54f1d8033ce3bdcb175da91 (patch) | |
| tree | f7f7e23929d493647c0e2f9b1a556d3101194538 /lib | |
| download | Module-CPANfile-tarball-master.tar.gz | |
Module-CPANfile-1.1000HEADModule-CPANfile-1.1000master
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/Module/CPANfile.pm | 323 | ||||
| -rw-r--r-- | lib/Module/CPANfile/Environment.pm | 173 | ||||
| -rw-r--r-- | lib/Module/CPANfile/Prereq.pm | 21 | ||||
| -rw-r--r-- | lib/Module/CPANfile/Prereqs.pm | 117 | ||||
| -rw-r--r-- | lib/Module/CPANfile/Requirement.pm | 25 | ||||
| -rw-r--r-- | lib/cpanfile-faq.pod | 128 | ||||
| -rw-r--r-- | lib/cpanfile.pod | 128 |
7 files changed, 915 insertions, 0 deletions
diff --git a/lib/Module/CPANfile.pm b/lib/Module/CPANfile.pm new file mode 100644 index 0000000..dc11a17 --- /dev/null +++ b/lib/Module/CPANfile.pm @@ -0,0 +1,323 @@ +package Module::CPANfile; +use strict; +use warnings; +use Cwd; +use Carp (); +use Module::CPANfile::Environment; +use Module::CPANfile::Requirement; + +our $VERSION = '1.1000'; + +sub new { + my($class, $file) = @_; + bless {}, $class; +} + +sub load { + my($proto, $file) = @_; + + my $self = ref $proto ? $proto : $proto->new; + $self->parse($file || Cwd::abs_path('cpanfile')); + $self; +} + +sub save { + my($self, $path) = @_; + + open my $out, ">", $path or die "$path: $!"; + print {$out} $self->to_string; +} + +sub parse { + my($self, $file) = @_; + + my $code = do { + open my $fh, "<", $file or die "$file: $!"; + join '', <$fh>; + }; + + my $env = Module::CPANfile::Environment->new($file); + $env->parse($code) or die $@; + + $self->{_mirrors} = $env->mirrors; + $self->{_prereqs} = $env->prereqs; +} + +sub from_prereqs { + my($proto, $prereqs) = @_; + + my $self = $proto->new; + $self->{_prereqs} = Module::CPANfile::Prereqs->from_cpan_meta($prereqs); + + $self; +} + +sub mirrors { + my $self = shift; + $self->{_mirrors} || []; +} + +sub features { + my $self = shift; + map $self->feature($_), $self->{_prereqs}->identifiers; +} + +sub feature { + my($self, $identifier) = @_; + $self->{_prereqs}->feature($identifier); +} + +sub prereq { shift->prereqs } + +sub prereqs { + my $self = shift; + $self->{_prereqs}->as_cpan_meta; +} + +sub merged_requirements { + my $self = shift; + $self->{_prereqs}->merged_requirements; +} + +sub effective_prereqs { + my($self, $features) = @_; + $self->prereqs_with(@{$features || []}); +} + +sub prereqs_with { + my($self, @feature_identifiers) = @_; + + my $prereqs = $self->prereqs; + my @others = map { $self->feature($_)->prereqs } @feature_identifiers; + + $prereqs->with_merged_prereqs(\@others); +} + +sub prereq_specs { + my $self = shift; + $self->prereqs->as_string_hash; +} + +sub prereq_for_module { + my($self, $module) = @_; + $self->{_prereqs}->find($module); +} + +sub options_for_module { + my($self, $module) = @_; + my $prereq = $self->prereq_for_module($module) or return; + $prereq->requirement->options; +} + +sub merge_meta { + my($self, $file, $version) = @_; + + require CPAN::Meta; + + $version ||= $file =~ /\.yml$/ ? '1.4' : '2'; + + my $prereq = $self->prereqs; + + my $meta = CPAN::Meta->load_file($file); + my $prereqs_hash = $prereq->with_merged_prereqs($meta->effective_prereqs)->as_string_hash; + my $struct = { %{$meta->as_struct}, prereqs => $prereqs_hash }; + + CPAN::Meta->new($struct)->save($file, { version => $version }); +} + +sub _dump { + my $str = shift; + require Data::Dumper; + chomp(my $value = Data::Dumper->new([$str])->Terse(1)->Dump); + $value; +} + +sub to_string { + my($self, $include_empty) = @_; + + my $mirrors = $self->mirrors; + my $prereqs = $self->prereq_specs; + + my $code = ''; + $code .= $self->_dump_mirrors($mirrors); + $code .= $self->_dump_prereqs($prereqs, $include_empty); + + for my $feature ($self->features) { + $code .= sprintf "feature %s, %s => sub {\n", _dump($feature->{identifier}), _dump($feature->{description}); + $code .= $self->_dump_prereqs($feature->{spec}, $include_empty, 4); + $code .= "}\n\n"; + } + + $code =~ s/\n+$/\n/s; + $code; +} + +sub _dump_mirrors { + my($self, $mirrors) = @_; + + my $code = ""; + + for my $url (@$mirrors) { + $code .= "mirror '$url';\n"; + } + + $code =~ s/\n+$/\n/s; + $code; +} + +sub _dump_prereqs { + my($self, $prereqs, $include_empty, $base_indent) = @_; + + my $code = ''; + for my $phase (qw(runtime configure build test develop)) { + my $indent = $phase eq 'runtime' ? '' : ' '; + $indent = (' ' x ($base_indent || 0)) . $indent; + + my($phase_code, $requirements); + $phase_code .= "on $phase => sub {\n" unless $phase eq 'runtime'; + + for my $type (qw(requires recommends suggests conflicts)) { + for my $mod (sort keys %{$prereqs->{$phase}{$type}}) { + my $ver = $prereqs->{$phase}{$type}{$mod}; + $phase_code .= $ver eq '0' + ? "${indent}$type '$mod';\n" + : "${indent}$type '$mod', '$ver';\n"; + $requirements++; + } + } + + $phase_code .= "\n" unless $requirements; + $phase_code .= "};\n" unless $phase eq 'runtime'; + + $code .= $phase_code . "\n" if $requirements or $include_empty; + } + + $code =~ s/\n+$/\n/s; + $code; +} + +1; + +__END__ + +=head1 NAME + +Module::CPANfile - Parse cpanfile + +=head1 SYNOPSIS + + use Module::CPANfile; + + my $file = Module::CPANfile->load("cpanfile"); + my $prereqs = $file->prereqs; # CPAN::Meta::Prereqs object + + my @features = $file->features; # CPAN::Meta::Feature objects + my $merged_prereqs = $file->prereqs_with(@identifiers); # CPAN::Meta::Prereqs + + $file->merge_meta('MYMETA.json'); + +=head1 DESCRIPTION + +Module::CPANfile is a tool to handle L<cpanfile> format to load application +specific dependencies, not just for CPAN distributions. + +=head1 METHODS + +=over 4 + +=item load + + $file = Module::CPANfile->load; + $file = Module::CPANfile->load('cpanfile'); + +Load and parse a cpanfile. By default it tries to load C<cpanfile> in +the current directory, unless you pass the path to its argument. + +=item from_prereqs + + $file = Module::CPANfile->from_prereqs({ + runtime => { requires => { DBI => '1.000' } }, + }); + +Creates a new Module::CPANfile object from prereqs hash you can get +via L<CPAN::Meta>'s C<prereqs>, or L<CPAN::Meta::Prereqs>' +C<as_string_hash>. + + # read MYMETA, then feed the prereqs to create Module::CPANfile + my $meta = CPAN::Meta->load_file('MYMETA.json'); + my $file = Module::CPANfile->from_prereqs($meta->prereqs); + + # load cpanfile, then recreate it with round-trip + my $file = Module::CPANfile->load('cpanfile'); + $file = Module::CPANfile->from_prereqs($file->prereq_specs); + # or $file->prereqs->as_string_hash + +=item prereqs + +Returns L<CPAN::Meta::Prereqs> object out of the parsed cpanfile. + +=item prereq_specs + +Returns a hash reference that should be passed to C<< CPAN::Meta::Prereqs->new >>. + +=item features + +Returns a list of features available in the cpanfile as L<CPAN::Meta::Feature>. + +=item prereqs_with(@identifiers), effective_prereqs(\@identifiers) + +Returns L<CPAN::Meta::Prereqs> object, with merged prereqs for +features identified with the C<@identifiers>. + +=item to_string($include_empty) + + $file->to_string; + $file->to_string(1); + +Returns a canonical string (code) representation for cpanfile. Useful +if you want to convert L<CPAN::Meta::Prereqs> to a new cpanfile. + + # read MYMETA's prereqs and print cpanfile representation of it + my $meta = CPAN::Meta->load_file('MYMETA.json'); + my $file = Module::CPANfile->from_prereqs($meta->prereqs); + print $file->to_string; + +By default, it omits the phase where there're no modules +registered. If you pass the argument of a true value, it will print +them as well. + +=item save + + $file->save('cpanfile'); + +Saves the currently loaded prereqs as a new C<cpanfile> by calling +C<to_string>. Beware B<this method will overwrite the existing +cpanfile without any warning or backup>. Taking a backup or giving +warnings to users is a caller's responsibility. + + # Read MYMETA.json and creates a new cpanfile + my $meta = CPAN::Meta->load_file('MYMETA.json'); + my $file = Module::CPANfile->from_prereqs($meta->prereqs); + $file->save('cpanfile'); + +=item merge_meta + + $file->merge_meta('META.yml'); + $file->merge_meta('MYMETA.json', '2.0'); + +Merge the effective prereqs with Meta specification loaded from the +given META file, using CPAN::Meta. You can specify the META spec +version in the second argument, which defaults to 1.4 in case the +given file is YAML, and 2 if it is JSON. + +=back + +=head1 AUTHOR + +Tatsuhiko Miyagawa + +=head1 SEE ALSO + +L<cpanfile>, L<CPAN::Meta>, L<CPAN::Meta::Spec> + +=cut diff --git a/lib/Module/CPANfile/Environment.pm b/lib/Module/CPANfile/Environment.pm new file mode 100644 index 0000000..e1c0ea1 --- /dev/null +++ b/lib/Module/CPANfile/Environment.pm @@ -0,0 +1,173 @@ +package Module::CPANfile::Environment; +use strict; +use warnings; +use Module::CPANfile::Prereqs; +use Carp (); + +my @bindings = qw( + on requires recommends suggests conflicts + feature + osname + mirror + configure_requires build_requires test_requires author_requires +); + +my $file_id = 1; + +sub new { + my($class, $file) = @_; + bless { + file => $file, + phase => 'runtime', # default phase + feature => undef, + features => {}, + prereqs => Module::CPANfile::Prereqs->new, + mirrors => [], + }, $class; +} + +sub bind { + my $self = shift; + my $pkg = caller; + + for my $binding (@bindings) { + no strict 'refs'; + *{"$pkg\::$binding"} = sub { $self->$binding(@_) }; + } +} + +sub parse { + my($self, $code) = @_; + + my $err; + { + local $@; + $file_id++; + $self->_evaluate(<<EVAL); +package Module::CPANfile::Sandbox$file_id; +no warnings; +BEGIN { \$_environment->bind } + +# line 1 "$self->{file}" +$code; +EVAL + $err = $@; + } + + if ($err) { die "Parsing $self->{file} failed: $err" }; + + return 1; +} + +sub _evaluate { + my $_environment = $_[0]; + eval $_[1]; +} + +sub prereqs { $_[0]->{prereqs} } + +sub mirrors { $_[0]->{mirrors} } + +# DSL goes from here + +sub on { + my($self, $phase, $code) = @_; + local $self->{phase} = $phase; + $code->(); +} + +sub feature { + my($self, $identifier, $description, $code) = @_; + + # shortcut: feature identifier => sub { ... } + if (@_ == 3 && ref($description) eq 'CODE') { + $code = $description; + $description = $identifier; + } + + unless (ref $description eq '' && ref $code eq 'CODE') { + Carp::croak("Usage: feature 'identifier', 'Description' => sub { ... }"); + } + + local $self->{feature} = $identifier; + $self->prereqs->add_feature($identifier, $description); + + $code->(); +} + +sub osname { die "TODO" } + +sub mirror { + my($self, $url) = @_; + push @{$self->{mirrors}}, $url; +} + +sub requirement_for { + my($self, $module, @args) = @_; + + my $requirement = 0; + $requirement = shift @args if @args % 2; + + return Module::CPANfile::Requirement->new( + name => $module, + version => $requirement, + @args, + ); +} + +sub requires { + my $self = shift; + $self->add_prereq(requires => @_); +} + +sub recommends { + my $self = shift; + $self->add_prereq(recommends => @_); +} + +sub suggests { + my $self = shift; + $self->add_prereq(suggests => @_); +} + +sub conflicts { + my $self = shift; + $self->add_prereq(conflicts => @_); +} + +sub add_prereq { + my($self, $type, $module, @args) = @_; + + $self->prereqs->add_prereq( + feature => $self->{feature}, + phase => $self->{phase}, + type => $type, + module => $module, + requirement => $self->requirement_for($module, @args), + ); +} + +# Module::Install compatible shortcuts + +sub configure_requires { + my($self, @args) = @_; + $self->on(configure => sub { $self->requires(@args) }); +} + +sub build_requires { + my($self, @args) = @_; + $self->on(build => sub { $self->requires(@args) }); +} + +sub test_requires { + my($self, @args) = @_; + $self->on(test => sub { $self->requires(@args) }); +} + +sub author_requires { + my($self, @args) = @_; + $self->on(develop => sub { $self->requires(@args) }); +} + +1; + diff --git a/lib/Module/CPANfile/Prereq.pm b/lib/Module/CPANfile/Prereq.pm new file mode 100644 index 0000000..cf675f1 --- /dev/null +++ b/lib/Module/CPANfile/Prereq.pm @@ -0,0 +1,21 @@ +package Module::CPANfile::Prereq; +use strict; + +sub new { + my($class, %options) = @_; + bless \%options, $class; +} + +sub feature { $_[0]->{feature} } +sub phase { $_[0]->{phase} } +sub type { $_[0]->{type} } +sub module { $_[0]->{module} } +sub requirement { $_[0]->{requirement} } + +sub match_feature { + my($self, $identifier) = @_; + no warnings 'uninitialized'; + $self->feature eq $identifier; +} + +1; diff --git a/lib/Module/CPANfile/Prereqs.pm b/lib/Module/CPANfile/Prereqs.pm new file mode 100644 index 0000000..c3126ea --- /dev/null +++ b/lib/Module/CPANfile/Prereqs.pm @@ -0,0 +1,117 @@ +package Module::CPANfile::Prereqs; +use strict; +use Carp (); +use CPAN::Meta::Feature; +use Module::CPANfile::Prereq; + +sub from_cpan_meta { + my($class, $prereqs) = @_; + + my $self = $class->new; + + for my $phase (keys %$prereqs) { + for my $type (keys %{ $prereqs->{$phase} }) { + while (my($module, $requirement) = each %{ $prereqs->{$phase}{$type} }) { + $self->add_prereq( + phase => $phase, + type => $type, + module => $module, + requirement => Module::CPANfile::Requirement->new(name => $module, version => $requirement), + ); + } + } + } + + $self; +} + +sub new { + my $class = shift; + bless { + prereqs => [], + features => {}, + }, $class; +} + +sub add_feature { + my($self, $identifier, $description) = @_; + $self->{features}{$identifier} = { description => $description }; +} + +sub add_prereq { + my($self, %args) = @_; + $self->add( Module::CPANfile::Prereq->new(%args) ); +} + +sub add { + my($self, $prereq) = @_; + push @{$self->{prereqs}}, $prereq; +} + +sub as_cpan_meta { + my $self = shift; + $self->{cpanmeta} ||= $self->build_cpan_meta; +} + +sub build_cpan_meta { + my($self, $identifier) = @_; + + my $prereq_spec = {}; + $self->prereq_each($identifier, sub { + my $prereq = shift; + $prereq_spec->{$prereq->phase}{$prereq->type}{$prereq->module} = $prereq->requirement->version; + }); + + CPAN::Meta::Prereqs->new($prereq_spec); +} + +sub prereq_each { + my($self, $identifier, $code) = @_; + + for my $prereq (@{$self->{prereqs}}) { + next unless $prereq->match_feature($identifier); + $code->($prereq); + } +} + +sub merged_requirements { + my $self = shift; + + my $reqs = CPAN::Meta::Requirements->new; + for my $prereq (@{$self->{prereqs}}) { + $reqs->add_string_requirement($prereq->module, $prereq->requirement->version); + } + + $reqs; +} + +sub find { + my($self, $module) = @_; + + for my $prereq (@{$self->{prereqs}}) { + return $prereq if $prereq->module eq $module; + } + + return; +} + +sub identifiers { + my $self = shift; + keys %{$self->{features}}; +} + +sub feature { + my($self, $identifier) = @_; + + my $data = $self->{features}{$identifier} + or Carp::croak("Unknown feature '$identifier'"); + + my $prereqs = $self->build_cpan_meta($identifier); + + CPAN::Meta::Feature->new($identifier, { + description => $data->{description}, + prereqs => $prereqs->as_string_hash, + }); +} + +1; diff --git a/lib/Module/CPANfile/Requirement.pm b/lib/Module/CPANfile/Requirement.pm new file mode 100644 index 0000000..01c6358 --- /dev/null +++ b/lib/Module/CPANfile/Requirement.pm @@ -0,0 +1,25 @@ +package Module::CPANfile::Requirement; +use strict; + +sub new { + my ($class, %args) = @_; + + $args{version} ||= 0; + + bless +{ + name => delete $args{name}, + version => delete $args{version}, + options => \%args, + }, $class; +} + +sub name { $_[0]->{name} } +sub version { $_[0]->{version} } + +sub options { $_[0]->{options} } + +sub has_options { + keys %{$_[0]->{options}} > 0; +} + +1; diff --git a/lib/cpanfile-faq.pod b/lib/cpanfile-faq.pod new file mode 100644 index 0000000..56548d8 --- /dev/null +++ b/lib/cpanfile-faq.pod @@ -0,0 +1,128 @@ +=head1 NAME + +cpanfile-faq - cpanfile FAQ + +=head1 QUESTIONS + +=head2 Does cpanfile replace Makefile.PL/Build.PL or META.yml/json? + +No, it doesn't. C<cpanfile> is a simpler way to declare CPAN +dependencies, mainly for I<your application> rather than CPAN +distributions. + +However, while CPAN distributions do not need to B<switch> to +C<cpanfile>, you can certainly I<manage> the dependencies in +C<cpanfile>, then export them into C<META.json> files when shipping to +CPAN, using tools such as L<Dist::Milla> or L<Module::Install::CPANfile> + +=head2 Why do we need yet another format? + +Here are some of the reasons that motivates the new L<cpanfile> +format. + +=over 4 + +=item Not everything is a CPAN distribution + +First of all, it is annoying to write (a dummy) C<Makefile.PL> when +what you develop is not a CPAN distribution, just so that installation +like C<cpanm --installdeps .> would work. + +It gets more painful when you develop a web application that you want +to deploy on a different environment using version control system +(such as PaaS/cloud infrastructure), because it requires you to often +commit the META file or C<inc/> directory (or even worse, both) to a +repository. + +Many web application frameworks generate a boiler-plate C<Makefile.PL> +for dependency declaration and to let you install dependencies with +C<< cpanm --installdeps . >>, but that doesn't always mean they are +meant to be installed. Things can be often much simpler if you run the +application from the checkout directory. + +With L<cpanfile>, dependencies can be installed either globally or +locally using supported tools such as L<cpanm> or L<Carton>. Because +C<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. + +=item Familiar DSL syntax + +This is a new file type, but the format and syntax isn't entirely +new. The metadata it can declare is exactly a subset of "Prereqs" in +L<CPAN Meta Spec|CPAN::Meta::Spec>. + +The syntax borrows a lot from L<Module::Install>. Module::Install is a +great way to easily declare module metadata such as name, author and +dependencies. L<cpanfile> format is simply to extract the dependencies +into a separate file, which means most of the developers are familiar +with the syntax. + +=item Complete CPAN Meta Spec v2 support + +C<cpanfile> basically allows you to declare L<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. + +=back + +=head2 How can I start using C<cpanfile>? + +First of all, most distributions on CPAN are not required to update to +this format. + +If your application currently uses C<Makefile.PL> etc. for dependency +declaration because of the current toolchain implementation (e.g. C<< +cpanm --installdeps . >>), you can upgrade to C<cpanfile> while +keeping the build file based installation working for the backward +compatibility. + +If you are an author of CPAN module and want to manage CPAN module +prerequisites using C<cpanfile> you can use one of the following +tools: + +=over 4 + +=item Dist::Milla + +L<Dist::Milla> is a profile for L<Dist::Zilla> that has a C<cpanfile> +support to declare dependencies for your module. + +=item Dist::Zilla + +L<Dist::Zilla::Plugin::Prereqs::FromCPANfile> provides a way to merge +dependencies declared in C<cpanfile> into META files as well as build +files. You can combine them using other prerequisite scanners like +C<AutoPrereqs>. + +=item Minilla + +L<Minilla> is a yet another authoring tool that supports C<cpanfile> +as a way to describe dependencies for your CPAN module. + +=item Module::Install + +L<Module::Install::CPANfile> provides a C<cpanfile> DSL that reads +C<cpanfile> to merge prerequisites when dumping C<MYMETA> files upon +installation. + +=item Module::Build + +L<Module::Build::Pluggable::CPANfile> merges C<cpanfile> dependencies +from C<Build.PL> when dumping out MYMETA information. + +However you're recommended to switch to an authoring system that emits +C<Build.PL> with parsed CPANfile information, like L<Dist::Zilla> +mentioned above. + +=item ExtUtils::MakeMaker + +L<ExtUtils::MakeMaker::CPANfile> merges C<cpanfile> prerequisites +when dumping C<MYMETA> files upon installation. + +However you're recommended to switch to an authoring system that emits +C<Makefile.PL> with parsed CPANfile information, like L<Dist::Zilla> +mentioned above. + +=back diff --git a/lib/cpanfile.pod b/lib/cpanfile.pod new file mode 100644 index 0000000..a554dd6 --- /dev/null +++ b/lib/cpanfile.pod @@ -0,0 +1,128 @@ +=head1 NAME + +cpanfile - A format for describing CPAN dependencies for Perl applications + +=head1 SYNOPSIS + + requires 'Plack', '1.0'; # 1.0 or newer + requires 'JSON', '>= 2.00, < 2.80'; + + recommends 'JSON::XS', '2.0'; + conflicts 'JSON', '< 1.0'; + + on 'test' => sub { + requires 'Test::More', '>= 0.96, < 2.0'; + recommends 'Test::TCP', '1.12'; + }; + + on 'develop' => sub { + recommends 'Devel::NYTProf'; + }; + + feature 'sqlite', 'SQLite support' => sub { + recommends 'DBD::SQLite'; + }; + +=head1 VERSION + +This document describes cpanfile format version 1.0. + +=head1 DESCRIPTION + +C<cpanfile> describes CPAN dependencies required to execute associated +Perl code. + +=head1 SYNTAX + +=over 4 + +=item requires, recommends, suggests, conflicts + + requires $module, $version_requirement; + +Describes the requirement for a module. See L<CPAN::Meta::Spec> for +the meanings of each requirement type. + +When version requirement is omitted, it is assumed that C<0> is +passed, meaning any version of the module would satisfy the +requirement. + +Version requirement can either be a version number or a string that +satisfies L<CPAN::Meta::Spec/Version Ranges>, such as C<< >= 1.0, != +1.1 >>. + +Note that, per L<CPAN::Meta::Spec>, when a plain version number is +given, it means the version I<or newer> is required. If you want a +specific version for a module, use the specific range syntax, i.e. +C< == 2.1 >. + +=item on + + on $phase => sub { ... }; + +Describe requirements for a specific phase. Available phases are +C<configure>, C<build>, C<test>, C<runtime> and C<develop>. + +=item feature + + feature $identifier, $description => sub { ... }; + +Group requirements with features. Description can be omitted, when it +is assumed to be the same as identifier. See +L<CPAN::Meta::Spec/optional_features> for more details. + +=item configure_requires, build_requires, test_requires, author_requires + + configure_requires $module, $version; + # on 'configure' => sub { requires $module, $version } + + build_requires $module, $version; + # on 'build' => sub { requires $module, $version }; + + test_requires $module, $version; + # on 'test' => sub { requires $module, $version }; + + author_requires $module, $version; + # on 'develop' => sub { requires $module, $version }; + +Shortcut for C<requires> in specific phase. This is mainly provided +for compatibilities with L<Module::Install> DSL. + +=back + +=head1 USAGE + +C<cpanfile> is a format to describe dependencies. How to use this file +is dependent on the tools reading/writing it. + +Usually, you're expected to place the C<cpanfile> in the root of the +directory containing the associated code. + +Tools supporting C<cpanfile> format (e.g. L<cpanm> and L<carton>) will +automatically detect the file and install dependencies for the code to +run. + +There are also tools to support converting cpanfile to CPAN toolchain +compatible formats, such as L<Module::CPANfile>, +L<Dist::Zilla::Plugin::Prereqs::FromCPANfile>, +L<Module::Install::CPANfile>, so that C<cpanfile> can be used to +describe dependencies for a CPAN distribution as well. + +=head1 AUTHOR + +Tatsuhiko Miyagawa + +=head1 ACKNOWLEDGEMENTS + +The format (DSL syntax) is inspired by L<Module::Install> and +L<Module::Build::Functions>. + +C<cpanfile> specification (this document) is based on Ruby's +L<Gemfile|http://bundler.io/v1.3/man/gemfile.5.html> specification. + +=head1 SEE ALSO + +L<CPAN::Meta::Spec> L<Module::Install> L<Carton> + +=cut + |
