Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Ovid (2709)

Ovid
  (email not shown publicly)
http://publius-ovidius.livejournal.com/
AOL IM: ovidperl (Add Buddy, Send Message)

Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.

Journal of Ovid (2709)

Monday July 16, 2007
11:05 AM

What's loaded?

[ #33818 ]

In a similar vein to this post, I once again need to see which modules are loaded. However, even with Sean Burke's excellent Module::Versions::Report, I can't tell if modules without version numbers are different, so I hacked up my Loaded.pm (first link), to generate md5 sums if no version number is present -- very common with in-house code -- but also to let me filter my version lists:

package Loaded;

use strict;
use warnings;
use Module::Info;
use Digest::MD5 'md5_hex';

################################################################## ############

=head1 NAME

Loaded -- identify which modules are loaded

=head1 SYNOPSIS

use Test::More 'no_plan';
use Loaded;
# all your tests
END { diag Loaded::versions() }

=head1 DESCRIPTION

This module allows you to dump out a list of loaded module versions at any
point in a program (typically at the end).  This is useful when you debugging
subtle problems.  Dump the version lists from separate installations to a file
and run C<diff> on them.

However, sometimes modules (particularly in-house code) doesn't have version
numbers.  When that happens, we just try to read the code and call
C<Digest::MD5::md5_hex()> on it.

You can pass a string to the import list and only modules whose name matches
that string (as a regex) will be loaded:

use Loaded '^Template::';   # get versions of all loaded Template:: modules

END { diag Loaded::versions() }

=cut

my $TARGET;

sub import {
    my $class = shift;
    my ($regex) = @_;
    $TARGET = qr/$regex/ if @_;
}

sub versions {
    my @modules;
    my $max = 0;
    while ( my ( $module, $path ) = each %INC ) {
        $module =~ s/\.pm$//;
        $module =~ s/\//::/g;
        next if $module =~ /^::/;
        next if __PACKAGE__ eq $module;
        next if $TARGET && $module !~ $TARGET;

        if ( length $module > $max ) {
            $max = length $module;
        }
        push @modules => [ $module, get_identifier( $module, $path ) ];
    }
    @modules = sort { $a->[0] cmp $b->[0] } @modules;
    $max += 2;
    no warnings 'uninitialized';
    return join "\n", map { sprintf "%-${max}s %s", @$_ } @modules;
}

sub get_identifier {
    my ( $module, $path ) = @_;

    my $mod = Module::Info->new_from_loaded($module);
    my $version = $mod ? $mod->version : '';
    return $version if $version;

    open my $fh, '<', $path
      or return "Could not determine version";
    my $text = do { local $/; <$fh> };

    # attempt to account for line endings.  May be a stupid idea.
    $text =~ s/[\n\r]//g;
    return md5_hex($text);
}

1;

Sample partial output:

# Regexp::Common::net             2.105
# Regexp::Common::number          2.108
# Regexp::Common::profanity       2.104
# Regexp::Common::whitespace      2.103
# Regexp::Common::zip             2.112
# SQL::Stripper                   4c8c58da79dddb6eb8760980c4446f5e
# Scalar::Util                    1.19
# SelectSaver                     1.01
# Storable                        2.15
# Sub::Uplevel                    0.09
# Symbol                          1.06
# Test::Class::Base               4a4587ee0b4a6b7fe9753f3e73cf9aeb
# Test::Database                  c8263967ebe0a2295b7bb2802d515266

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • I'd love to see these features in Module::Version::Report, which is now my problem child ;)
    • Your's? I see that Ruslan U. Zakirov maintains it and he/she has a bestpractical.com email address. Is this some strange alias or something? :)

      • Ruslan works for me. I _believe_ that both he and I are comaint on PAUSE. But it's in my (BPS's) repo and we do a lot of that 'collective code ownership' thing.
        • So, next question then. Why do you walk the symbol table instead of consulting %INC? Is this in case someone diddles %INC? That seems a bit strange, so I'm assuming that you've seen something I've missed.

          • It predates me, but my guess is:

            DB sub Foo::bar { 1}

                                                                                                                                             
            • Now I'm really confused. The snippet you have isn't valid Perl :/ What's the DB mean there? Something related to strange things with the debugger and the DB package? The rest I understand and think it's a non-issue (if you're manually creating a namespace, then the version check is irrelevant as you'll pick that up in code you already have).

              • Sorry. the bits were me copying and pasting sample code from the debugger.

                It's not _manually_ created code I'm concerned about. It's runtime-generated code. Packages that are automatically instantiated by other code. M::V::R isn't just "what versions are loaded" but also "what's loaded", which can often be a very useful bit of debugging information itself.
                • Reminds me of another debugging tool I've thought about:

                  use Package::Recreate 'recreate';

                  print recreate($some_package);

                  That would then walk through the package's symbol table and attempt to create a string that's equivalent to the code of a loaded package. It would likely be very fragile, though, hence using it as a debugging tool.

  • I was attempting to answer the "what's loaded from where" question.

    http://scratchcomputing.com/svn/Devel-TraceDeps/trunk/ [scratchcomputing.com]

    Inspired by chromatic's Devel::TraceUse and driven by a desire to get more info than Module::ScanDeps... ALL it needs is a front-end. (TM)