The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

#!perl
use strict;
use utf8;
use 5.008001;
use Getopt::Long ();
debugf find_perl_files scan_inner_packages scan scan_test_requires load_diff_src
);
my $version;
my $diff;
my $include_empty;
my $scan_test_requires;
my $dir = ".";
my $ignorefile;
my $ignore_regexp;
my @ignore = qw(eg examples share fatlib _build .git blib local .build);
my $add_ignore;
my $p = Getopt::Long::Parser->new(
config => [qw(posix_default no_ignore_case auto_help)]
);
$p->getoptions(
'version!' => \$version,
'diff=s' => \$diff,
'ignore=s' => \$add_ignore,
'ignore-file=s' => \$ignorefile,
'include-empty!' => \$include_empty,
'scan-test-requires' => \$scan_test_requires,
'dir=s' => \$dir,
);
push @ignore, split /,/,$add_ignore if $add_ignore;
if ($ignorefile) {
! -r $ignorefile && die "Unable to read ignore-file '$ignorefile' !" ;
open my $fh, '<', $ignorefile;
chomp(my @lines = <$fh>);
$ignore_regexp = join '|', @lines;
close $fh;
}
if ($version) {
printf "%s %s\n",
File::Basename::basename($0),
$App::scan_prereqs_cpanfile::VERSION;
exit 0;
}
{
my ($runtime_files, $test_files, $configure_files, $develop_files) = find_perl_files($dir, ignore => \@ignore, ignore_regexp => $ignore_regexp);
debugf($develop_files);
my @inner_packages = scan_inner_packages(@$test_files, @$runtime_files, @$configure_files, @$develop_files);
my $meta_prereqs = $diff ? load_diff_src($diff) : +{};
# runtime
my $runtime_prereqs = scan($runtime_files, \@inner_packages, $meta_prereqs, [qw(runtime)], 'runtime', +{});
# test
my $test_prereqs = scan($test_files, \@inner_packages, $meta_prereqs, [qw(test runtime)], 'test', $runtime_prereqs);
# configure
my $configure_prereqs = scan($configure_files, \@inner_packages, $meta_prereqs, [qw(configure runtime)], 'configure', $runtime_prereqs);
# develop
my $develop_prereqs = scan($develop_files, \@inner_packages, $meta_prereqs, [qw(develop test runtime)], 'develop', +{ %{$runtime_prereqs||{}}, %{$test_prereqs||{}}});
if ($scan_test_requires) {
$develop_prereqs = scan_test_requires($dir, $develop_prereqs);
}
print Module::CPANfile->from_prereqs(
{
runtime => {
requires => $runtime_prereqs,
},
configure => {
requires => $configure_prereqs,
},
test => {
requires => $test_prereqs,
},
develop => {
requires => $develop_prereqs,
},
}
)->to_string($include_empty);
}
__END__
=head1 NAME
scan-prereqs-cpanfile - Scan prerequisite modules and generate CPANfile
=head1 SYNOPSIS
% scan-prereqs-cpanfile
--diff=META.json # Generate diff from META.json
--diff=cpanfile # Generate diff from cpanfile
--ignore=extlib
--dir=/foo/bar
--scan-test-requires
=head1 DESCRIPTION
This script scans prerequisite modules from your code, and generate CPANfile.
You can also list missing prerequisite modules.
=head1 SCANNING RULES
=over 4
=item Used modules in `Build.PL` or `Makefile.PL` as 'test' requires
=item Used modules in `t/` as 'test' requires
=item Used modules in `xt/`, `benchmark/` and `author/` as 'develop' requires
=item Used modules in other directories as 'runtime' requires
=back
=head1 OPTIONS
=over 4
=item --diff
--diff=META.json # Generate diff from META.json
--diff=cpanfile # Generate diff from cpanfile
Compare the scanning result with META.json, META.yml or cpanfile.
With this option, scan-prereqs-cpanfile displays missing prerequisite modules only.
=item --ignore
--ignore=tools,extlib
Ignore some directories.
=item --ignore-file
--ignore-file=ignored.regex
Ignore all files and directories matching a regex pattern listed in this file.
=item --include-empty
By default, phases without any prereqs are not dumped, By giving this option, cpanfile will have an empty block such as:
on test => sub {
};
Defaults to false.
=item --scan-test-requires
Scan test files and include the modules specified by L<Test::Requires> as 'develop' requires.
=item --dir
--dir=DIRECTORY
Scan for modules in DIRECTORY (instead of '.')
=back
=head1 AUTHOR
Tokuhiro Matsuno
=head1 SEE ALSO
L<Module::CPANfile>, L<Perl::PrereqScanner::Lite>