Fix describe_env failure on nonexistent @INC on Win32
[dbsrgits/DBIx-Class.git] / t / 00describe_environment.t
CommitLineData
cebc0cc8 1###
2### This version is rather 5.8-centric, because DBIC itself is 5.8
3### It certainly can be rewritten to degrade well on 5.6
4###
5
3cd1b172 6# Very important to grab the snapshot early, as we will be reporting
7# the INC indices from the POV of whoever ran the script, *NOT* from
8# the POV of the internals
9my @initial_INC;
cebc0cc8 10BEGIN {
3cd1b172 11 @initial_INC = @INC;
12}
13
c0329273 14BEGIN { do "./t/lib/ANFANG.pm" or die ( $@ || $! ) }
3cd1b172 15
c0329273 16BEGIN {
750a4ad2 17 if ( "$]" < 5.010) {
cebc0cc8 18
19 # Pre-5.10 perls pollute %INC on unsuccesfull module
20 # require, making it appear as if the module is already
21 # loaded on subsequent require()s
22 # Can't seem to find the exact RT/perldelta entry
23 #
24 # The reason we can't just use a sane, clean loader, is because
25 # if a Module require()s another module the %INC will still
26 # get filled with crap and we are back to square one. A global
27 # fix is really the only way for this test, as we try to load
28 # each available module separately, and have no control (nor
29 # knowledge) over their common dependencies.
30 #
31 # we want to do this here, in the very beginning, before even
32 # warnings/strict are loaded
33
cebc0cc8 34 require DBICTest::Util::OverrideRequire;
35
36 DBICTest::Util::OverrideRequire::override_global_require( sub {
37 my $res = eval { $_[0]->() };
38 if ($@ ne '') {
39 delete $INC{$_[1]};
40 die $@;
41 }
42 return $res;
43 } );
44 }
45}
46
cebc0cc8 47use strict;
48use warnings;
49
50use Test::More 'no_plan';
51use Config;
52use File::Find 'find';
3cd1b172 53use Digest::MD5 ();
54use Cwd 'abs_path';
c6b73be9 55use File::Spec;
843b427a 56use List::Util 'max';
cebc0cc8 57use ExtUtils::MakeMaker;
cebc0cc8 58
c6b73be9 59use DBICTest::RunMode;
10be570e 60use DBIx::Class::_Util 'visit_namespaces';
c6b73be9 61use DBIx::Class::Optional::Dependencies;
cebc0cc8 62
c6b73be9 63my $known_paths = {
55c6fb91 64 SA => {
65 config_key => 'sitearch',
66 },
67 SL => {
68 config_key => 'sitelib',
69 },
c6b73be9 70 SS => {
71 config_key => 'sitelib_stem',
72 match_order => 1,
73 },
74 SP => {
75 config_key => 'siteprefix',
76 match_order => 2,
77 },
55c6fb91 78 VA => {
79 config_key => 'vendorarch',
80 },
81 VL => {
82 config_key => 'vendorlib',
83 },
c6b73be9 84 VS => {
85 config_key => 'vendorlib_stem',
86 match_order => 3,
87 },
88 VP => {
89 config_key => 'vendorprefix',
90 match_order => 4,
91 },
55c6fb91 92 PA => {
93 config_key => 'archlib',
94 },
95 PL => {
96 config_key => 'privlib',
97 },
c6b73be9 98 PP => {
99 config_key => 'prefix',
100 match_order => 5,
101 },
102 BLA => {
103 rel_path => './blib/arch',
104 skip_unversioned_modules => 1,
105 },
106 BLL => {
107 rel_path => './blib/lib',
108 skip_unversioned_modules => 1,
109 },
55c6fb91 110 INC => {
c6b73be9 111 rel_path => './inc',
55c6fb91 112 },
113 LIB => {
c6b73be9 114 rel_path => './lib',
115 skip_unversioned_modules => 1,
55c6fb91 116 },
3cd1b172 117 T => {
c6b73be9 118 rel_path => './t',
119 skip_unversioned_modules => 1,
120 },
65ea9cad 121 XT => {
122 rel_path => './xt',
123 skip_unversioned_modules => 1,
124 },
c6b73be9 125 CWD => {
126 rel_path => '.',
3cd1b172 127 },
55c6fb91 128 HOME => {
c6b73be9 129 rel_path => '~',
130 abs_unix_path => abs_unix_path (
55c6fb91 131 eval { require File::HomeDir and File::HomeDir->my_home }
132 ||
c6b73be9 133 $ENV{USERPROFILE}
134 ||
55c6fb91 135 $ENV{HOME}
136 ||
137 glob('~')
138 ),
139 },
cebc0cc8 140};
141
c6b73be9 142for my $k (keys %$known_paths) {
143 my $v = $known_paths->{$k};
4bfba4ee 144
55c6fb91 145 # never use home as a found-in-dir marker - it is too broad
146 # HOME is only used by the shortener
3cd1b172 147 $v->{marker} = $k unless $k eq 'HOME';
4bfba4ee 148
c6b73be9 149 unless ( $v->{abs_unix_path} ) {
150 if ( $v->{rel_path} ) {
151 $v->{abs_unix_path} = abs_unix_path( $v->{rel_path} );
55c6fb91 152 }
153 elsif ( $Config{ $v->{config_key} || '' } ) {
c6b73be9 154 $v->{abs_unix_path} = abs_unix_path (
55c6fb91 155 $Config{"$v->{config_key}exp"} || $Config{$v->{config_key}}
156 );
157 }
158 }
cebc0cc8 159
c6b73be9 160 delete $known_paths->{$k} unless $v->{abs_unix_path} and -d $v->{abs_unix_path};
cebc0cc8 161}
c6b73be9 162my $seen_markers = {};
cebc0cc8 163
55c6fb91 164# first run through lib/ and *try* to load anything we can find
cebc0cc8 165# within our own project
166find({
167 wanted => sub {
168 -f $_ or return;
169
399b9455 170 $_ =~ m|lib/DBIx/Class/_TempExtlib| and return;
171
cebc0cc8 172 # can't just `require $fn`, as we need %INC to be
173 # populated properly
174 my ($mod) = $_ =~ /^ lib [\/\\] (.+) \.pm $/x
175 or return;
176
55c6fb91 177 try_module_require(join ('::', File::Spec->splitdir($mod)) )
cebc0cc8 178 },
179 no_chdir => 1,
180}, 'lib' );
181
55c6fb91 182
183
cebc0cc8 184# now run through OptDeps and attempt loading everything else
185#
186# some things needs to be sorted before other things
187# positive - load first
188# negative - load last
189my $load_weights = {
190 # Make sure oracle is tried last - some clients (e.g. 10.2) have symbol
191 # clashes with libssl, and will segfault everything coming after them
192 "DBD::Oracle" => -999,
193};
3cd1b172 194
195my @known_modules = sort
cebc0cc8 196 { ($load_weights->{$b}||0) <=> ($load_weights->{$a}||0) }
8fc4291e 197 qw( Data::Dumper ),
cebc0cc8 198 keys %{
199 DBIx::Class::Optional::Dependencies->req_list_for([
250d9e55 200 grep
201 # some DBDs are notoriously problematic to load
202 # hence only show stuff based on test_rdbms which will
203 # take into account necessary ENVs
55c6fb91 204 { $_ !~ /^ (?: rdbms | dist )_ /x }
250d9e55 205 keys %{DBIx::Class::Optional::Dependencies->req_group_list}
cebc0cc8 206 ])
207 }
208;
209
3cd1b172 210try_module_require($_) for @known_modules;
cebc0cc8 211
55c6fb91 212my $has_versionpm = eval { require version };
3cd1b172 213
214
215# At this point we've loaded everything we ever could, but some modules
216# (understandably) crapped out. For an even more thorough report, note
217# everthing present in @INC we excplicitly know about (via OptDeps)
218# *even though* it didn't load
219my $known_failed_loads;
220
221for my $mod (@known_modules) {
222 my $inc_key = module_notional_filename($mod);
223 next if defined $INC{$inc_key};
224
225 if (defined( my $idx = module_found_at_inc_index( $mod, \@INC ) ) ) {
c6b73be9 226 $known_failed_loads->{$mod} = abs_unix_path( "$INC[$idx]/$inc_key" );
3cd1b172 227 }
228
229}
230
231my $perl = 'perl';
232
233# This is a cool idea, but the line is too long even with shortening :(
234#
235#for my $i ( 1 .. $Config{config_argc} ) {
236# my $conf_arg = $Config{"config_arg$i"};
237# $conf_arg =~ s!
238# \= (.+)
239# !
c6b73be9 240# '=' . shorten_fn($1)
3cd1b172 241# !ex;
242#
243# $perl .= " $conf_arg";
244#}
245
246my $interesting_modules = {
247 # pseudo module
248 $perl => {
249 version => $],
65ea9cad 250 abs_unix_path => abs_unix_path($^X),
3cd1b172 251 }
55c6fb91 252};
3cd1b172 253
254
65ea9cad 255# drill through the *ENTIRE* symtable and build a map of interesting modules
cebc0cc8 256visit_namespaces( action => sub {
4bfba4ee 257 no strict 'refs';
cebc0cc8 258 my $pkg = shift;
259
260 # keep going, but nothing to see here
261 return 1 if $pkg eq 'main';
262
4bfba4ee 263 # private - not interested, including no further descent
cebc0cc8 264 return 0 if $pkg =~ / (?: ^ | :: ) _ /x;
265
3cd1b172 266 my $inc_key = module_notional_filename($pkg);
cebc0cc8 267
c6b73be9 268 my $abs_unix_path = (
3cd1b172 269 $INC{$inc_key}
4bfba4ee 270 and
3cd1b172 271 -f $INC{$inc_key}
4bfba4ee 272 and
3cd1b172 273 -r $INC{$inc_key}
55c6fb91 274 and
c6b73be9 275 abs_unix_path($INC{$inc_key})
3cd1b172 276 );
cebc0cc8 277
3cd1b172 278 # handle versions first (not interested in synthetic classes)
cebc0cc8 279 if (
3cd1b172 280 defined ${"${pkg}::VERSION"}
cebc0cc8 281 and
3cd1b172 282 ${"${pkg}::VERSION"} !~ /\Qset by base.pm/
cebc0cc8 283 ) {
cebc0cc8 284
3cd1b172 285 # make sure a version can be extracted, be noisy when it doesn't work
286 # do this even if we are throwing away the result below in lieu of EUMM
287 my $mod_ver = eval { $pkg->VERSION };
288
289 if (my $err = $@) {
290 $err =~ s/^/ /mg;
291 say_err (
292 "Calling `$pkg->VERSION` resulted in an exception, which should never "
293 . "happen - please file a bug with the distribution containing $pkg. "
294 . "Complete exception text below:\n\n$err"
295 );
296 }
297 elsif( ! defined $mod_ver or ! length $mod_ver ) {
298 my $ret = defined $mod_ver
299 ? "the empty string ''"
300 : "'undef'"
301 ;
302
303 say_err (
304 "Calling `$pkg->VERSION` returned $ret, even though \$${pkg}::VERSION "
305 . "is defined, which should never happen - please file a bug with the "
306 . "distribution containing $pkg."
307 );
308
309 undef $mod_ver;
310 }
311
312 if (
c6b73be9 313 $abs_unix_path
3cd1b172 314 and
c6b73be9 315 defined ( my $eumm_ver = eval { MM->parse_version( $abs_unix_path ) } )
3cd1b172 316 ) {
317
318 # can only run the check reliably if v.pm is there
319 if (
320 $has_versionpm
321 and
322 defined $mod_ver
323 and
324 $eumm_ver ne $mod_ver
325 and
326 (
327 ( eval { version->parse( do { (my $v = $eumm_ver) =~ s/_//g; $v } ) } || 0 )
328 !=
329 ( eval { version->parse( do { (my $v = $mod_ver) =~ s/_//g; $v } ) } || 0 )
330 )
331 ) {
332 say_err (
333 "Mismatch of versions '$mod_ver' and '$eumm_ver', obtained respectively "
c6b73be9 334 . "via `$pkg->VERSION` and parsing the version out of @{[ shorten_fn( $abs_unix_path ) ]} "
3cd1b172 335 . "with ExtUtils::MakeMaker\@@{[ ExtUtils::MakeMaker->VERSION ]}. "
336 . "This should never happen - please check whether this is still present "
337 . "in the latest version, and then file a bug with the distribution "
338 . "containing $pkg."
339 );
340 }
341
342 $interesting_modules->{$pkg}{version} = $eumm_ver;
343 }
344 elsif( defined $mod_ver ) {
345
346 $interesting_modules->{$pkg}{version} = $mod_ver;
347 }
cebc0cc8 348 }
65ea9cad 349 elsif ( $known_failed_loads->{$pkg} ) {
350 $abs_unix_path = $known_failed_loads->{$pkg};
c6b73be9 351 $interesting_modules->{$pkg}{version} = '!! LOAD FAIL !!';
55c6fb91 352 }
353
c6b73be9 354 if ($abs_unix_path) {
355 my ($marker, $initial_inc_idx);
356
357 my $current_inc_idx = module_found_at_inc_index($pkg, \@INC);
358 my $p = subpath_of_known_path( $abs_unix_path );
359
360 if (
361 defined $current_inc_idx
362 and
363 $p->{marker}
364 and
365 abs_unix_path($INC[$current_inc_idx]) eq $p->{abs_unix_path}
366 ) {
367 $marker = $p->{marker};
3cd1b172 368 }
c6b73be9 369 elsif (defined ( $initial_inc_idx = module_found_at_inc_index($pkg, \@initial_INC) ) ) {
370 $marker = "\$INC[$initial_inc_idx]";
3cd1b172 371 }
55c6fb91 372
3cd1b172 373 # we are only interested if there was a declared version already above
c6b73be9 374 # OR if the module came from somewhere other than skip_unversioned_modules
3cd1b172 375 if (
376 $marker
377 and
378 (
379 $interesting_modules->{$pkg}
380 or
c6b73be9 381 !$p->{skip_unversioned_modules}
3cd1b172 382 )
383 ) {
384 $interesting_modules->{$pkg}{source_marker} = $marker;
c6b73be9 385 $seen_markers->{$marker} = 1;
55c6fb91 386 }
3cd1b172 387
388 # at this point only fill in the path (md5 calc) IFF it is interesting
389 # in any respect
c6b73be9 390 $interesting_modules->{$pkg}{abs_unix_path} = $abs_unix_path
3cd1b172 391 if $interesting_modules->{$pkg};
cebc0cc8 392 }
393
394 1;
395});
396
65ea9cad 397# compress identical versions sourced from ./blib, ./lib, ./t and ./xt
398# as close to the root of a namespace as we can
c6b73be9 399purge_identically_versioned_submodules_with_markers([ map {
400 ( $_->{skip_unversioned_modules} && $_->{marker} ) || ()
401} values %$known_paths ]);
cebc0cc8 402
3cd1b172 403ok 1, (scalar keys %$interesting_modules) . " distinctly versioned modules found";
cebc0cc8 404
405# do not announce anything under ci - we are watching for STDERR silence
3cd1b172 406exit 0 if DBICTest::RunMode->is_ci;
cebc0cc8 407
cebc0cc8 408
55c6fb91 409# diag the result out
410my $max_ver_len = max map
411 { length "$_" }
3cd1b172 412 ( 'xxx.yyyzzz_bbb', map { $_->{version} || '' } values %$interesting_modules )
55c6fb91 413;
c6b73be9 414my $max_marker_len = max map { length $_ } ( '$INC[999]', keys %$seen_markers );
55c6fb91 415
ba35e8ec 416# Note - must be less than 76 chars wide to account for the diag() prefix
55c6fb91 417my $discl = <<'EOD';
418
ba35e8ec 419List of loadable modules within both *OPTIONAL* and core dependency chains
65ea9cad 420present on this system (modules sourced from ./blib, ./lib, ./t, and ./xt
c6b73be9 421with versions identical to their parent namespace were omitted for brevity)
422
423 *** Note that *MANY* of these modules will *NEVER* be loaded ***
424 *** during normal operation of DBIx::Class ***
55c6fb91 425EOD
cebc0cc8 426
3cd1b172 427# pre-assemble everything and print it in one shot
428# makes it less likely for parallel test execution to insert bogus lines
429my $final_out = "\n$discl\n";
cebc0cc8 430
c6b73be9 431$final_out .= "\@INC at startup (does not reflect manipulation at runtime):\n";
cebc0cc8 432
c6b73be9 433my $in_inc_skip;
434for (0.. $#initial_INC) {
435
65ea9cad 436 my $shortname = shorten_fn( $initial_INC[$_] );
c6b73be9 437
65ea9cad 438 # when *to* print a line of INC
c6b73be9 439 if (
440 ! $ENV{AUTOMATED_TESTING}
441 or
442 @initial_INC < 11
443 or
444 $seen_markers->{"\$INC[$_]"}
445 or
65ea9cad 446 ! -e $shortname
c6b73be9 447 or
65ea9cad 448 ! File::Spec->file_name_is_absolute($shortname)
c6b73be9 449 ) {
450 $in_inc_skip = 0;
451 $final_out .= sprintf ( "% 3s: %s\n",
452 $_,
65ea9cad 453 $shortname
c6b73be9 454 );
455 }
456 elsif(! $in_inc_skip++) {
457 $final_out .= " ...\n";
458 }
459}
460
461$final_out .= "\n";
462
463if (my @seen_known_paths = grep { $known_paths->{$_} } keys %$seen_markers) {
3cd1b172 464
465 $final_out .= join "\n", 'Sourcing markers:', (map
466 {
467 sprintf "%*s: %s",
468 $max_marker_len => $_->{marker},
c6b73be9 469 ($_->{config_key} ? "\$Config{$_->{config_key}}" : "$_->{rel_path}/" )
3cd1b172 470 }
471 sort
55c6fb91 472 {
3cd1b172 473 !!$b->{config_key} cmp !!$a->{config_key}
474 or
475 ( $a->{marker}||'') cmp ($b->{marker}||'')
cebc0cc8 476 }
c6b73be9 477 @{$known_paths}{@seen_known_paths}
3cd1b172 478 ), '', '';
cebc0cc8 479
cebc0cc8 480}
481
3cd1b172 482$final_out .= "=============================\n";
cebc0cc8 483
3cd1b172 484$final_out .= join "\n", (map
485 { sprintf (
843b427a 486 "%*s %*s %*s%s",
3cd1b172 487 $max_marker_len => $interesting_modules->{$_}{source_marker} || '',
488 $max_ver_len => ( defined $interesting_modules->{$_}{version}
489 ? $interesting_modules->{$_}{version}
490 : ''
491 ),
c6b73be9 492 -78 => $_,
493 ($interesting_modules->{$_}{abs_unix_path}
494 ? " [ MD5: @{[ get_md5( $interesting_modules->{$_}{abs_unix_path} ) ]} ]"
495 : "! -f \$INC{'@{[ module_notional_filename($_) ]}'}"
3cd1b172 496 ),
497 ) }
498 sort { lc($a) cmp lc($b) } keys %$interesting_modules
499), '';
500
501$final_out .= "=============================\n$discl\n\n";
4bfba4ee 502
3cd1b172 503diag $final_out;
4bfba4ee 504
02154caf 505# *very* large printouts may not finish flushing before the test exits
506# injecting a <testname> ... ok in the middle of the diag
507# http://www.cpantesters.org/cpan/report/fbdac74c-35ca-11e6-ab41-c893a58a4b8c
508select( undef, undef, undef, 0.2 );
509
55c6fb91 510exit 0;
4bfba4ee 511
4bfba4ee 512
4bfba4ee 513
c6b73be9 514sub say_err { print STDERR "\n", @_, "\n\n" };
cebc0cc8 515
55c6fb91 516# do !!!NOT!!! use Module::Runtime's require_module - it breaks CORE::require
517sub try_module_require {
518 # trap deprecation warnings and whatnot
519 local $SIG{__WARN__} = sub {};
520 local $@;
521 eval "require $_[0]";
522}
cebc0cc8 523
c6b73be9 524sub abs_unix_path {
525 return '' unless (
526 defined $_[0]
527 and
528 ( -e $_[0] or File::Spec->file_name_is_absolute($_[0]) )
529 );
55c6fb91 530
3cd1b172 531 # File::Spec's rel2abs does not resolve symlinks
532 # we *need* to look at the filesystem to be sure
2c038b0a 533 #
534 # But looking at the FS for non-existing basenames *may*
535 # throw on some OSes so be extra paranoid:
536 # http://www.cpantesters.org/cpan/report/26a6e42f-6c23-1014-b7dd-5cd275d8a230
537 #
538 my $abs_fn = eval { abs_path($_[0]) } || '';
55c6fb91 539
2c038b0a 540 if ( $abs_fn and $^O eq 'MSWin32' ) {
55c6fb91 541
542 # sometimes we can get a short/longname mix, normalize everything to longnames
2c038b0a 543 $abs_fn = Win32::GetLongPathName($abs_fn)
544 if -e $abs_fn;
55c6fb91 545
546 # Fixup (native) slashes in Config not matching (unixy) slashes in INC
c6b73be9 547 $abs_fn =~ s|\\|/|g;
55c6fb91 548 }
cebc0cc8 549
c6b73be9 550 $abs_fn;
cebc0cc8 551}
552
55c6fb91 553sub shorten_fn {
554 my $fn = shift;
555
c6b73be9 556 my $abs_fn = abs_unix_path($fn);
55c6fb91 557
2c038b0a 558 if ($abs_fn and my $p = subpath_of_known_path( $fn ) ) {
c6b73be9 559 $abs_fn =~ s| (?<! / ) $|/|x
560 if -d $abs_fn;
561
562 if ($p->{rel_path}) {
563 $abs_fn =~ s!\Q$p->{abs_unix_path}!$p->{rel_path}!
564 and return $abs_fn;
565 }
566 elsif ($p->{config_key}) {
567 $abs_fn =~ s!\Q$p->{abs_unix_path}!<<$p->{marker}>>!
568 and
569 $seen_markers->{$p->{marker}} = 1
570 and
571 return $abs_fn;
572 }
55c6fb91 573 }
574
c6b73be9 575 # we got so far - not a known path
576 # return the unixified version it if was absolute, leave as-is otherwise
65ea9cad 577 my $rv = ( $abs_fn and File::Spec->file_name_is_absolute( $fn ) )
c6b73be9 578 ? $abs_fn
579 : $fn
580 ;
65ea9cad 581
582 $rv = "( ! -e ) $rv" unless -e $rv;
583
584 return $rv;
55c6fb91 585}
586
c6b73be9 587sub subpath_of_known_path {
588 my $abs_fn = abs_unix_path( $_[0] )
55c6fb91 589 or return '';
590
c6b73be9 591 for my $p (
592 sort {
593 length( $b->{abs_unix_path} ) <=> length( $a->{abs_unix_path} )
594 or
595 ( $a->{match_order} || 0 ) <=> ( $b->{match_order} || 0 )
596 }
597 values %$known_paths
55c6fb91 598 ) {
3cd1b172 599 # run through the matcher twice - first always append a /
600 # then try without
601 # important to avoid false positives
602 for my $suff ( '/', '' ) {
c6b73be9 603 return { %$p } if 0 == index( $abs_fn, "$p->{abs_unix_path}$suff" );
3cd1b172 604 }
55c6fb91 605 }
606}
607
3cd1b172 608sub module_found_at_inc_index {
c6b73be9 609 my ($mod, $inc_dirs) = @_;
610
611 return undef unless @$inc_dirs;
3cd1b172 612
613 my $fn = module_notional_filename($mod);
614
7dc14bc0 615 # trust INC if it specifies an existing path
616 if( -f ( my $existing_path = abs_unix_path( $INC{$fn} ) ) ) {
617 for my $i ( 0 .. $#$inc_dirs ) {
618
619 # searching from here on out won't mean anything
620 # FIXME - there is actually a way to interrogate this safely, but
621 # that's a fight for another day
622 return undef if length ref $inc_dirs->[$i];
623
624 return $i
625 if 0 == index( $existing_path, abs_unix_path( $inc_dirs->[$i] ) . '/' );
626 }
627 }
c6b73be9 628
7dc14bc0 629 for my $i ( 0 .. $#$inc_dirs ) {
3cd1b172 630
631 if (
c6b73be9 632 -d $inc_dirs->[$i]
3cd1b172 633 and
c6b73be9 634 -f "$inc_dirs->[$i]/$fn"
3cd1b172 635 and
c6b73be9 636 -r "$inc_dirs->[$i]/$fn"
3cd1b172 637 ) {
638 return $i;
639 }
640 }
641
642 return undef;
643}
644
645sub purge_identically_versioned_submodules_with_markers {
646 my $markers = shift;
647
648 return unless @$markers;
649
650 for my $mod ( sort { length($b) <=> length($a) } keys %$interesting_modules ) {
651
652 next unless defined $interesting_modules->{$mod}{version};
653
654 my $marker = $interesting_modules->{$mod}{source_marker}
655 or next;
656
657 next unless grep { $marker eq $_ } @$markers;
658
659 my $parent = $mod;
660
661 while ( $parent =~ s/ :: (?: . (?! :: ) )+ $ //x ) {
662 $interesting_modules->{$parent}
663 and
664 ($interesting_modules->{$parent}{version}||'') eq $interesting_modules->{$mod}{version}
665 and
666 ($interesting_modules->{$parent}{source_marker}||'') eq $interesting_modules->{$mod}{source_marker}
667 and
668 delete $interesting_modules->{$mod}
669 and
670 last
671 }
672 }
673}
674
675sub module_notional_filename {
676 (my $fn = $_[0] . '.pm') =~ s|::|/|g;
677 $fn;
678}
679
55c6fb91 680sub get_md5 {
681 # we already checked for -r/-f, just bail if can't open
682 open my $fh, '<:raw', $_[0] or return '';
55c6fb91 683 Digest::MD5->new->addfile($fh)->hexdigest;
684}