Who's relying on that CPAN release?

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!

3 Comments

Making CPANdeps do recursive reverse deps is on my to-do list.

It would be also interesting to track who, among CPAN Testers, is not an automated smoker and has installed your distribution. That would give interesting statistics on distribution usage besides CPAN dependency links.

Here are two programs that I now use to list dependencies and reverse deps on the command-line (they come respectively from App-ListPrereqs and App-ListRevDeps. They cache API results for 24h, by default.

What are the prerequisites for Text::ANSITable?

% list-prereqs Text::ANSITable

What are the prerequisites for Moo (recursively)?

% list-prereqs -r Moo

Which distributions depend on Text::ANSITable?

% list-rev-deps Text::ANSITable

Leave a comment

About Toby Inkster

user-pic I'm tobyink on CPAN, IRC and PerlMonks.