Sometimes it’s useful to know who exactly is relying on your CPAN distribution – for example, if you’re planning an incompatible API change for a module, and wish to contact people using it to give them advance notice. MetaCPAN handily includes a “reverse dependencies” link for every distribution. However, sometimes that might not be enough; you may want recursive results. In which case you need to dig around in the MetaCPAN API.
Thankfully there’s a little module called MetaCPAN::API that helps you do just that. Here’s how I’ve used it (plus Moo) to retrieve recursive dependencies starting at a seed…
use v5.14; package MetaCPANx::RevDeps { use Moo; use List::MoreUtils qw(part); has seed => ( is => 'ro', required => 1, coerce => sub { ref($_[0]) ? $_[0] : 'MetaCPANx::RevDeps::Release'->new(distribution => $_[0]); }, ); has dependents => ( is => 'ro', builder => sub { +{} }, ); sub add { my $self = shift; my $hash = $self->dependents; # prioritise higher version numbers; non-developer releases my @all = map { sort { $b->version_numified <=> $a->version_numified } @$_ } part { $_->maturity eq 'developer' } @_; my @to_crawl; for my $dist (@all) { unless (exists $hash->{ $dist->distribution }) { $hash->{ $dist->distribution } = $dist; push @to_crawl, $dist; } } for my $dist (@to_crawl) { $self->add( $dist->fetch_direct_dependents ); } } sub BUILD { my $self = shift; $self->add( $self->seed->fetch_direct_dependents ); } }; package MetaCPANx::RevDeps::Release { use Moo; use overload q[""] => sub { my $self = shift; join q[ ], grep defined, $self->distribution, $self->version; }; has distribution => ( is => 'ro', required => 1, ); has abstract => ( is => 'ro', coerce => sub { $_[0] =~ s/\s+/ /rsmg }, ); has [qw/ author date maturity version version_numified /] => ( is => 'ro', ); has _metacpan => ( is => 'lazy', builder => sub { require MetaCPAN::API; 'MetaCPAN::API'->new; }, ); sub TO_JSON { my %hash = %{+shift}; delete $hash{$_} for qw/ _metacpan /; \%hash; } sub fetch_direct_dependents { my $self = shift; my $class = ref($self); my $data = $self->_metacpan->post( '/search/reverse_dependencies/' . $self->distribution, { query => { filtered => { query => { 'match_all' => {} }, filter => { and => [ { term => { 'release.status' => 'latest' } }, { term => { 'release.authorized' => \1 } }, ], }, }, }, size => 5000, from => 0, }, ) or return; return map { $class->new( %{ $_->{_source} }, _metacpan => $self->_metacpan, ) } @{ $data->{hits}{hits} }; } }; my $collection = 'MetaCPANx::RevDeps'->new(seed => 'MooX-late'); for my $key (sort keys %{ $collection->dependents }) { my $dist = $collection->dependents->{$key}; printf("%s (%s) - %s\n", $dist, $dist->author, $dist->abstract); }
Caveats:
- MetaCPAN includes build-time and testing dependencies, as well as optional dependencies in its data.
- If you run this script on one of Moose’s dependencies (including its optional dependencies), prepare to wait a very long time for results!