#!/usr/bin/perl use strict; use warnings; use File::Copy; use MetaCPAN::Client; use Text::Diff; use Data::Dumper; my $versions_filename = "$ENV{'HOME'}/lib/pmv/versions"; my $previous_versions_filename = "$ENV{'HOME'}/lib/pmv/versions.last"; my $debug_versions_filename = "$ENV{'HOME'}/lib/pmv/versions.debug"; my $dists_per_query = 50; my @modules = qw( accessors aliased Algorithm-C3 Algorithm-Diff Algorithm-Diff-XS Any-Moose AnyEvent AnyEvent-AIO AnyEvent-BDB Apache-Session Apache-Session-Browseable Apache-Session-LDAP Apache-Session-NoSQL Archive-Any Archive-Any-Lite Archive-Peek Archive-Tar Archive-Zip Array-Compare Array-Diff Authen-DigestMD5 Authen-Radius autobox autobox-Core autobox-dump autobox-List-Util autodie autovivification B-COW B-Hooks-EndOfScope B-Hooks-OP-Check B-Hooks-OP-Annotation B-Keywords B-Utils BDB BerkeleyDB BSD-Resource Business-ISBN-Data Cache-Cache Canary-Stability Capture-Tiny Carp-Clan Carp-Fix-1_25 Child Class-Accessor Class-C3 Class-C3-XS Class-Data-Inheritable Class-DBI-Plugin Class-Factory-Util Class-Inspector Class-Load Class-Load-XS Class-Loader Class-Method-Modifiers Class-Singleton Class-Tiny Class-XSAccessor Clone Clone-Choose common-sense Compress-LZF Compress-Raw-Bzip2 Compress-Raw-Lzma Compress-Raw-Zlib Config-Tiny ConfigReader-Simple Const-Fast constant-boolean Contextual-Return Convert-Bencode Convert-BinHex Convert-PEM Convert-TNEF Convert-UUlib Coro CPAN-Changes CPAN-DistnameInfo CPAN-Meta CPAN-Meta-Check CPAN-Meta-Requirements CPAN-Meta-YAML CPAN-Requirements-Dynamic Cpanel-JSON-XS Crypt-CBC Crypt-Cracklib Crypt-Curve25519 Crypt-DSA Crypt-DH Crypt-GPG Crypt-IDEA Crypt-PasswdMD5 Crypt-PBKDF2 Crypt-SmbHash Cwd-Guard Data-Alias Data-Binary Data-Buffer Data-Compare Data-Dump Data-Dump-Streamer Data-Dumper-Names Data-HexDump Data-OptList Data-Section Data-Section-Simple Data-Tumbler Data-UUID Data-Visitor Date-ISO8601 Date-Range Date-Simple DateTime DateTime-Calendar-Mayan DateTime-Format-Builder DateTime-Format-Epoch DateTime-Format-HTTP DateTime-Format-IBeat DateTime-Format-Mail DateTime-Format-MySQL DateTime-Format-Strptime DateTime-Format-XSD DateTime-Locale DateTime-TimeZone DateTime-TimeZone-SystemV DateTime-TimeZone-Tzfile DBD-CSV DBD-SQLite DBI DBM-Deep Declare-Constraints-Simple Devel-CallChecker Devel-Caller Devel-CheckBin Devel-CheckCompiler Devel-Cover Devel-Cycle Devel-Declare Devel-EnforceEncapsulation Devel-GlobalDestruction Devel-GlobalDestruction-XS Devel-Hide Devel-Leak Devel-LexAlias Devel-OverloadInfo Devel-PartialDump Devel-StackTrace Devel-Symdump Digest-BubbleBabble Digest-CRC Digest-MD2 Digest-MD4 Digest-MD5-File Digest-Perl-MD5 Digest-SHA Dir-Self Dist-CheckConflicts DynaLoader-Functions Email-Address Email-MessageID Email-MIME-ContentType Email-MIME-Encodings Env-Sanctify Error EV Eval-Closure Event Exception-Base Exception-Class Expect Exporter-Declare Exporter-Tiny ExtUtils-BuildRC ExtUtils-CBuilder ExtUtils-CChecker ExtUtils-Config ExtUtils-Depends ExtUtils-HasCompiler ExtUtils-Helpers ExtUtils-InstallPaths ExtUtils-MakeMaker-CPANfile ExtUtils-ParseXS ExtUtils-PkgConfig Feature-Compat-Class Fennec Fennec-Lite File-chdir File-Comments File-Copy-Recursive-Reduced File-Find-Object File-Find-Object-Rule File-Find-Rule File-Find-Rule-Perl File-Find-Rule-VCS File-HomeDir File-MMagic File-pushd File-Remove File-ShareDir File-ShareDir-Install File-ShareDir-ProjectDistDir File-Slurp File-Slurp-Tiny File-Slurper File-TreeCreate File-Which FileHandle-Fmode FileHandle-Unget Filter Filter-Simple Finance-Quote FreezeThaw Function-Parameters GD GDGraph GDGraph3d GDTextUtil Geography-Countries Getopt-Long-Descriptive grepmail Guard Hash-FieldHash Hash-Merge Hash-Merge-Simple Hash-StoredIterator Hash-Util-FieldHash-Compat Hook-LexWrap HTML-Lint HTML-SimpleLinkExtor HTML-Tagset HTML-Tidy HTTP-CookieJar HTTP-SimpleLinkChecker HTTP-Size HTTP-Tiny Import-Into Importer indirect IO-AIO IO-All IO-Capture IO-Compress IO-Compress-Lzma IO-FDPass IO-Multiplex IO-Socket-INET6 IO-Socket-IP IO-Socket-Socks IO-Socket-SSL IO-Socket-Timeout IO-String IO-Stringy IO-stringy IO-Tty IO-Zlib IPC-Cmd IPC-Run IPC-Run3 IPC-System-Simple Jcode JSON JSON-MaybeXS JSON-PP JSON-XS Lingua-EN-Inflect List-MoreUtils List-MoreUtils-XS List-SomeUtils List-SomeUtils-XS LMAP-CID2SPF Locale-Maketext-Lexicon Locale-Maketext-Simple Locale-US LockFile-Simple Mail-IMAPClient Mail-Message Mail-Mbox-MessageParser Mail-Sender Mail-Sendmail Mail-SPF Mail-SPF-Query Mail-SPF-Test Mail-SRS Mail-Transport MailTools Math-Base-Convert Math-BigInt-GMP Math-Calc-Units Math-GMP Math-Random-MT-Auto Math-Round MCE MCE-Shared MetaCPAN-API MetaCPAN-API-Tiny MetaCPAN-Client MIME-Base32 MIME-tools MIME-Types Mixin-Linewise MLDBM Mock-Quick Modern-Perl Module-Build Module-Build-Deprecated Module-Build-Tiny Module-Build-XSUtil Module-CoreList Module-CPANfile Module-CPANTS-Analyse Module-Extract-Namespaces Module-Extract-Use Module-Extract-VERSION Module-ExtractUse Module-Find Module-Implementation Module-Info Module-Load Module-Load-Conditional Module-Manifest Module-Metadata Module-Pluggable Module-Refresh Module-Runtime Module-Runtime-Conflicts Module-Signature Moo Moose MooseX-ConfigFromFile MooseX-GlobRef MooseX-InsideOut MooseX-NonMoose MooseX-Role-Parameterized MooseX-Role-WithOverloading MooseX-Types MooseX-Types-Path-Tiny MooseX-Types-Stringlike Mouse MouseX-ConfigFromFile MouseX-Getopt MouseX-SimpleConfig MouseX-Types-Path-Class Mozilla-PublicSuffix MRO-Compat namespace-autoclean namespace-clean Net-CIDR Net-CIDR-Lite Net-Daemon Net-DNS Net-DNS-Resolver-Programmable Net-FTPSSL Net-HTTPTunnel Net-IDN-Encode Net-IP Net-LibIDN Net-Server Net-SMTP-SSL Net-SMTP-TLS Net-SSH-Perl Net-SSLeay Net-Telnet NetAddr-IP Number-Compare Object-HashBase Object-ID Object-InsideOut OLE-Storage_Lite PAR-Dist Package-Anon Package-Constants Package-DeprecationManager Package-Generator Package-Stash Package-Stash-XS PadWalker Parallel-Runner Params-Check Params-Classify Params-Coerce Params-Util Params-ValidationCompiler Params-Validate parent Parse-CPAN-Meta Parse-Distname Parse-RecDescent Path-Class Path-FindDev Path-IsDev Path-Tiny Perl-Critic Perl-Critic-Deprecated Perl-Critic-Lax Perl-Critic-More Perl-Critic-Pulp Perl-Destruct-Level Perl-MinimumVersion Perl-OSType Perl-PrereqScanner-NotQuiteLite Perl-Tidy perl5i Perl6-Caller Perl6-Junction Perl6-Slurp PerlIO-gzip PerlIO-utf8_strict PerlIO-via-Timeout PlRPC Pod-Coverage Pod-Coverage-Moose Pod-Coverage-TrustMe Pod-Coverage-TrustPod Pod-Escapes Pod-Eventual Pod-MinimumVersion Pod-Readme Pod-Simple Pod-Spell Pod-Strip Pod-Wordlist-hanekomu PPI PPI-HTML PPIx-QuoteLike PPIx-Regexp PPIx-Utils PPIx-Utilities Probe-Perl Readonly Readonly-XS Ref-Util Ref-Util-XS Regexp-Assemble Regexp-Common Regexp-Parser Regexp-Trie Return-MultiLevel Role-Tiny RRD-Simple Safe-Isa Scalar-Properties Scope-Guard Search-Elasticsearch Search-Elasticsearch-Client-2_0 Sendmail-AccessDB Sendmail-Milter Sendmail-PMilter Sereal Sereal-Decoder Sereal-Encoder Set-Array Set-Scalar Socket6 Software-License Software-License-CCpack Specio Spellunker Spiffy Spreadsheet-WriteExcel SQL-Statement Statistics-ChiSquare strictures Sub-Exporter Sub-Exporter-ForMethods Sub-Exporter-Progressive Sub-Identify Sub-Info Sub-Install Sub-Name Sub-Quote Sub-Uplevel String-CRC32 String-Escape String-Format SUPER superclass SVN-Notify Symbol-Util Sys-HostAddr Sys-Hostname-Long Sysadm-Install Taint-Runtime Taint-Util TAP-Formatter-HTML TAP-Formatter-JUnit Task-Weaken TermReadKey Term-ReadLine-Gnu Term-Table Test-API Test-Assert Test-Base Test-Builder-Tester Test-CheckChanges Test-CheckDeps Test-Class Test-ClassAPI Test-CleanNamespaces Test-Command Test-CPAN-Meta Test-CPAN-Meta-JSON Test-CPAN-Meta-YAML Test-Deep Test-Differences Test-Directory Test-DistManifest Test-Distribution Test-EOL Test-Exception Test-FailWarnings Test-Fatal Test-File Test-File-ShareDir Test-Fixme Test-Harness Test-HasVersion Test-HTML-Tidy Test-Image-GD Test-InDistDir Test-Kwalitee Test-Kwalitee-Extra Test-LeakTrace Test-LongString Test-Manifest Test-Memory-Cycle Test-MinimumVersion Test-MockModule Test-Modern Test-Mojibake Test-More-UTF8 Test-Most Test-Needs Test-NoTabs Test-NoWarnings Test-Object Test-Output Test-Perl-Critic Test-Pod Test-Pod-Coverage Test-Portability-Files Test-Prereq Test-Regexp Test-Requires Test-RequiresInternet Test-Script Test-Signature Test-Simple Test-Spelling Test-SubCalls Test-Synopsis Test-Taint Test-Tester Test-TinyMocker Test-TrailingSpace Test-Unit-Lite Test-use-ok Test-UseAllModules Test-utf8 Test-Valgrind Test-Vars Test-Version Test-Warn Test-Warnings Test-Without-Module Test-WriteVariants Test-XML Test-YAML Test-YAML-Meta Test-YAML-Valid Test2 Test2-AsyncSubtest Test2-Plugin-NoWarnings Test2-Suite Test2-Tools-Explain Test2-Workflow Text-CSV_XS Text-Diff Text-Glob Text-Haml Text-Hunspell Text-SpellChecker Text-Template Text-Trac Tie-EncryptedHash Tie-IxHash Tie-RefHash-Weak Tie-ToObject Time-Piece-MySQL Time-y2038 TimeDate Tree-DAG_Node true Try-Tiny Type-Tie Type-Tiny Types-Path-Tiny Types-Serialiser Unicode-CheckUTF8 Unicode-Map Unicode-Map8 Unicode-MapUTF8 Unicode-String Unicode-UTF8 UNIVERSAL-isa UNIVERSAL-moniker UNIVERSAL-require Unix-Syslog URI URI-cpan User-Identity Variable-Magic Variable-OnDestruct version Version-Requirements WeakRef weblint XML-DTDParser XML-LibXML XML-LibXSLT XML-NamespaceSupport XML-Rules XML-SAX XML-SAX-Base XML-SAX-Expat XML-SemanticDiff XML-Simple XString YAML YAML-LibYAML YAML-Old YAML-Syck YAML-Tiny ); # Modules not on MetaCPAN (yet) my @bad_modules = qw( common-sense grepmail LMAP-CID2SPF weblint ); my %bad_modules_hash; @bad_modules_hash{@bad_modules} = (); # Extract version from dist name sub version_from_name ($$) { my ($dist, $name) = @_; my $version = substr($name, length($dist)+1); $version =~ s/^v//; return $version; } # Save the current state move($versions_filename, $previous_versions_filename); # Find the latest module versions my $mcpan = MetaCPAN::Client->new(); my @distro_set; my %stable_release = (); my %debug_release = (); my %devel_release = (); open(PERL_MODULE_VERSIONS, ">", $versions_filename); open(PERL_DEBUG_VERSIONS, ">", $debug_versions_filename); while (my $distro = shift(@modules)) { next if exists $bad_modules_hash{$distro}; push @distro_set, $distro; next unless scalar(@distro_set) >= $dists_per_query or scalar(@modules) == 0; print "Processing $distro_set[0] to $distro_set[-1]\n"; # Look up latest stable release for each of those dists my $releases = $mcpan->all( 'releases', { es_filter => { and => [ { term => { maturity => 'released' } }, { terms => { distribution => \@distro_set } } ] }, fields => [qw< authorized date distribution name version >], } ); # Look for the latest release of each dist in the results while (my $rel = $releases->next) { my $this_release = $rel->{'data'}; my $dist = $this_release->{'distribution'}; $stable_release{$dist} //= { date => "" }; $debug_release{$dist} //= ""; if ( $this_release->{'date'} gt $stable_release{$dist}->{'date'} ) { $debug_release{$dist} = $this_release; $this_release->{'version'} =~ s/^v//; $stable_release{$dist} = { auth => $this_release->{'authorized'}, date => $this_release->{'date'}, dver => version_from_name($dist, $this_release->{'name'}), mver => $this_release->{'version'}, }; } } # Make sure we got all of them foreach(@distro_set) { my $dist = $_; if (!defined($stable_release{$dist}->{'dver'})) { # Didn't find the dist, try lower case instead # (this is for you, Text-Haml) my $lc_dist = lc $dist; my $results = $mcpan->release( { all => [ { distribution => $lc_dist }, { maturity => 'released' }, ] }, { fields => [qw< authorized date name version >] } ); while ( my $rel = $results->next ) { my $this_release = $rel->{'data'}; if ( $this_release->{'date'} gt $stable_release{$dist}->{'date'} ) { $debug_release{$dist} = $this_release; $this_release->{'version'} =~ s/^v//; $stable_release{$dist} = { auth => $this_release->{'authorized'}, date => $this_release->{'date'}, dver => version_from_name($dist, $this_release->{'name'}), mver => $this_release->{'version'}, }; } } } print PERL_DEBUG_VERSIONS "\n\nStable \$debug_release for $dist:\n"; print PERL_DEBUG_VERSIONS Dumper($debug_release{$dist}); } # Now look up latest developer release for each of those dists %debug_release = (); $releases = $mcpan->all( 'releases', { es_filter => { and => [ { term => { maturity => 'developer' } }, { terms => { distribution => \@distro_set } } ] }, fields => [qw< authorized date distribution name version >], } ); # Look for the latest release of each dist in the results # Ignore devel releases prior to the current stable release while (my $rel = $releases->next) { my $this_release = $rel->{'data'}; my $dist = $this_release->{'distribution'}; $devel_release{$dist} //= { date => "" }; $debug_release{$dist} //= ""; if ( $this_release->{'date'} gt $devel_release{$dist}->{'date'} and $this_release->{'date'} gt $stable_release{$dist}->{'date'} ) { $debug_release{$dist} = $this_release; $this_release->{'version'} =~ s/^v//; $devel_release{$dist} = { auth => $this_release->{'authorized'}, date => $this_release->{'date'}, dver => version_from_name($dist, $this_release->{'name'}), mver => $this_release->{'version'}, }; } } # Output our findings foreach(@distro_set) { my $dist = $_; if (!defined($devel_release{$dist}->{'dver'})) { printf PERL_MODULE_VERSIONS "%-30s Stable: %s\n", $dist, $stable_release{$dist}->{'dver'} } else { printf PERL_MODULE_VERSIONS "%-30s Stable: %-8s Developer: %s\n", $dist, $stable_release{$dist}->{'dver'}, $devel_release{$dist}->{'dver'}; print PERL_DEBUG_VERSIONS "\n\nDevel \$debug_release for $dist:\n"; print PERL_DEBUG_VERSIONS Dumper($debug_release{$dist}); } # v0 API returns 'true'/'false'; v1 API returns 1/0 $stable_release{$dist}->{'auth'} = 0 if ($stable_release{$dist}->{'auth'} eq 'false'); unless ($stable_release{$dist}->{'auth'} eq 'true' || $stable_release{$dist}->{'auth'} == 1) { printf PERL_MODULE_VERSIONS "Note: %s release %s is not authorized\n", $dist, $stable_release{$dist}->{'dver'}; } if ($stable_release{$dist}->{'dver'} ne $stable_release{$dist}->{'mver'}) { printf PERL_MODULE_VERSIONS "Note: %s latest META version (%s) does not match latest release (%s)\n", $dist, $stable_release{$dist}->{'mver'}, $stable_release{$dist}->{'dver'}; } if (defined($devel_release{$dist}->{'dver'}) and $devel_release{$dist}->{'dver'} ne $devel_release{$dist}->{'mver'} and $devel_release{$dist}->{'dver'} ne $devel_release{$dist}->{'mver'} . '-TRIAL') { printf PERL_MODULE_VERSIONS "Note: %s devel META version (%s) does not match devel release (%s)\n", $dist, $devel_release{$dist}->{'mver'}, $devel_release{$dist}->{'dver'}; } } @distro_set = (); %stable_release = (); %devel_release = (); } close(PERL_MODULE_VERSIONS); close(PERL_DEBUG_VERSIONS); # Compare the old with the new print "All done - running diff\n"; diff $previous_versions_filename, $versions_filename, { STYLE => "OldStyle", OUTPUT => \*STDOUT }