Who’s relying on that CPAN release?

This is a very old article. It has been imported from older blogging software, and the formatting, images, etc may have been lost. Some links may be broken. Some of the information may no longer be correct. Opinions expressed in this article may no longer be held.

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!