#!/usr/bin/perl
no
warnings;
use
subs
qw(get_caller_info)
;
use
vars
qw($VERSION $logger)
;
$VERSION
=
'1.17_08'
;
BEGIN {
$logger
= Log::Log4perl->get_logger(
'Indexer'
);
}
__PACKAGE__->run(
@ARGV
)
unless
caller
;
sub
run
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$class
,
@args
) =
@_
;
my
$self
=
$class
->new;
$self
->setup_run_info;
DIST:
foreach
my
$dist
(
@args
)
{
$logger
->debug(
"Dist is $dist\n"
);
unless
( -e
$dist
)
{
$logger
->error(
"Could not find [$dist]"
);
next
;
}
$logger
->info(
"Processing $dist\n"
);
$self
->clear_dist_info;
$self
->setup_dist_info(
$dist
) or
next
DIST;
$self
->examine_dist or
next
DIST;
$self
->set_run_info(
'completed'
, 1 );
$self
->set_run_info(
'run_end_time'
,
time
);
$logger
->info(
"Finished processing $dist"
);
$logger
->debug(
sub
{ Dumper(
$self
) } );
}
$self
;
}
sub
new {
bless
{},
$_
[0] }
sub
examine_dist_steps
{
my
@methods
= (
[
'unpack_dist'
,
"Could not unpack distribtion!"
, 1 ],
[
'find_dist_dir'
,
"Did not find distro directory!"
, 1 ],
[
'get_file_list'
,
'Could not get file list'
, 1 ],
[
'parse_meta_files'
,
"Could not parse META.yml!"
, 0 ],
[
'find_modules'
,
"Could not find modules!"
, 1 ],
[
'find_tests'
,
"Could not find tests!"
, 0 ],
);
}
sub
examine_dist
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
) =
@_
;
$self
->set_run_info(
'examine_start_time'
,
time
);
foreach
my
$tuple
(
$self
->examine_dist_steps )
{
my
(
$method
,
$error_msg
,
$die_on_error
) =
@$tuple
;
unless
(
$self
->
$method
() )
{
$logger
->error(
$error_msg
);
$self
->set_run_info(
'fatal_error'
,
$error_msg
);
if
(
$die_on_error
)
{
$logger
->error(
"Fatal error, stopping: $error_msg"
);
return
;
}
}
}
{
my
@file_info
= ();
foreach
my
$file
( @{
$self
->dist_info(
'modules'
) } )
{
$logger
->debug(
"Processing module $file"
);
my
$hash
=
$self
->get_module_info(
$file
);
push
@file_info
,
$hash
;
}
$self
->set_dist_info(
'module_info'
, [
@file_info
] );
}
{
my
@file_info
= ();
foreach
my
$file
( @{
$self
->dist_info(
'tests'
) || [] } )
{
$logger
->debug(
"Processing test $file"
);
my
$hash
=
$self
->get_test_info(
$file
);
push
@file_info
,
$hash
;
}
$self
->set_dist_info(
'test_info'
, [
@file_info
] );
}
$self
->set_run_info(
'examine_end_time'
,
time
);
$self
->set_run_info(
'examine_time'
,
$self
->run_info(
'examine_end_time'
) -
$self
->run_info(
'examine_start_time'
)
);
return
1;
}
sub
clear_run_info
{
$logger
->trace(
sub
{ get_caller_info } );
$logger
->debug(
"Clearing run_info\n"
);
$_
[0]->{run_info} = {};
}
sub
setup_run_info
{
$logger
->trace(
sub
{ get_caller_info } );
my
$perl
= Probe::Perl->new;
$_
[0]->set_run_info(
'root_working_dir'
, cwd() );
$_
[0]->set_run_info(
'run_start_time'
,
time
);
$_
[0]->set_run_info(
'completed'
, 0 );
$_
[0]->set_run_info(
'pid'
, $$ );
$_
[0]->set_run_info(
'ppid'
,
$_
[0]->
getppid
);
$_
[0]->set_run_info(
'indexer'
,
ref
$_
[0] );
$_
[0]->set_run_info(
'indexer_versions'
,
$_
[0]->VERSION );
$_
[0]->set_run_info(
'perl_version'
,
$perl
->perl_version );
$_
[0]->set_run_info(
'perl_path'
,
$perl
->find_perl_interpreter );
$_
[0]->set_run_info(
'perl_config'
, \
%Config::Config
);
$_
[0]->set_run_info(
'operating_system'
, $^O );
$_
[0]->set_run_info(
'operating_system_type'
,
$perl
->os_type );
return
1;
}
sub
set_run_info
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$key
,
$value
) =
@_
;
$logger
->debug(
"Setting run_info key [$key] to [$value]\n"
);
$self
->{run_info}{
$key
} =
$value
;
}
sub
run_info
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$key
) =
@_
;
$logger
->debug(
"Run info for $key is "
.
$self
->{run_info}{
$key
} );
$self
->{run_info}{
$key
};
}
sub
clear_dist_info
{
$logger
->trace(
sub
{ get_caller_info } );
$logger
->debug(
"Clearing dist_info\n"
);
$_
[0]->{dist_info} = {};
}
sub
setup_dist_info
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$dist
) =
@_
;
$logger
->debug(
"Setting dist [$dist]\n"
);
$self
->set_dist_info(
'dist_file'
,
$dist
);
$self
->set_dist_info(
'dist_size'
, -s
$dist
);
$self
->set_dist_info(
'dist_basename'
, basename(
$dist
) );
$self
->set_dist_info(
'dist_date'
, (
stat
(
$dist
))[9] );
$self
->set_dist_info(
'dist_md5'
,
$self
->get_md5(
$dist
) );
$logger
->debug(
"dist size "
.
$self
->dist_info(
'dist_size'
) .
" dist date "
.
$self
->dist_info(
'dist_date'
)
);
my
(
undef
,
undef
,
$author
) =
$dist
=~ m|/([A-Z])/\1([A-Z])/(\1\2[A-Z]+)/|;
$self
->set_dist_info(
'dist_author'
,
$author
);
$logger
->debug(
"dist author [$author]"
);
unless
(
$self
->dist_info(
'dist_size'
) )
{
$logger
->error(
"Dist size was 0!"
);
$self
->set_run_info(
'fatal_error'
,
"Dist size was 0!"
);
return
;
}
return
1;
}
sub
set_dist_info
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$key
,
$value
) =
@_
;
$logger
->debug(
"Setting dist_info key [$key] to [$value]\n"
);
$self
->{dist_info}{
$key
} =
$value
;
}
sub
dist_info
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$key
) =
@_
;
$logger
->debug(
"dist info for $key is "
.
$self
->{dist_info}{
$key
} );
$self
->{dist_info}{
$key
};
}
sub
unpack_dist
{
$logger
->trace(
sub
{ get_caller_info } );
local
$Archive::Extract::WARN
= 0;
local
$Archive::Tar::WARN
=
$Archive::Extract::WARN
;
my
$self
=
shift
;
my
$dist
=
$self
->dist_info(
'dist_file'
);
$logger
->debug(
"Unpacking dist $dist"
);
return
unless
$self
->get_unpack_dir;
my
$extractor
=
eval
{
Archive::Extract->new(
archive
=>
$dist
);
};
local
$Archive::Tar::WARN
= 0;
if
(
$extractor
->type eq
'gz'
)
{
$logger
->error(
"Dist $dist claims to be a gz, so try .tgz instead"
);
$extractor
=
eval
{
Archive::Extract->new(
archive
=>
$dist
,
type
=>
'tgz'
)
};
}
unless
(
$extractor
)
{
$logger
->error(
"Could create Archive::Extract object for $dist [$@]"
);
$self
->set_dist_info(
'dist_archive_type'
,
'unknown'
);
return
;
}
$self
->set_dist_info(
'dist_archive_type'
,
$extractor
->type );
my
$rc
=
$extractor
->extract(
to
=>
$self
->dist_info(
'unpack_dir'
) );
$logger
->debug(
"Archive::Extract returns [$rc] for $dist"
);
unless
(
$rc
)
{
$logger
->error(
"Archive::Extract could not extract $dist: "
.
$extractor
->error(0) );
$self
->set_dist_info(
'extraction_error'
,
$extractor
->error(0) );
}
$self
->set_dist_info(
'dist_extract_path'
,
$extractor
->extract_path );
1;
}
sub
get_unpack_dir
{
$logger
->trace(
sub
{ get_caller_info } );
my
$self
=
shift
;
(
my
$prefix
= __PACKAGE__ ) =~ s/::/-/g;
$logger
->debug(
"Preparing temp dir\n"
);
my
$unpack_dir
=
eval
{ File::Temp::tempdir(
$prefix
.
"-$$.XXXX"
,
DIR
=>
$self
->run_info(
'root_working_dir'
),
CLEANUP
=> 1,
) };
if
( $@ )
{
$logger
->error(
"Temp dir error: $@"
);
return
;
}
$self
->set_dist_info(
'unpack_dir'
,
$unpack_dir
);
$logger
->debug(
"Unpacking into directory [$unpack_dir]"
);
1;
}
sub
find_dist_dir
{
$logger
->trace(
sub
{ get_caller_info } );
$logger
->debug(
"Cwd is "
.
$_
[0]->dist_info(
"unpack_dir"
) );
my
@files
=
qw( MANIFEST Makefile.PL Build.PL META.yml )
;
if
(
grep
{ -e }
@files
)
{
$_
[0]->set_dist_info(
$_
[0]->dist_info(
"unpack_dir"
) );
return
1;
}
$logger
->debug(
"Did not find dist directory at top level"
);
my
(
$wanted
,
$reporter
) =
File::Find::Closures::find_by_directory_contains(
@files
);
File::Find::find(
$wanted
,
$_
[0]->dist_info(
"unpack_dir"
) );
my
@found
=
sort
{
length
$a
<=>
length
$b
}
$reporter
->();
$logger
->debug(
"Found files @found"
);
$logger
->debug(
"Found dist file at $found[0]"
);
unless
(
$found
[0] )
{
$logger
->debug(
"Didn't find anything that looks like a module directory!"
);
return
;
}
if
(
chdir
$found
[0] )
{
$logger
->debug(
"Changed to $found[0]"
);
$_
[0]->set_dist_info(
'dist_dir'
,
$found
[0] );
return
1;
}
exit
;
return
;
}
sub
get_file_list
{
$logger
->trace(
sub
{ get_caller_info } );
$logger
->debug(
"Cwd is "
. cwd() );
unless
( -e
'Makefile.PL'
or -e
'Build.PL'
)
{
$logger
->error(
"No Makefile.PL or Build.PL"
);
$_
[0]->set_dist_info(
'manifest'
, [] );
return
;
}
my
$manifest
= [
sort
keys
%{ ExtUtils::Manifest::manifind() } ];
$logger
->debug(
"manifest is [ "
,
join
(
"|"
,
@$manifest
),
" ]"
);
$_
[0]->set_dist_info(
'manifest'
, [
@$manifest
] );
my
@file_info
=
map
{
$logger
->debug(
"Getting file info for $_"
);
$_
[0]->get_file_info(
$_
)
}
@$manifest
;
$_
[0]->set_dist_info(
'manifest_file_info'
, [
@file_info
] );
$manifest
;
}
sub
get_file_info
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$file
) =
@_
;
my
$hash
= {
name
=>
$file
};
$hash
->{md5} =
$self
->get_md5(
$file
);
$hash
->{mtime} = (
stat
$file
)[9];
$hash
->{bytesize} = -s _;
$hash
->{file_mime_type} =
$self
->file_magic(
$file
);
$hash
->{line_count} =
$self
->count_lines(
$file
);
$hash
;
}
sub
get_blib_file_list
{
$logger
->trace(
sub
{ get_caller_info } );
unless
( -d
'blib/lib'
)
{
$logger
->error(
"No blib/lib found!"
);
$_
[0]->set_dist_info(
'blib'
, [] );
return
;
}
my
$blib
= [
grep
{ m|^blib/| and ! m|.
exists
$| }
sort
keys
%{ ExtUtils::Manifest::manifind() } ];
$_
[0]->set_dist_info(
'blib'
,
$blib
);
}
sub
look_in_lib
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$wanted
,
$reporter
) = File::Find::Closures::find_by_regex(
qr/\.pm\z/
);
File::Find::find(
$wanted
,
'lib'
);
my
@modules
=
$reporter
->();
unless
(
@modules
)
{
$logger
->debug(
"Did not find any modules in lib"
);
return
;
}
$_
[0]->set_dist_info(
'modules'
, [
@modules
] );
return
1;
}
sub
look_in_cwd
{
$logger
->trace(
sub
{ get_caller_info } );
my
@modules
=
glob
(
"*.pm"
);
unless
(
@modules
)
{
$logger
->debug(
"Did not find any modules in cwd"
);
return
;
}
$_
[0]->set_dist_info(
'modules'
, [
@modules
] );
return
1;
}
sub
look_in_meta_yml_provides
{
$logger
->trace(
sub
{ get_caller_info } );
unless
( -e
'META.yml'
)
{
$logger
->debug(
"Did not find a META.yml, so can't check provides"
);
return
;
}
my
$yaml
= YAML::LoadFile(
'META.yml'
);
unless
(
exists
$yaml
->{provides} )
{
$logger
->debug(
"Did not find a provides in META.yml"
);
return
;
}
my
$provides
=
$yaml
->{provides};
my
@modules
= ();
foreach
my
$key
(
keys
%$provides
)
{
my
(
$namespace
,
$file
,
$version
) =
(
$key
, @{
$provides
->{
$key
}}{
qw(file version)
} );
push
@modules
,
$file
;
}
$_
[0]->set_dist_info(
'modules'
, [
@modules
] );
return
1;
}
sub
look_for_pm
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$wanted
,
$reporter
) = File::Find::Closures::find_by_regex(
qr/\.pm\z/
);
File::Find::find(
$wanted
, cwd() );
my
@modules
=
$reporter
->();
unless
(
@modules
)
{
$logger
->debug(
"Did not find any modules in lib"
);
return
;
}
$_
[0]->set_dist_info(
'modules'
, [
@modules
] );
return
1;
}
sub
parse_meta_files
{
$logger
->trace(
sub
{ get_caller_info } );
if
( -e
'META.yml'
)
{
my
$yaml
= YAML::Syck::LoadFile(
'META.yml'
);
$_
[0]->set_dist_info(
'META.yml'
,
$yaml
);
return
$yaml
;
}
return
;
}
sub
find_module_techniques
{
my
@methods
= (
[
'run_build_file'
,
"Got from running build file"
],
[
'look_in_lib'
,
"Guessed from looking in lib/"
],
[
'look_in_cwd'
,
"Guessed from looking in cwd"
],
[
'look_in_meta_yml_provides'
,
"Guessed from looking in META.yml"
],
[
'look_for_pm'
,
"Guessed from looking in cwd"
],
);
}
sub
find_modules
{
$logger
->trace(
sub
{ get_caller_info } );
my
@methods
=
$_
[0]->find_module_techniques;
foreach
my
$tuple
(
@methods
)
{
my
(
$method
,
$message
) =
@$tuple
;
next
unless
$_
[0]->
$method
();
$logger
->debug(
$message
);
return
1;
}
return
;
}
sub
find_tests
{
$logger
->trace(
sub
{ get_caller_info } );
my
@tests
;
push
@tests
,
'test.pl'
if
-e
'test.pl'
;
my
(
$wanted
,
$reporter
) = File::Find::Closures::find_by_regex(
qr/\.t$/
);
File::Find::find(
$wanted
,
"t"
);
push
@tests
,
$reporter
->();
$logger
->debug(
"Found tests [@tests]"
);
$_
[0]->set_dist_info(
'tests'
, [
@tests
] );
return
scalar
@tests
;
}
sub
run_build_file
{
$logger
->trace(
sub
{ get_caller_info } );
foreach
my
$method
(
qw(
choose_build_file setup_build run_build get_blib_file_list )
)
{
$_
[0]->
$method
() or
return
;
}
my
@modules
=
grep
/\.pm$/, @{
$_
[0]->dist_info(
'blib'
) };
$logger
->debug(
"Modules are @modules\n"
);
$_
[0]->set_dist_info(
'modules'
, [
@modules
] );
return
1;
}
sub
choose_build_file
{
$logger
->trace(
sub
{ get_caller_info } );
my
$guesser
= Distribution::Guess::BuildSystem->new(
dist_dir
=>
$_
[0]->dist_info(
'dist_dir'
)
);
$_
[0]->set_dist_info(
'build_system_guess'
,
$guesser
->just_give_me_a_hash
);
my
$file
=
eval
{
$guesser
->preferred_build_file };
$logger
->debug(
"Build file is $file"
);
$logger
->debug(
"At is $@"
)
if
$@;
unless
(
defined
$file
)
{
$logger
->error(
"Did not find a build file"
);
return
;
}
$_
[0]->set_dist_info(
'build_file'
,
$file
);
return
1;
}
sub
setup_build
{
$logger
->trace(
sub
{ get_caller_info } );
my
$file
=
$_
[0]->dist_info(
'build_file'
);
my
$command
=
"$^X $file"
;
$_
[0]->run_something(
$command
,
'build_file_output'
);
}
sub
run_build
{
$logger
->trace(
sub
{ get_caller_info } );
my
$file
=
$_
[0]->dist_info(
'build_file'
);
my
$command
=
$file
eq
'Build.PL'
?
"$^X ./Build"
:
"make"
;
$_
[0]->run_something(
$command
,
'build_output'
);
}
sub
run_something
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$command
,
$info_key
) =
@_
;
{
$logger
->debug(
"Running $command"
);
my
$pid
= IPC::Open2::open2(
my
$read
,
my
$write
,
"$command 2>&1 < /dev/null"
);
close
$write
;
{
local
$/;
my
$output
= <
$read
>;
$self
->set_dist_info(
$info_key
,
$output
);
}
waitpid
$pid
, 0;
}
}
sub
get_module_info_tasks
{
(
[
'extract_module_namespaces'
,
'Extract the namespaces a file declares'
],
[
'extract_module_version'
,
'Extract the version of the module'
],
[
'extract_module_dependencies'
,
'Extract module dependencies'
],
)
}
sub
get_module_info
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$file
) =
@_
;
my
$hash
=
$self
->get_file_info(
$file
);
$logger
->debug(
"get_module_info called with [$file]\n"
);
my
@tasks
=
$self
->get_module_info_tasks;
foreach
my
$task
(
@tasks
)
{
my
(
$method
,
$description
) =
@$task
;
$logger
->debug(
"get_module_info calling [$method]\n"
);
$self
->
$method
(
$file
,
$hash
);
}
$hash
;
}
sub
extract_module_namespaces
{
my
(
$self
,
$file
,
$hash
) =
@_
;
my
@packages
= Module::Extract::Namespaces->from_file(
$file
);
$logger
->
warn
(
"Didn't find any packages in $file"
)
unless
@packages
;
$hash
->{packages} = [
@packages
];
$hash
->{primary_package} =
$packages
[0];
1;
}
sub
extract_module_version
{
my
(
$self
,
$file
,
$hash
) =
@_
;
$hash
->{version} = Module::Extract::VERSION->parse_version_safely(
$file
);
1;
}
sub
extract_module_dependencies
{
my
(
$self
,
$file
,
$hash
) =
@_
;
my
$use_extractor
= Module::Extract::Use->new;
my
@uses
=
$use_extractor
->get_modules(
$file
);
if
(
$use_extractor
->error )
{
$logger
->error(
"Could not extract uses for [$file]: "
.
$use_extractor
->error );
}
$hash
->{uses} = [
@uses
];
1;
}
sub
get_test_info
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$file
) =
@_
;
my
$hash
=
$self
->get_file_info(
$file
);
my
$extractor
= Module::Extract::Use->new;
my
@uses
=
$extractor
->get_modules(
$file
);
$hash
->{uses} = [
@uses
];
$hash
;
}
sub
count_lines
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$file
) =
@_
;
my
$class
=
'SourceCode::LineCounter::Perl'
;
eval
{
eval
"require $class"
} or
return
;
$self
->set_run_info(
'line_counter_class'
,
$class
);
$self
->set_run_info(
'line_counter_version'
,
$class
->VERSION );
$logger
->debug(
"Counting lines in $file"
);
$logger
->error(
"File [$file] does not exist"
)
unless
-e
$file
;
my
$counter
=
$class
->new;
$counter
->count(
$file
);
my
$hash
= {
map
{
$_
=>
$counter
->
$_
() }
qw( total code comment documentation blank )
};
return
$hash
;
}
sub
file_magic
{
$logger
->trace(
sub
{ get_caller_info } );
my
(
$self
,
$file
) =
@_
;
my
$class
=
"File::MMagic"
;
eval
{
eval
"require $class"
} or
return
;
$self
->set_run_info(
'file_magic_class'
,
$class
);
$self
->set_run_info(
'file_magic_version'
,
$class
->VERSION );
$class
->new->checktype_filename(
$file
);
}
sub
cleanup
{
$logger
->trace(
sub
{ get_caller_info } );
return
1;
File::Path::rmtree(
[
$_
[0]->run_info(
'unpack_dir'
)
],
0, 0
);
return
1;
}
sub
report_dist_info
{
$logger
->trace(
sub
{ get_caller_info } );
no
warnings
'uninitialized'
;
my
$module_hash
=
$_
[0]->dist_info(
'module_versions'
);
while
(
my
(
$k
,
$v
) =
each
%$module_hash
)
{
print
"$k => $v\n\t"
;
}
print
"\n"
;
}
sub
get_caller_info
{
my
(
$package
,
$filename
,
$line
,
$subroutine
,
$hasargs
,
$wantarray
,
$evaltext
,
$is_require
,
$hints
,
$bitmask
) =
caller
(4);
$filename
= File::Basename::basename(
$filename
);
return
join
" : "
,
$package
,
$filename
,
$line
,
$subroutine
;
}
sub
get_md5
{
my
$context
= MD5->new;
$context
->add(
$_
[1] );
$context
->hexdigest;
}
sub
getppid
{
unless
( $^O =~ /Win32/ ) {
return
CORE::
getppid
}
-1;
}
1;