use strict;
use warnings;
use List::Util qw/none uniq/;
use Module::CoreList;
use Data::Dumper;
use Getopt::Long;
use Pod::Usage;
use CHI;
use WWW::Mechanize::Cached;
use HTTP::Tiny::Mech;
use MetaCPAN::Client;
$Data::Dumper::Indent = 1;
my %opts;
GetOptions(\%opts,
'recursive|r',
'runtime', 'requires', 'recommends', 'suggests',
'non-core', 'no-develop',
'help') or pod2usage;
pod2usage(1) if $opts{'help'};
pod2usage "$0: command is missing" if @ARGV == 0;
my $cmd = $ARGV[0];
pod2usage "$0: unknown command ($cmd)"
if none {$cmd eq $_} 'tree', 'list', 'ignored', 'show', 'prereqs', 'mod2dist';
pod2usage "$0: distribution is missing" if @ARGV == 1 && $cmd ne 'mod2dist';
my $dist = $ARGV[1];
pod2usage "$0: too many arguments" if @ARGV > 2;
my $mcpan = MetaCPAN::Client->new(
ua => HTTP::Tiny::Mech->new(
mechua => WWW::Mechanize::Cached->new(
cache => CHI->new(
driver => 'File',
root_dir => 'metacpan-cache',
),
),
),
);
if ($cmd eq 'show') {
my $dist = $mcpan->release($dist)->data;
print Dumper $dist;
exit;
} elsif ($cmd eq 'prereqs') {
my $prereqs = $mcpan->release($dist)->data->{'metadata'}{'prereqs'};
delete $prereqs->{'develop'} if $opts{'no-develop'};
$prereqs = $prereqs->{'runtime'} if $opts{'runtime'};
sub process_prereqs {
my $prereqs = shift;
return $prereqs unless $opts{'non-core'};
my %r;
for my $k (keys %$prereqs) {
if (ref $prereqs->{$k} eq 'HASH') {
my $r = process_prereqs($prereqs->{$k});
$r{$k} = $r if %$r;
} else {
next if Module::CoreList::is_core($k);
$r{$k} = $prereqs->{$k};
}
}
return \%r;
}
if ($opts{'requires'}) {
$prereqs = $prereqs->{'requires'};
} elsif ($opts{'recommends'}) {
$prereqs = $prereqs->{'recommends'};
} elsif ($opts{'suggests'}) {
$prereqs = $prereqs->{'suggests'};
}
$prereqs = process_prereqs $prereqs;
print Dumper $prereqs;
exit;
} elsif ($cmd eq 'mod2dist') {
if (defined $dist) {
print $mcpan->module($dist)->distribution;
} else {
while (<STDIN>) {
chomp;
print $mcpan->module($_)->distribution, "\n";
}
}
exit;
}
my %seen;
my @mod_deps;
my @ignored;
sub process_release {
my ($release, $l, $module) = @_;
$l ||= 1;
return if $release eq 'perl';
if ($cmd eq 'tree') {
my $indent = ' ' x (($l - 1) * 2);
my $prefix = $seen{$release} ? '// ' : '';
my $postfix = defined($module) ? ' (' . $module . ')' : '';
print $indent, $prefix, $release, $postfix, "\n";
}
return if $seen{$release};
$seen{$release} = 1;
if ($cmd eq 'list') {
push @mod_deps, $module if defined($module);
}
return if $l > 1 && !$opts{'recursive'};
$release = $mcpan->release($release);
my @requires = grep { $_ ne 'perl' }
keys %{$release->metadata->{'prereqs'}{'runtime'}{'requires'}};
foreach my $r (@requires) {
if (Module::CoreList::is_core($r)) {
push @ignored, $r if $cmd eq 'ignored';
next;
}
process_release($mcpan->module($r)->distribution, $l + 1, $r);
}
}
process_release $dist;
if ($cmd eq 'list') {
@mod_deps = sort @mod_deps;
print join("\n", @mod_deps), "\n";
} elsif ($cmd eq 'ignored') {
@ignored = uniq sort @ignored;
print join("\n", @ignored), "\n";
}
__END__
=head1 SYNOPSIS
deps.pl [OPTIONS] tree|list|ignored|show|prereqs DISTRIBUTION
Options:
--recursive (tree, list, ignored)
--runtime (prereqs)
--requires (prereqs)
--recommends (prereqs)
--suggests (prereqs)
--non-core (prereqs)
--no-develop (prereqs)
--help
=cut
How to determine which dependencies can be preinstalled on Alpine Linux? Let's take Carton
for example. It's dependency tree is:
$ perl deps.pl tree -r Carton
Carton
Class-Tiny (Class::Tiny)
Module-CPANfile (Module::CPANfile)
Menlo-Legacy (Menlo::CLI::Compat)
Menlo (Menlo)
File-Which (File::Which)
CPAN-DistnameInfo (CPAN::DistnameInfo)
String-ShellQuote (String::ShellQuote)
ExtUtils-Helpers (ExtUtils::Helpers)
CPAN-Meta-Check (CPAN::Meta::Check)
Parse-PMFile (Parse::PMFile)
Win32-ShellQuote (Win32::ShellQuote)
URI (URI)
Capture-Tiny (Capture::Tiny)
CPAN-Common-Index (CPAN::Common::Index)
// URI (URI)
// Class-Tiny (Class::Tiny)
Tie-Handle-Offset (Tie::Handle::SkipHeader)
// CPAN-DistnameInfo (CPAN::DistnameInfo)
HTTP-Tinyish (HTTP::Tinyish)
// File-Which (File::Which)
IPC-Run3 (IPC::Run3)
// Module-CPANfile (Module::CPANfile)
local-lib (local::lib)
Module-Build (Module::Build)
// Class-Tiny (Class::Tiny)
ExtUtils-InstallPaths (ExtUtils::InstallPaths)
ExtUtils-Config (ExtUtils::Config)
File-pushd (File::pushd)
// ExtUtils-Config (ExtUtils::Config)
Try-Tiny (Try::Tiny)
Path-Tiny (Path::Tiny)
This shows only the runtime
dependencies. There are also configure
, build
and test
dependencies.
Now, there's no perl-carton
package, as such we need to know its other dependencies:
$ perl deps.pl --non-core --no-develop prereqs Carton
$VAR1 = {
'runtime' => {
'recommends' => {
'App::FatPacker' => '0.009018',
'File::pushd' => '0'
},
'requires' => {
'Try::Tiny' => '0.09',
'Module::CPANfile' => '0.9031',
'Class::Tiny' => '1.001',
'Path::Tiny' => '0.033',
'Menlo::CLI::Compat' => '1.9018',
'perl' => 'v5.8.5'
}
}
};
Convert it into distribution names:
$ echo 'App::FatPacker
File::pushd
Try::Tiny
Module::CPANfile
Class::Tiny
Path::Tiny
Menlo::CLI::Compat' | perl deps.pl mod2dist
App-FatPacker
File-pushd
Try-Tiny
Module-CPANfile
Class-Tiny
Path-Tiny
Menlo-Legacy
There are the following Alpine packages:
perl-file-pushd
perl-try-tiny
perl-class-tiny
perl-path-tiny
This leaves:
App-FatPacker
Module-CPANfile
Menlo-Legacy
Repeat the procedure for them:
$ perl deps.pl --non-core --no-develop prereqs App-FatPacker
$VAR1 = {
'runtime' => {
'requires' => {
'perl' => '5.008000'
}
}
};
$ perl deps.pl --non-core --no-develop prereqs Module-CPANfile
$VAR1 = {
'test' => {
'requires' => {
'File::pushd' => '0'
}
}
};
$ perl deps.pl mod2dist File::pushd
File-pushd
Existing packages: perl-file-pushd
.
$ perl deps.pl --non-core --no-develop prereqs Menlo-Legacy
$VAR1 = {
'runtime' => {
'requires' => {
'perl' => '5.008001',
'Menlo' => '1.9018'
}
}
};
$ perl deps.pl mod2dist Menlo
Menlo
Leftover dependencies: Menlo
.
$ perl deps.pl --non-core --no-develop prereqs Menlo
$VAR1 = {
'runtime' => {
'suggests' => {
'Archive::Zip' => '0',
'File::HomeDir' => '0',
'Module::Signature' => '0',
'LWP::UserAgent' => '5.802'
},
'requires' => {
'perl' => '5.008001',
'local::lib' => '0',
'Win32::ShellQuote' => '0',
'Class::Tiny' => '1.001',
'CPAN::Common::Index' => '0.006',
'URI' => '0',
'Capture::Tiny' => '0',
'ExtUtils::Config' => '0.003',
'File::Which' => '0',
'File::pushd' => '0',
'HTTP::Tinyish' => '0.04',
'ExtUtils::InstallPaths' => '0.002',
'ExtUtils::Helpers' => '0.020',
'CPAN::Meta::Check' => '0',
'Parse::PMFile' => '0.26',
'String::ShellQuote' => '0',
'CPAN::DistnameInfo' => '0',
'Module::CPANfile' => '0'
}
}
};
Ignore suggests
since cpan
doesn't install them by default.
$ echo 'local::lib
Win32::ShellQuote
Class::Tiny
CPAN::Common::Index
URI
Capture::Tiny
ExtUtils::Config
File::Which
File::pushd
HTTP::Tinyish
ExtUtils::InstallPaths
ExtUtils::Helpers
CPAN::Meta::Check
Parse::PMFile
String::ShellQuote
CPAN::DistnameInfo
Module::CPANfile' | perl deps.pl mod2dist
local-lib
Win32-ShellQuote
Class-Tiny
CPAN-Common-Index
URI
Capture-Tiny
ExtUtils-Config
File-Which
File-pushd
HTTP-Tinyish
ExtUtils-InstallPaths
ExtUtils-Helpers
CPAN-Meta-Check
Parse-PMFile
String-ShellQuote
CPAN-DistnameInfo
Module-CPANfile
Existing packages:
perl-class-tiny
perl-uri
perl-capture-tiny
perl-extutils-config
perl-file-which
perl-file-pushd
perl-extutils-installpaths
perl-extutils-helpers
perl-cpan-meta-check
perl-string-shellquote
Leftover dependencies:
Module-CPANfile
(already examined)Win32-ShellQuote
CPAN-DistnameInfo
local-lib
HTTP-Tinyish
CPAN-Common-Index
Parse-PMFile
$ perl deps.pl --non-core --no-develop prereqs Win32-ShellQuote
$VAR1 = {
'runtime' => {
'requires' => {
'perl' => '5.006'
}
}
};
$ perl deps.pl --non-core --no-develop prereqs CPAN-DistnameInfo
$VAR1 = {};
$ perl deps.pl --non-core --no-develop prereqs local-lib
$VAR1 = {
'runtime' => {
'requires' => {
'Module::Build' => '0.36',
'perl' => '5.006'
}
}
};
$ echo Module::Build | perl deps.pl mod2dist
Module-Build
Existing packages: perl-module-build
.
$ perl deps.pl --non-core --no-develop prereqs HTTP-Tinyish
$VAR1 = {
'runtime' => {
'requires' => {
'perl' => '5.008001',
'IPC::Run3' => '0',
'File::Which' => '0'
}
}
};
$ echo 'IPC::Run3
File::Which' | perl deps.pl mod2dist
IPC-Run3
File-Which
Existing package:
perl-ipc-run3
perl-file-which
$ perl deps.pl --non-core --no-develop prereqs CPAN-Common-Index
$VAR1 = {
'runtime' => {
'requires' => {
'Tie::Handle::SkipHeader' => '0',
'perl' => '5.008001',
'Class::Tiny' => '0',
'URI' => '0',
'CPAN::DistnameInfo' => '0'
}
},
'configure' => {
'requires' => {
'perl' => '5.008001'
}
},
'test' => {
'requires' => {
'perl' => '5.008001',
'Test::Fatal' => '0',
'Test::FailWarnings' => '0',
'Test::Deep' => '0'
}
}
};
$ echo 'Tie::Handle::SkipHeader
Class::Tiny
URI
CPAN::DistnameInfo
Test::Fatal
Test::FailWarnings
Test::Deep' | perl deps.pl mod2dist
Tie-Handle-Offset
Class-Tiny
URI
CPAN-DistnameInfo
Test-Fatal
Test-FailWarnings
Test-Deep
Existing packages:
perl-class-tiny
perl-uri
perl-test-fatal
perl-test-failwarnings
perl-test-deep
Leftover dependencies:
CPAN-DistnameInfo
(already examined)Tie-Handle-Offset
$ perl deps.pl --non-core --no-develop prereqs Tie-Handle-Offset
$VAR1 = {
'runtime' => {
'requires' => {
'perl' => '5.006'
}
},
'configure' => {
'requires' => {
'perl' => '5.006'
}
},
'test' => {
'requires' => {
'perl' => '5.006'
}
}
};
$ perl deps.pl --non-core --no-develop prereqs Parse-PMFile
$VAR1 = {
'configure' => {
'requires' => {
'ExtUtils::MakeMaker::CPANfile' => '0.09'
}
}
};
$ echo ExtUtils::MakeMaker::CPANfile | perl deps.pl mod2dist
ExtUtils-MakeMaker-CPANfile
Leftover dependencies:
ExtUtils-MakeMaker-CPANfile
$ perl deps.pl --non-core --no-develop prereqs ExtUtils-MakeMaker-CPANfile
$VAR1 = {
'runtime' => {
'requires' => {
'Module::CPANfile' => '0'
}
}
};
$ echo Module::CPANfile | perl deps.pl mod2dist
Module-CPANfile
Leftover dependencies:
Module-CPANfile
(already examined)
This gives us the following list of Alpine packages that can be preinstalled:
perl-capture-tiny
perl-class-tiny
perl-cpan-meta-check
perl-extutils-config
perl-extutils-helpers
perl-extutils-installpaths
perl-file-pushd
perl-file-which
perl-ipc-run3
perl-module-build
perl-path-tiny
perl-string-shellquote
perl-test-deep
perl-test-failwarnings
perl-test-fatal
perl-try-tiny
perl-uri
Then cpan
has to install only:
App-FatPacker
Carton
CPAN-Common-Index
CPAN-DistnameInfo
ExtUtils-MakeMaker-CPANfile
HTTP-Tinyish
local-lib
Menlo
Menlo-Legacy
Module-CPANfile
Parse-PMFile
Tie-Handle-Offset
Win32-ShellQuote