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 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 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
Re:I'd take a patch
Ovid on 2007-07-16T19:14:42
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?
:) Re:I'd take a patch
jesse on 2007-07-16T19:18:56
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.Re:I'd take a patch
Ovid on 2007-07-16T21:29:57
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.
Re:I'd take a patch
jesse on 2007-07-16T21:36:22
It predates me, but my guess is:
DB sub Foo::bar { 1}
DB print YAML::Dump(\%INC);Re:I'd take a patch
Ovid on 2007-07-17T07:46:30
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). Re:I'd take a patch
jesse on 2007-07-17T15:40:10
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.Re:I'd take a patch
Ovid on 2007-07-17T15:51:54
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.
Re:I'd take a patch
jesse on 2007-07-17T16:02:32
That could be interesting as part of a "build a mock package" toolkit.