File Coverage

blib/lib/Devel/TraceDeps.pm
Criterion Covered Total %
statement 56 88 63.6
branch 16 40 40.0
condition 5 14 35.7
subroutine 6 8 75.0
pod n/a
total 83 150 55.3


line stmt bran cond sub pod time code
1             package Devel::TraceDeps;
2             $VERSION = v0.0.3;
3              
4             =head1 NAME
5              
6             Devel::TraceDeps - track loaded modules and objects
7              
8             =head1 SYNOPSIS
9              
10             $ perl -MDevel::TraceDeps your_program.pl
11              
12             And the real fun is to pull a tree of dependencies off of your test
13             suite.
14              
15             $ perl -MDevel::eps=tree -S prove -l -r t
16             $ ls tracedeps/
17              
18             And of course no Devel:: module would be complete without an obligatory
19             cute little shortcut which needlessly involves the DB backend:
20              
21             $ perl -d:eps whatever.pl
22              
23             TODO: a cute little shortcut which needlessly claims an otherwise
24             very funny-looking toplevel namespace.
25              
26             $ perl -MapDeps whatever.pl
27              
28             =head1 About
29              
30             Devel::TraceDeps delivers a comprehensive report of everything which was
31             loaded into your perl process via the C, C, or
32             C mechanisms.
33              
34             Unlike Devel::TraceLoad, this does not load any modules itself and is
35             intended to be very unintrusive. Unlike Module::ScanDeps, it is
36             designed to run alongside your test suite.
37              
38             For access to the resultant data, see the API in
39             L.
40              
41             In tree mode, forking processes and various other runtime effects
42             *should* be supported but surprises abound in this realm -- tests and
43             patches welcome.
44              
45             TODO reports on shared objects loaded by DynaLoader/XSLoader.
46              
47             TODO somehow catching the 'use foo 1.2' VERSION assertions. This is
48             handled by use() and is therefore outside of our reach (without some
49             tricks involving $SIG{__DIE__} or such.)
50              
51             =cut
52              
53             =begin note
54              
55             Depth can be inferred, though it is really meaningless because it is an
56             accident of chronology -- the second level never appears if something is
57             already loaded.
58              
59             Types are:
60              
61             'do', $what, $package, $line, $file
62             'req', $what, $package, $line, $file
63             'ver', $version
64              
65             TODO:
66             'loaded', $module, $return, $version||'undef', $modfile
67             'dlmod', $module
68             'failed', $module, $message
69             'done', $what, $return
70              
71             Does anything appear in %INC without our knowing?
72              
73             Dynaloader: @DynaLoader::dl_shared_objects or @DynaLoader::dl_modules ?
74              
75             =head1 Naming
76              
77             By $0, but need to address -e and maybe subprocesses. Perhaps the
78             import option takes care of that? There's also this issue of cleaning.
79              
80             -MDevel::TraceDeps=tree
81             cleans the .tracedeps/ dir
82             sets PERL5OPT to =child,$PWD/.tracedeps
83             does no tracing?
84              
85             =head1 After
86              
87             Which modules were successfully loaded:
88              
89             $module, $version
90              
91             Other data would be
92              
93             foreach $module (@loaded) {
94             push(@{$something{$module}{wanters}}, $wanter);
95             }
96              
97             =end note
98              
99             =cut
100              
101             my %store;
102              
103             # tracking the steps in the tree
104             my @trace;
105             my $tracemark = 0;
106              
107             my $debugging = 0; # for -d:... usage
108             BEGIN {
109 1 50   1   20765 if(defined(%DB::)) {
110 1         2 $debugging = 1;
111 1     0   6 *DB::DB = sub {};
  0         0  
112             }
113             *CORE::GLOBAL::do = sub {
114 0     0   0 my $target = shift;
115              
116 0         0 my ($p, $f, $l) = CORE::caller;
117 0   0     0 my $list = $store{$p} ||= [];
118              
119 0         0 push(@trace, ++$tracemark); $tracemark = 0;
  0         0  
120 0         0 push(@$list, my $req = {file => $f, line => $l, did => $target,
121             trace => join('-', @trace),
122             });
123             #warn "$p does $target ($f, $l)\n";
124 0         0 my $x = bless({mod => $target, req => $req, by => \@caller},
125             'Devel::TraceDeps::Watch');
126              
127 0         0 my $ret = CORE::do($target);
128 0 0       0 return($ret) if($ret);
129             #$x->{err} = $@ if($@);
130 0 0       0 if(defined($ret)) {
131 0 0       0 $req->{err} = "returned '$ret'" unless($ret);
132             }
133             else {
134 0         0 $req->{err} = $!;
135             }
136              
137 0         0 return($ret);
138 1         6 };
139             *CORE::GLOBAL::require = sub {
140 48     48   21559 my ($required) = @_;
141 48         73 my $module = $required; # don't touch the $required value
142              
143 48         258 my @caller = CORE::caller(0);
144 48         94 my ($p, $f, $l) = @caller;
145              
146             # remember it
147 48   100     147 my $list = $store{$p} ||= [];
148             #warn "$p wants $module ($f, $l)\n";
149              
150             # do data-gathering
151              
152             # pass through version numbers
153             # XXX require("0.4") edge cases :-/
154             # bah! this is version 5something dude
155 48 100 66     270 if(($module =~ m/^5(?:\.|$)/) or (ord(substr($module, 0, 1)) == 5)) {
156             # using it as a string breaks the versiony magic
157             # but an untouched value works fine
158             # ok, if it has literal dots it is a number
159 1 50       10 my $version =
    50          
160             $module eq '5' ? '5.000' :
161             $module =~ m/^5(?:\.|$)/ ? $module : sprintf("%vd", $module);
162 1         7 push(@$list, {file => $f, line => $l, ver => $version,
163             trace => join('-', @trace, ++$tracemark),
164             });
165 1         23 return CORE::require $required;
166             }
167              
168 47         67 push(@trace, ++$tracemark); $tracemark = 0;
  47         54  
169              
170 47         260 push(@$list, my $req = {
171             file => $f, line => $l, req => $module,
172             trace => join('-', @trace),
173             });
174              
175 47 100       128 if(exists($INC{$module})) {
176 43         70 $tracemark = pop(@trace);
177 43         152 return(1);
178             }
179              
180             # delicious and necessary evil: the object goes out of scope in that
181             # moment between the here and the there, thus: after the
182             # CORE::require completes, even if we're in eval.
183              
184             #warn join("|", 'caller =', @caller), "\n";
185 4         37 my $x = bless({mod => $module, req => $req, by => \@caller},
186             'Devel::TraceDeps::Watch');
187              
188             # apparently goto doesn't work here,
189             # so we need to tweak the caller stack?
190 4         4509 return scalar(CORE::require($module));
191 1         968 };
192             }
193             {
194             package Devel::TraceDeps::Watch;
195             sub DESTROY {
196 4     4   1574 my $self = shift;
197 4         19 my $req = $self->{req};
198 4 100       18 unless($INC{$self->{mod}}) {
199 1         4 $req->{fail} = 1;
200             }
201 4         8 $tracemark = pop(@trace);
202              
203             # hmm, can we tell if this is global cleanup time?
204              
205 4         12 my $caller = delete($self->{by});
206              
207 4 100       13 if(my $err = $@) {
208             # XXX ugh. eval("require foo") vs eval {require foo}!
209             # thanks base.pm
210 1 50       8 if($err =~ m/^(Can't locate .*\)) at /) {
211 1         3 my $fix_err = $1;
212 1         5 my @from = @$caller;
213             # emulate the builtin eval error here (eek)
214 1 50 33     9 my $at_file =
215             ($from[6] or $from[3] =~ m/::BEGIN$/) ? "(eval 424242)" :
216             $from[1];
217 1         2 my $at_line = $from[2];
218 1         5 $fix_err .= " at $at_file line $at_line.\n";
219 1         2 $@ = $fix_err; # YES I REALLY MEAN THAT
220             }
221             # the @INC bits are not important
222 1         7 $err =~ s/\(\@INC contains: .*/.../;
223 1         3 $err =~ s/\n$//;
224 1         3 $err =~ s/\n/\\n/g;
225 1         2 $req->{err} = $err;
226             }
227 4         62 return;
228             }
229             }
230              
231             sub _output {
232 1     1   5 my (%args) = @_;
233 1 50       6 return if($args{is_root});
234              
235 1         2 my $fh;
236 1 50       5 if(my $dir = $args{in_tree}) {
237 0         0 my $program = $args{program};
238 0         0 $program =~ s#^/+##;
239 0         0 $program =~ s#/+#---#g;
240 0         0 $outfile = $dir . '/' . $program;
241 0 0       0 if($$ != $args{init_pid}) {
242 0         0 $outfile .= '.' . $$;
243             }
244 0 0       0 open($fh, '>', $outfile) or die "cannot save $outfile $!";
245             }
246             else {
247 1         3 $fh = \*STDOUT;
248             }
249 1         8 foreach my $key (keys(%store)) {
250 7         63 print $fh $key, "\n";
251 7         10 foreach my $item (@{$store{$key}}) {
  7         20  
252 194         710 print $fh join("\n", ' -----',
253 48         156 map({" $_: $item->{$_}"} keys %$item)), "\n";
254             }
255             }
256             }
257              
258             ########################################################################
259             { # closure
260             my %self;
261              
262 1     1   324 END { _output(%self); }
263              
264             sub import {
265 1     1   8 my $class = shift;
266 1         2 my (@args) = @_;
267             #warn "my pid is $$";
268 1 50       11 if(@args) {
269 0 0         if($args[0] eq 'tree') {
    0          
270 0           $self{is_root} = 1;
271 0   0       my $dir = $args[1] || 'tracedeps';
272 0 0         if(-e $dir) {
273 0           die "$dir exists!";
274             }
275             else {
276 0           mkdir($dir);
277             }
278              
279             # just setup the subprocesses
280 0   0       $ENV{PERL5OPT} = join(' ',
281             split(/ /, $ENV{PERL5OPT}||''), "-MDevel::TraceDeps=tree=$dir"
282             );
283             }
284             elsif($args[0] =~ s/^tree=//) {
285             # subprocess
286 0           $self{in_tree} = $args[0];
287 0           $self{program} = $0;
288 0           $self{init_pid} = $$;
289             }
290             else {
291 0           die "unknown import args @args";
292             }
293             }
294             }
295             }
296             ########################################################################
297              
298             =head1 Possible Issues
299              
300             I think these are going to be very pathological cases since I've already
301             run a fair body of code through this without any visible hitches.
302              
303             =head2 Version Number Ambiguity
304              
305             If you try to require("5.whatever.pm"), it might fail.
306              
307             =head2 Caller
308              
309             If a required module expects to do something with caller() at BEGIN time
310             (e.g. outside of import()), we have problems. If I could think of a
311             good reason to rewrite the results of caller(), I would.
312              
313             =head2 Tree
314              
315             The tree setting goes all the way down into any perl subprocesses by
316             setting ourselves in PERL5OPT. This is probably what you want if you're
317             trying to package or bundle some code, but needs a knob if you're trying
318             to do something else with it.
319              
320             The PERL5OPT variable gets dropped if you use taint. Patches welcome!
321              
322             =head1 AUTHOR
323              
324             Eric Wilhelm @
325              
326             http://scratchcomputing.com/
327              
328             =head1 BUGS
329              
330             If you found this module on CPAN, please report any bugs or feature
331             requests through the web interface at L. I will be
332             notified, and then you'll automatically be notified of progress on your
333             bug as I make changes.
334              
335             If you pulled this development version from my /svn/, please contact me
336             directly.
337              
338             =head1 COPYRIGHT
339              
340             Copyright (C) 2008 Eric L. Wilhelm, All Rights Reserved.
341              
342             =head1 NO WARRANTY
343              
344             Absolutely, positively NO WARRANTY, neither express or implied, is
345             offered with this software. You use this software at your own risk. In
346             case of loss, no person or entity owes you anything whatsoever. You
347             have been warned.
348              
349             =head1 LICENSE
350              
351             This program is free software; you can redistribute it and/or modify it
352             under the same terms as Perl itself.
353              
354             =cut
355              
356             my $fakery = 'kwalitee police look the other way now please
357             use strict;
358             '; # we cannot use modules here, not even strict.pm
359              
360              
361             # vi:ts=2:sw=2:et:sta
362             1;