Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Finding Module Versions - How Would You Do It?

by Tommy (Chaplain)
on Feb 13, 2013 at 00:25 UTC ( [id://1018469]=perlquestion: print w/replies, xml ) Need Help??

Tommy has asked for the wisdom of the Perl Monks concerning the following question:

The Problem

Let's say you have a need to find out the version of a given module on your system. Easy right? Usually I just do this:

perl -MSome::Module -e 'print Some::Module->VERSION'

However I ran into some problems with that today. Have you ever tried that little trick above? It seems slick right? I've seen it used in various documentation before, but with it's "accuracy", it suffers a serious shortcoming: it requires the module to be compiled by the Perl interpreter and anything in a BEGIN block gets *Gasp!* executed.

The Trouble

That's just fine until you try that one-liner on, for example, Test::More or others like it. Many, many, many modules in the Test::* namespace start getting crazy kinds of busy in just the compile-time phase. They start testing schtuff. And they start spewing all kinds of lines of output that have nothing to do with the version number you were looking for. In fact, you might get such mangled output that you never see the version number at all because the module thought it was use()d incorrectly and bailed before "run time" ever kicked off. We could complain about that, but there's no point in it. The behavior is annoying in this case, but it had to be so. By design, these modules are working just as they should, and you can't very well change that.

The Compromise

So what's next? While this is a bit of an annoyance, you could somewhat easily search out the source code for a given module on your system and open it up (Perl is open source!) Then you have the version number right there and all is well. You got what you came for.

The Fun Begins

But wait cowboy! Not so fast! What if you need to automate this? What if you need to find out the version number of many modules, maybe upwards of 20 or more, and you might need to do this on a regular basis? The solution seems that you're going to have to downgrade your expectations a bit and try to parse out the version numbers of a given large list of modules using some as-intelligent-as-possible regexes.

The downside to this is that you're trying to determine with the greatest level of certainty that you're regex(es) are actually going to find the *correct* module version number. And while it's ugly, it's not impossible for that kind of information to be obscured in the module source by an author who was trying to be "cute" or "clever" or even downright devious. It is even perfectly legal for a module to change it's supposed $VERSION number, treating it as any other mutable variable. It can be done at compile time and runtime too (anybody who does that should get a really long timeout in the corner). That original one-liner I put out there has it's weakness, but herein lies its strength: when it runs, it sees in real time what the real module version is supposed to be. This kind of goes without saying, but I thought I'd highlight it anyway for the sake of completeness.

In the end, unless you want to write a full-blown script using PPI (which I tried and it actually misses things that grep does not), or unless you know something I don't, you're left with the regex option. And that's not fun.

The Challenge

I'm going to show you how I did it, and I'm going to ask you if you know a better way-- becuase I'd like to use a better way than what I've currently got. So let's move forward and consider a more specific use case: You've got a directory of files that contain Perl source code and they could contain any random number of use statements. Just assume that they are using all kinds of modules. Your job is to detect those "use" statements, and find out what the running $VERSION number is for each module getting use()d.

The perldoc utility provides an awesome feature that let's you specify the -l (lowercase L) flag, and perldoc will print out the location of the source file for that module. Very handy. Knowing this, you can ask perldoc for the location of each file for each "use" statement you detect. The rest of the business of discovering the true version of that module is up to you.

Here's how I did it today, and here's exactly what I hope you can help me improve:

The Code

# at the command prompt, assuming we're already in the top-level direc +tory # containing all the perl source files... Be advised: this won't work + until # you remove all the comments grep -rhP '\buse\b' * | \ # find "use"-like statements perl -E 'say $_ =~ /\buse\s+([\w:]+)/ for <>;' | \ # find the modules +being used sort | \ # weed out... uniq | \ # ...any duplicates while read packname; # and start looping through the results do perldoc -l $packname 2>&-; # ask perldoc where source file is, igno +re errors done | \ grep -v '.pod$' | \ # exclude pod files from the results; they are not + modules while read packfile; # and loop through the results of the source file + lookups do echo -n $packfile ' -> '; # here the real output begins. Show the +filename: grep -P '\$([\w:]){0,}VERSION\b\s+=\s{0,}[[:punct:]]{0,1}\d' $packf +ile; # then ^^above^^ try your best to parse out the version number of t +he module # which will then get sent to the terminal done

The "clean" version (that does work when executed)

grep -rhP '\buse\b' * | \ perl -E 'say $_ =~ /\buse\s+([\w:]+)/ for <>;' | \ sort | \ uniq | \ while read packname; do perldoc -l $packname 2>&-; done | \ grep -v '.pod$' | \ while read packfile; do echo -n $packfile ' -> '; grep -P '\$([\w:]){0,}VERSION\b\s+=\s{0,}[[:punct:]]{0,1}\d' $packf +ile; done

...And finally, the actual one-liner in all it's beauty

grep -rhP '\buse\b' * | perl -E 'say $_ =~ /\buse\s+([\w:]+)/ for <>;' + | sort | uniq | while read packname; do perldoc -l $packname 2>&-; d +one | grep -v '.pod$' | while read packfile; do echo -n $packfile ' - +> '; grep -P '\$([\w:]){0,}VERSION\b\s+=\s{0,}[[:punct:]]{0,1}\d' $pa +ckfile; done

The Output

I piped the previous command through | column -ts - ...

/usr/share/perl/5.10/English.pm > our $VERSI +ON = '1.04'; /usr/share/perl/5.10/File/Find.pm > our $VERSI +ON = '1.14'; /usr/lib/perl/5.10/File/Spec.pm > $VERSION = + '3.40'; /usr/share/perl/5.10/File/Temp.pm > $VERSION = + '0.22'; /usr/share/perl/5.10/FindBin.pm > $VERSION = + "1.50"; /usr/local/share/perl/5.10.1/Module/Build.pm > $VERSION = + '0.4003'; /usr/local/share/perl/5.10.1/Pod/Coverage/TrustPod.pm > $Pod::Co +verage::TrustPod::VERSION = '0.100002'; /usr/share/perl/5.10/subs.pm > our $VERSI +ON = '1.00'; /usr/local/share/perl/5.10.1/Test/CPAN/Changes.pm > our $VERSI +ON = '0.19'; /usr/local/share/perl/5.10.1/Test/CPAN/Meta.pm > $VERSION = + '0.22'; /usr/local/share/perl/5.10.1/Test/CPAN/Meta/JSON.pm > $VERSION = + '0.14'; /usr/local/share/perl/5.10.1/Test/DistManifest.pm > $Test::D +istManifest::VERSION = '1.012'; /usr/local/share/perl/5.10.1/Test/Fatal.pm > $Test::F +atal::VERSION = '0.010'; /usr/local/share/perl/5.10.1/Test/Kwalitee.pm > $VERSION = + '1.01'; /usr/local/share/perl/5.10.1/Test/MinimumVersion.pm > $Test::M +inimumVersion::VERSION = '0.101080'; /usr/local/share/perl/5.10.1/Test/Mojibake.pm > our $VERSI +ON = '0.7'; # VERSION /usr/local/share/perl/5.10.1/Test/More.pm > our $VERSI +ON = '0.98'; /usr/local/share/perl/5.10.1/Test/NoTabs.pm > $VERSION = + '1.3'; /usr/local/share/perl/5.10.1/Test/NoWarnings.pm > $VERSI +ON = '1.04'; /usr/local/share/perl/5.10.1/Test/Perl/Critic.pm > our $VERSI +ON = 1.02; /usr/local/share/perl/5.10.1/Test/Pod.pm > our $VERSI +ON = '1.45'; /usr/local/share/perl/5.10.1/Test/Pod/Coverage.pm > our $VERSI +ON = "1.08"; /usr/local/share/perl/5.10.1/Test/Portability/Files.pm > $Test::P +ortability::Files::VERSION = '0.06'; /usr/local/share/perl/5.10.1/Test/Script.pm > $VERSI +ON = '1.07'; /usr/local/share/perl/5.10.1/Test/Vars.pm > our $VERSI +ON = '0.002'; /usr/local/share/perl/5.10.1/Test/Version.pm > our $VERSI +ON = '1.002001'; # VERSION

The Bottom Line

So there you have it. That's an ugly, but working one-liner that produces only a few false-positives which can be easily weeded out or the regexes improved. Can you show me up and do it better (please)?

Update

It CAN be improved. Here's a step in the right direction. When I have more time I'll move it into that one-liner! One might consider it cheating, but it isn't cheating any more than using perldoc -l

perl -MCPAN -MCPAN::Shell -e 'CPAN::Shell->autobundle'| awk '{ print $1 " " $2 }' | column -t ... now read that into a hash and pull out the versions you need from the modules you found in the first bit of the grep command I presented.


Tommy
A mistake can be valuable or costly, depending on how faithfully you pursue correction

Replies are listed 'Best First'.
Re: Finding Module Versions - How Would You Do It?
by Tux (Canon) on Feb 13, 2013 at 07:20 UTC

    Use V, I use it more than once each day.

    $ perl -MV=Module::Info Module::Info /pro/lib/perl5/site_perl/5.14.1/Module/Info.pm: 0.33 /pro/lib/perl5/5.14.1/Module/Info.pm: 0.32 $ perl -MV=DBI,DBD::CSV,Text::CSV_XS,SQL::Statement DBI /pro/lib/perl5/site_perl/5.14.1/x86_64-linux-ld/DBI.pm: 1.623 DBD::CSV /pro/lib/perl5/site_perl/5.14.1/DBD/CSV.pm: 0.38 /pro/lib/perl5/5.14.1/DBD/CSV.pm: 0.33 Text::CSV_XS /pro/lib/perl5/site_perl/5.14.1/x86_64-linux-ld/Text/CSV_XS.pm +: 0.95 SQL::Statement /pro/lib/perl5/site_perl/5.14.1/SQL/Statement.pm: 1.402 /pro/lib/perl5/5.14.1/SQL/Statement.pm: 1.33 $

    As you can see, it can also be used to trace left-behind cruft from updates that could optionally be removed.

    You can also use it in scripts and make slick tables the way you like


    Enjoy, Have FUN! H.Merijn
Re: Finding Module Versions - How Would You Do It?
by tobyink (Canon) on Feb 13, 2013 at 07:01 UTC

    use Module::Info

    perl -MModule::Info -E'say Module::Info->new_from_module($_)->version +for @ARGV' LWP::UserAgent HTTP::Message
    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: Finding Module Versions - How Would You Do It?
by Plankton (Vicar) on Feb 13, 2013 at 02:47 UTC
    I suspect that your CPAN Modules were installed by doing something like ...
    $ perl -MCPAN -e install DBI
    ... but just in case this is not the case and your CPAN Perl modules were installed via yum/rpm (or Debian deb files) you could easily discover the version of the Perl module with a command line like this, rpm -q perl-DBI, in the case of Redhat.
      If you're using the OS vendor's outdated version of perl for your own perl programs, then that would be a great idea.

      Personally, I'll stick to using CPAN installed modules compiled against a modern version of perl.

        Or one could create their own RPMs, but that is a lot of work when you hit a modules that has a lot of dependencies. It would be great if CPAN would generate the RPM's as well as working out and installing the dependencies.
Re: Finding Module Versions - How Would You Do It?
by kcott (Archbishop) on Feb 13, 2013 at 07:38 UTC

    G'day Tommy,

    With older versions of Perl, I used to do:

    $ perl -e 'use Test::More; print $Test::More::VERSION, "\n"' 0.98

    With newer versions of Perl, I now use the slightly shorter:

    $ perl -E 'use Test::More; say $Test::More::VERSION' 0.98

    And to automate, using a selection of modules from your list, you could do something like:

    $ module_list='Test::More English File::Find Test::Perl::Critic' $ for i in $module_list; do > echo $i > eval "echo \`perl -E 'use $i; say \$$i::VERSION'\`" > done Test::More 0.98 English 1.04 File::Find 1.19 Test::Perl::Critic 1.02

    -- Ken

      I have this bash function in my .bashrc:

      pmv () { perl -M$1 -e "print \$$1::VERSION . qq/\n/;"; }

      which is based along similar lines and works for me. It is invoked like this:

      pmv Test::More

      Simple, but effective.

        Have you two even read the post at all? This is exactly what Tommy doesn’t want to do for reasons explained there.

Re: Finding Module Versions - How Would You Do It?
by DrHyde (Prior) on Feb 13, 2013 at 11:54 UTC
    When I needed this for cpXXXan I borrowed the code from PAUSE.
Re: Finding Module Versions - How Would You Do It?
by Khen1950fx (Canon) on Feb 13, 2013 at 07:58 UTC
    I usually end up using this:
    #!/usr/bin/perl BEGIN { $| = 1; $^W = 1; } use strict; use warnings; use CPAN; use Term::ANSIColor::Print; my $print = Term::ANSIColor::Print->new(); my @mods = (qw( Scope::Guard Algorithm::C3 Class::C3 Exporter Sub::Install Encode Test Test::More Test::Harness Text::Wrap Pod::Escapes Pod::Simple Pod::Man File::Spec ExtUtils::MakeMaker namespace::clean namespace::autoclean Class::MOP Declare::Constraints::Simple Package::DeprecationManager Sub::Name Try::Tiny Test::Fatal Test::Requires Dist::CheckConflicts Data::OptList Devel::GlobalDestruction List::MoreUtils MRO::Compat Params::Util Scalar::Util Sub::Exporter Task::Weaken Fey Fey::ORM IO::KQueue File::ChangeNotify KiokuDB Moose Moose::Meta::Class MooseX::Aliases MooseX::Attribute::Prototype MooseX::SlurpyConstructor MooseX::Role::WithOverloading MooseX::ClassAttribute MooseX::MethodAttributes MooseX::NonMoose MooseX::POE MooseX::Role::Parameterized MooseX::SemiAffordanceAccessor MooseX::StrictConstructor MooseX::UndefTolerant Package::Stash::XS Package::Stash Eval::Closure Locale::BR DateTime::TimeZone SQL::Translator Set::Object Test::Moose Test::XML::Compare Any::Moose IO::Moose Readonly Readonly::XS)); foreach my $mod(@mods) { my (@matches) = grep(($_ eq $mod), @mods); if(@matches) { foreach $mod( CPAN::Shell->expand("Module", $mod)) { $print->bold_black( "\n\tmodule: ", $mod->id), $print->bold_blue( "\tcurrent version: ", $mod->cpan_version), $print->bold_black( "\tinstalled version: ", $mod->inst_version); } } }
Re: Finding Module Versions - How Would You Do It?
by Anonymous Monk on Feb 13, 2013 at 08:24 UTC

      Oh yes, I suppose I should have mentioned p5u, given that I wrote it. Sample output:

      $ p5u v LWP::UserAgent LWP::UserAgent /home/tai/.perlbrew/libs/perl-5.16.2@default/lib/perl5/LWP/UserAge +nt.pm: 6.04

      Or show the versions available on the CPAN:

      $ p5u v -c LWP::UserAgent LWP::UserAgent cpan:GAAS/LWP5emu-0.01_01.tar.gz#lib/LWP/UserAgent.pm: 0.01_01 (19 +98-04-24T19:34:25.000Z) cpan:GAAS/libwww-perl-5.834.tar.gz#lib/LWP/UserAgent.pm: 5.834 (20 +09-11-21T13:09:14.000Z) cpan:GAAS/libwww-perl-5.835.tar.gz#lib/LWP/UserAgent.pm: 5.835 (20 +10-05-05T21:13:47.000Z) cpan:GAAS/libwww-perl-5.836.tar.gz#lib/LWP/UserAgent.pm: 5.836 (20 +10-05-13T07:34:58.000Z) cpan:GAAS/libwww-perl-5.837.tar.gz#lib/LWP/UserAgent.pm: 5.837 (20 +10-09-20T21:24:38.000Z) cpan:GAAS/libwww-perl-6.00.tar.gz#lib/LWP/UserAgent.pm: 6.00 (2011 +-03-08T19:25:05.000Z) cpan:GAAS/libwww-perl-6.01.tar.gz#lib/LWP/UserAgent.pm: 6.01 (2011 +-03-09T23:30:57.000Z) cpan:GAAS/libwww-perl-6.02.tar.gz#lib/LWP/UserAgent.pm: 6.02 (2011 +-03-27T11:35:01.000Z) cpan:GAAS/libwww-perl-6.03.tar.gz#lib/LWP/UserAgent.pm: 6.03 (2011 +-10-15T13:38:28.000Z)

      Or the BackPAN:

      $ p5u v -b LWP::UserAgent LWP::UserAgent backpan:GAAS/libwww-perl-5.00.tar.gz#lib/LWP/UserAgent.pm: 5.00 (1 +996-05-26T14:01:51.000Z) backpan:GAAS/libwww-perl-5.01.tar.gz#lib/LWP/UserAgent.pm: 5.01 (1 +996-08-02T16:38:58.000Z) [snip long output]

      Or show a combination of the above:

      $ p5u v -lcb LWP::UserAgent LWP::UserAgent /home/tai/.perlbrew/libs/perl-5.16.2@default/lib/perl5/LWP/UserAge +nt.pm: 6.04 cpan:GAAS/LWP5emu-0.01_01.tar.gz#lib/LWP/UserAgent.pm: 0.01_01 (19 +98-04-24T19:34:25.000Z) cpan:GAAS/libwww-perl-5.834.tar.gz#lib/LWP/UserAgent.pm: 5.834 (20 +09-11-21T13:09:14.000Z) cpan:GAAS/libwww-perl-5.835.tar.gz#lib/LWP/UserAgent.pm: 5.835 (20 +10-05-05T21:13:47.000Z) cpan:GAAS/libwww-perl-5.836.tar.gz#lib/LWP/UserAgent.pm: 5.836 (20 +10-05-13T07:34:58.000Z) cpan:GAAS/libwww-perl-5.837.tar.gz#lib/LWP/UserAgent.pm: 5.837 (20 +10-09-20T21:24:38.000Z) cpan:GAAS/libwww-perl-6.00.tar.gz#lib/LWP/UserAgent.pm: 6.00 (2011 +-03-08T19:25:05.000Z) cpan:GAAS/libwww-perl-6.01.tar.gz#lib/LWP/UserAgent.pm: 6.01 (2011 +-03-09T23:30:57.000Z) cpan:GAAS/libwww-perl-6.02.tar.gz#lib/LWP/UserAgent.pm: 6.02 (2011 +-03-27T11:35:01.000Z) cpan:GAAS/libwww-perl-6.03.tar.gz#lib/LWP/UserAgent.pm: 6.03 (2011 +-10-15T13:38:28.000Z) backpan:GAAS/libwww-perl-5.00.tar.gz#lib/LWP/UserAgent.pm: 5.00 (1 +996-05-26T14:01:51.000Z) backpan:GAAS/libwww-perl-5.01.tar.gz#lib/LWP/UserAgent.pm: 5.01 (1 +996-08-02T16:38:58.000Z) [snip long output]

      Let's check if our currently installed version of LWP::UserAgent is reliable...

      $ p5u testers -v 6.04 libwww-perl CPAN Testers results for libwww-perl version 6.04 PASS FAIL ETC Perl 5.006 0 0 40 Perl 5.008 256 15 1 Perl 5.010 282 3 3 Perl 5.012 670 2 3 Perl 5.014 510 16 2 Perl 5.016 309 26 0 Perl 5.017 229 32 0
      package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1018469]
Approved by johngg
Front-paged by Old_Gray_Bear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (4)
As of 2024-03-29 10:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found