7 use Storable qw
/lock_store lock_retrieve/;
10 ####################################
12 $Data::Dumper
::Sortkeys
= 1;
14 my $additional_deps = {
15 'IO' => { 'ExtUtils::ParseXS' => '3.21' },
16 'Class::MethodMaker' => { 'Fatal' => '1' },
18 # override which dependencies should not be considered a core module
19 my $no_core_dependency = {
23 ####################################
24 # is this a core module?
26 my($module, $perl_version) = @_;
28 require Module
::CoreList
;
29 Module
::CoreList
->import();
32 my @v = split/\./, $Config{'version'};
33 my $v = $perl_version || $v[0] + $v[1]/1000;
35 return if defined $no_core_dependency->{$module};
36 if(exists $Module::CoreList
::version
{$v}{$module}) {
37 return($Module::CoreList
::version
{$v}{$module});
42 ####################################
46 print "[INFO] cmd: $cmd\n" if $verbose;
48 open(my $ph, '-|', $cmd." 2>&1") or die("cannot execute cmd: $cmd");
49 while(my $line = <$ph>) {
53 if($?
!= 0 and !defined $force) { my $rc = $?
>>8; die("cmd failed (rc:$rc): $cmd\n$out") };
57 ####################################
58 # get all dependencies for a tarball
59 # needs a filename like: Storable-2.21.tar.gz
61 my($file, $download, $quiet) = @_;
62 $quiet = 0 unless defined $quiet;
67 return if defined $already_checked{$file};
68 $already_checked{$file} = 1;
70 print "checking dependecies for: $file\n" unless $quiet;
71 print "." if $quiet == 1;
72 my $meta = get_meta_for_tarball
($file);
74 my $deps = get_deps_from_meta
($meta);
75 add_additional_deps
($file, $deps);
76 $deps_cache{$file} = $deps;
77 for my $dep (keys %{$deps}) {
78 my $depv = $deps->{$dep};
79 my $cv = is_core_module
($dep);
80 if($cv and version_compare
($cv, $depv)) {
81 print " -> $dep ($depv) skipped core dependency\n" unless $quiet;
84 print " -> $dep ($depv)\n" unless $quiet;
86 download_module
($dep, $depv);
92 ####################################
93 # download all dependencies for a tarball
94 # needs a filename like: Storable-2.21.tar.gz
97 return get_deps
($file, 1);
100 ####################################
102 # needs a module name like: IO::All
103 sub download_module
{
105 my $ver = shift || 0;
106 my $no_dep = shift || 0;
107 my $quiet = shift || 0;
109 print "[INFO] download_module($mod, $ver)\n" if $verbose;
111 our %already_downloaded;
114 return \
@downloaded if defined $already_downloaded{$mod.$ver};
115 $already_downloaded{$mod.$ver} = 1;
117 # we dont need core modules or perl dependency
118 if($mod eq 'perl') { return \
@downloaded; }
120 my $urlpath = get_url_for_module
($mod);
121 return \
@downloaded if defined $already_downloaded{$urlpath};
122 if($urlpath =~ m/\/perl\
-[\d\
.]+\
.tar\
.gz
/) {
123 $already_downloaded{$urlpath} = 1;
124 print " -> links to perl\n";
127 my $tarball=$urlpath; $tarball =~ s/^.*\///g
;
129 if( ! -f
$tarball and !defined $already_downloaded{$urlpath}) {
130 cmd
('wget --retry-connrefused -q "http://search.cpan.org'.$urlpath.'"');
131 $already_downloaded{$urlpath} = 1;
132 download_deps
($tarball) unless $no_dep == 1;
133 push @downloaded, $tarball;
134 print "downloaded $tarball\n" unless $quiet;
136 if(!defined $deps_checked{$tarball}) {
137 print "$tarball already downloaded\n";
138 #print "rechecking dependency\n";
139 #download_deps($tarball);
140 $deps_checked{$tarball} = 1;
142 print "$tarball already downloaded\n";
148 ####################################
151 cmd
('wget --retry-connrefused -q "http://thruk.org/libs/src/'.$module.'"');
155 ####################################
157 # needs a filename like: Storable-2.21.tar.gz
158 # returns module name and version
161 my($module,$version) = ($file, 0);
163 if($file =~ m/\-([0-9\.]*)(\.[\w\d]+)*(\.tar\.gz|\.tgz|\.zip)/) {
167 $module =~ s/\-[0-9\.\w]*(\.tar\.gz|\.tgz|\.zip)//g;
168 $module =~ s/\-/::/g;
169 $module = translate_module_name
($module);
171 return($module,$version);
174 ####################################
175 # return real module name
176 # TODO: get real name from http://search.cpan.org/search?mode=dist&query=<module>
177 sub translate_module_name
{
180 'Filter' => 'Filter::exec',
181 'IO::Compress' => 'IO::Compress::Base',
182 'IO::stringy' => 'IO::Scalar',
183 'Scalar::List::Utils' => 'List::Util::XS',
184 'libwww::perl' => 'LWP',
185 'Template::Toolkit' => 'Template',
186 'TermReadKey' => 'Term::ReadKey',
187 'Gearman' => 'Gearman::Client',
188 'PathTools' => 'File::Spec',
189 'libnet' => 'Net::Cmd',
190 'podlators' => 'Pod::Man',
191 'Text::Tabs+Wrap' => 'Text::Tabs',
193 $name =~ s/^inc::Module::Install.*?/Module::Install/g;
194 return $tr->{$name} if defined $tr->{$name};
198 ####################################
199 # return filename from list
201 my($mod, $files, $version) = @_;
202 $version = 0 unless defined $version;
203 if($version > 0 and defined $files->{$mod}) {
204 my($m,$v) = file_to_module
($files->{$mod});
205 return $files->{$mod} if version_compare
($v, $version);
207 return $files->{$mod} if defined $files->{$mod};
211 ####################################
219 if(-f
'.deps.cache') {
220 # this may fail due to mismatching hosts systems
222 $data = lock_retrieve
('.deps.cache') or die("cannot read .deps.cache: $!");
226 # remove all tarballs from cache which no longer exist
227 for my $tf (keys %{$data->{'deps'}}) {
229 delete $data->{'deps'}->{$tf};
230 for my $key (keys %{$data->{'files'}}) {
231 delete $data->{'files'}->{$key} if $data->{'files'}->{$key} eq $tf;
233 print " -> cleaned dependecies for: $tf because tarball does not exist anymore\n" unless $quiet;
239 chdir("src") or die("cannot change to src dir");
240 my @tarballs = glob("*.tgz *.tar.gz *.zip");
242 %deps_cache = %{$data->{'deps'}} if defined $data->{'deps'};
243 %deps_files = %{$data->{'files'}} if defined $data->{'files'};
244 my($x,$max) = (1, scalar @tarballs);
245 for my $tarball (sort @tarballs) {
246 if(!defined $deps_cache{$tarball}) {
247 printf("*** (%3s/%s) ", $x, $max) if($max > 1 and !$quiet);
248 get_deps
($tarball, undef, $quiet);
253 chdir($cwd) or die("cannot change dir back");
255 lock_store
({'files' => \
%deps_files, 'deps' => \
%deps_cache}, '.deps.cache');
257 return(\
%deps_cache, \
%deps_files);
260 ####################################
261 sub sort_by_dependency
{
266 # ExtUtils-MakeMaker has to go first
267 for my $m (sort keys %{$modules}) {
268 if($m =~ m/^ExtUtils\-MakeMaker\-\d+/) {
270 delete $modules->{$m};
274 my $num = scalar keys %{$modules};
276 for my $m (sort keys %{$modules}) {
277 for my $s (sort keys %{$modules->{$m}}) {
278 if(grep {/$s/} @sorted) {
279 delete $modules->{$m}->{$s};
282 if(scalar keys %{$modules->{$m}} == 0) {
284 delete $modules->{$m};
287 my $new = scalar keys %{$modules};
289 print Dumper
$modules;
290 die("circular dependency");
298 ####################################
299 # compare two version strings
300 # return 1 if $v1 >= $v2
301 sub version_compare
{
303 return 0 if !defined $v1 or $v1 eq 'undef';
304 return 1 if !defined $v2 or $v2 eq 'undef';
305 $v1 =~ s/^(\d+\.\d+).*$/$1/;
306 $v2 =~ s/^(\d+\.\d+).*$/$1/;
307 return 1 if $v1 >= $v2;
311 ####################################
316 my $already_printed = {};
318 # 1st clean up and resolve modules to files
320 for my $file (keys %{$deps}) {
321 $modules->{$file} = {};
322 for my $dep (keys %{$deps->{$file}}) {
323 next if $dep eq 'perl';
324 next if $dep eq 'strict';
325 next if $dep eq 'warnings';
326 next if $dep eq 'lib';
327 next if $dep eq 'blib';
328 next if $dep eq 'utf8';
330 next if $dep eq 'IPC::Open'; # core module but not recognized
331 my $cv = is_core_module
($dep, 5.008);
333 my $fdep = module_to_file
($dep, $files, $deps->{$file}->{$dep});
335 next if $fdep eq $file;
336 $modules->{$file}->{$fdep} = 1
338 if($dep !~ m/^Test::/ and $dep !~ m/^Devel::/) {
339 warn("cannot resolve dependency '$dep' to file, referenced by: $file\n") unless $already_printed->{$dep};
340 $already_printed->{$dep} = 1;
346 my @sorted = sort_by_dependency
($modules);
351 ####################################
353 sub get_url_for_module
{
356 $mod = translate_module_name
($mod);
357 return $url_cache{$mod} if exists $url_cache{$mod};
358 for my $url ('http://search.cpan.org/perldoc?'.$mod, 'http://search.cpan.org/dist/'.$mod) {
359 my $out = cmd
("wget --retry-connrefused -O - '".$url."'", 1);
360 if($out =~ m/href="(\/CPAN\
/authors\/id\
/.*?\/.*?
(\
.tar\
.gz
|\
.tgz
|\
.zip
))">/) {
361 $url_cache{$mod} = $1;
365 print "cannot find
$mod on cpan
\n";
369 ####################################
373 my $PERL = shift || '/usr/bin/perl';
374 my $verbose = shift || 0;
376 my $max = shift || 1;
378 die("error
: $file does
not exist
in ".`pwd`) unless -e $file;
379 die("error
: module name missing
") unless defined $file;
381 my $LOG = "install
.log";
382 printf("*** (%3s/%s) ", $x, $max);
383 printf("%-55s
", $file);
385 my($modname, $modvers) = file_to_module($file);
387 if( $modname eq "DBD
::Oracle
") {
388 if(defined $ENV{'ORACLE_HOME'} and -f $ENV{'ORACLE_HOME'}."/lib/libclntsh
.so
") {
389 $ENV{'LD_LIBRARY_PATH'} = $ENV{'ORACLE_HOME'}."/lib
";
390 $ENV{'PATH'} = $ENV{'PATH'}.":".$ENV{'ORACLE_HOME'}."/bin
";
398 my $core = is_core_module($modname);
399 if(version_compare($core, $modvers)) {
400 print "skipped core module
$core >= $modvers\n";
406 `grep $file $TARGET/modlist.txt 2>&1`;
407 $installed = 1 if $? == 0;
408 if( $installed and $modname ne 'parent' ) {
409 print "already installed
\n";
414 my $dir = _unpack($file);
418 print "installing
... ";
420 my $makefile_opts = '';
421 if($modname eq 'XML::LibXML') {
422 $makefile_opts = 'FORCE=1';
424 if($modname eq 'List::MoreUtils') {
425 system("sed
-i
-e
'/url\\s*=>.*github/d' -e
'/perl.*=>\\s*\\\$^V/d' Makefile
.PL
");
427 if($modname eq 'GD') {
428 system("sed
-i
's|-Wformat=0||g' Makefile
.PL
");
432 local $SIG{ALRM} = sub { die "timeout on
: $file\n" };
433 alarm(120); # single module should not take longer than 1 minute
434 if( -f "Build
.PL
" ) {
435 `$PERL Build.PL >> $LOG 2>&1 && $PERL ./Build >> $LOG 2>&1 && $PERL ./Build install >> $LOG 2>&1`;
436 if($? != 0 ) { die("error
: rc
$?
\n".`cat $LOG`."\n"); }
437 } elsif( -f "Makefile
.PL
" ) {
438 `sed -i -e 's/auto_install;//g' Makefile.PL`;
440 # retry because sometimes Makefile.PL will be rebuild due to broken time
441 for my $retry (1..3) {
442 `echo "\n\n\n" | $PERL Makefile.PL $makefile_opts >> $LOG 2>&1 && make -j 5 >> $LOG 2>&1 && make install >> $LOG 2>&1`;
446 if($rc != 0 ) { die("error
: rc
$rc\n".`cat $LOG`."\n"); }
448 die("error
: no Build
.PL
or Makefile
.PL found
in $file!\n");
450 `grep '^==> Auto-install the' $LOG >/dev/null 2>&1 | grep -v optional`;
451 if($? == 0 ) { die("dependency error
: rc
$?
\n".`cat $LOG`."\n"); }
457 `echo $file >> $TARGET/modlist.txt`;
461 my $duration = $end - $start;
462 print "ok
(".$duration."s
)\n";
463 my $grepv = "grep -v
'Test::' | grep -v
'Linux::Inotify2' | grep -v
'IO::KQueue' | grep -v
'prerequisite Carp' | grep -v ExtUtils
::Install
| grep -v
^Add
| grep -v
'We have '";
464 system("grep 'Warning: prerequisite' $LOG | $grepv"); # or die('dependency error');
465 system("grep 'is not installed' $LOG | grep ' ! ' | $grepv"); # or die('dependency error');
466 system("grep 'is installed, but we need version' $LOG | grep ' ! ' | $grepv"); # or die('dependency error');
467 system("grep 'is not a known MakeMaker parameter' $LOG | grep INSTALL_BASE
| $grepv") or die('build error');
470 chomp(my $pwd = `pwd`);
471 print "installation took too long
, see
$pwd/$dir/$LOG for details
\n";
476 # makes Module::Build 1000 times faster
477 if($modname eq 'JSON::XS') {
478 $ENV{'PERL_JSON_BACKEND'} = 'JSON::XS';
484 ####################################
485 # return meta content from unpacked package
486 sub get_meta_for_dir {
494 cmd("yes n
| perl Makefile
.PL
", 1) if -e 'Makefile.PL';
495 cmd("yes n
| perl Build
.PL
", 1) if -e 'Build.PL';
502 if(-s "$dir/MYMETA
.json
") {
505 $meta = JSON::from_json(`cat $dir/MYMETA.json | tr '\n' ' '`);
507 print Dumper $@ if $@;
509 elsif(-s "$dir/MYMETA
.yml
") {
511 $meta = YAML::LoadFile("$dir/META
.yml
");
513 print Dumper $@ if $@;
515 elsif(-s "$dir/META
.json
") {
518 $meta = JSON::from_json(`cat $dir/META.json | tr '\n' ' '`);
520 print Dumper $@ if $@;
522 elsif(-s "$dir/META
.yml
") {
525 $meta = YAML::LoadFile("$dir/META
.yml
");
527 print Dumper $@ if $@;
529 $meta->{requires} = {} unless defined $meta->{requires};
530 if(-s "$dir/Makefile
.PL
") {
531 my $content = `cat $dir/Makefile.PL`;
532 if($content =~ m/WriteMakefile\s*\(/) {
533 if($content =~ m/'PREREQ_PM'\s*=>\s*\{(.*?)}/) {
535 $mod_str =~ s/\n/ /g;
536 my %modules = $mod_str =~ m/\'(.*?)'\s*=>\s*\'(.*?)\'/;
537 %{$meta->{requires}} = (%{$meta->{requires}}, %modules);
540 if($content =~ m/^\s*requires\s+/m) {
541 my %modules = $content =~ m/^\s*requires\s+(.*?)\s*=>\s*(.*?);/gm;
542 %{$meta->{requires}} = (%{$meta->{requires}}, %modules);
544 if($content =~ m/^\s*use\s+[a-zA-Z:]+\s*/m) {
545 my %modules = $content =~ m/^\s*use\s+([a-zA-Z:]+)\s*([\d\.]+)/gm;
546 delete $modules{'inc::Module::Install'};
547 delete $modules{'inc::Module::Install::DSL'};
548 %{$meta->{requires}} = (%{$meta->{requires}}, %modules);
552 # add deps from the Makefile
553 if(-s "$dir/Makefile
") {
554 my $prereq = `grep PREREQ_PM $dir/Makefile`;
556 $prereq =~ s/^#\s+PREREQ_PM\s*=>\s*({.*}).*$/\$req = $1;/g;
557 $prereq =~ s/([\w:]+)=/'$1'=/g;
561 for my $mod (keys %{$req}) {
562 $meta->{requires}->{$mod} = $req->{$mod};
567 $meta->{requires} = {} unless defined $meta->{requires};
568 $meta->{build_requires} = {} unless defined $meta->{build_requires};
569 $meta->{configure_requires} = {} unless defined $meta->{configure_requires};
570 $meta->{prereqs}->{'build'}->{'requires'} = {} unless defined $meta->{prereqs}->{'build'}->{'requires'};
571 $meta->{prereqs}->{'configure'}->{'requires'} = {} unless defined $meta->{prereqs}->{'configure'}->{'requires'};
572 $meta->{prereqs}->{'runtime'}->{'requires'} = {} unless defined $meta->{prereqs}->{'runtime'}->{'requires'};
574 $meta->{requires}->{'Module::Build'} = 1 if -s $dir.'/Build.PL';
576 if(!defined $meta->{name}) {
577 $meta->{name} = $dir;
578 $meta->{name} =~ s/^(.*)\-.*?$/$1/;
579 $meta->{name} =~ s/\-/::/g;
585 ####################################
586 # return dependencies from meta data
587 sub get_deps_from_meta {
588 my($meta, $all) = @_;
589 my %deps = (%{$meta->{requires}},
590 %{$meta->{build_requires}},
591 %{$meta->{configure_requires}},
592 %{$meta->{prereqs}->{'build'}->{'requires'}},
593 %{$meta->{prereqs}->{'configure'}->{'requires'}},
594 %{$meta->{prereqs}->{'runtime'}->{'requires'}},
596 my $stripped_deps = {};
597 for my $dep (keys %deps) {
598 my $val = $deps{$dep};
599 $dep =~ s/('|")//gmx;
600 $val =~ s/('|")//gmx;
601 next if $val =~ m/win32/;
602 next if $dep eq 'perl';
603 next if $dep eq 'warnings';
604 next if $dep eq 'strict';
605 next if $dep eq 'lib';
608 next if $dep eq 'IPC::Open'; # core module but not recognized
609 next if $dep =~ m/^Test::/;
610 next if $dep eq 'Test';
612 $stripped_deps->{$dep} = $val;
614 return $stripped_deps;
617 ####################################
618 # add additional dependencies
619 sub add_additional_deps {
620 my($file, $deps) = @_;
621 my($modname, $modvers) = file_to_module($file);
622 if($additional_deps->{$modname}) {
623 for my $key (keys %{$additional_deps->{$modname}}) {
624 $deps->{$key} = $additional_deps->{$modname}->{$key};
630 ####################################
631 # return dependencies from meta data
634 if($file =~ m/\.zip$/gmx) {
637 cmd("tar zxf
$file");
640 $dir =~ s/(\.tar\.gz|\.tgz|\.zip)//g;
646 ####################################
647 # find orphaned packages
649 my($deps, $files, $verbose) = @_;
651 for my $file (keys %{$deps}) {
652 # verify this module is used somewhere
654 for my $file2 (keys %{$deps}) {
655 next if $file eq $file2;
656 for my $dep2 (keys %{$deps->{$file2}}) {
657 next if $dep2 eq 'perl';
658 next if $dep2 eq 'strict';
659 next if $dep2 eq 'warnings';
660 next if $dep2 eq 'lib';
661 next if $dep2 eq 'v';
662 my $fdep2 = module_to_file($dep2, $files, $deps->{$file2}->{$dep2});
664 $found = 1 if $fdep2 eq $file;
665 if($found && $verbose) { print "$file is used by
$file2\n"; }
670 $orphaned->{$file} = 1 unless $found;
675 ####################################
679 if($in =~ m/Makefile\.PL$/mx) {
681 $dir =~ s/Makefile\.PL$//gmx;
682 $meta = get_meta_for_dir($dir);
684 elsif($in =~ m/(\.tar.gz|\.tgz|\.zip)$/mx) {
685 $meta = get_meta_for_tarball($in);
688 die("unsupported file
: ".$in);
693 ####################################
694 sub get_meta_for_tarball {
699 my $dir = _unpack($file);
700 my $meta = get_meta_for_dir($dir);
702 for my $f (split/\n/,`find -name \*.pm; find -name \*.PL`) {
703 next if $f =~ m|/inc/|mxo; # skip inc modules included by Module::Install packaged modules
704 open(my $fh, '<', $f);
705 while(my $line = <$fh>) {
706 if($line =~ m/^package\s+(.*?)(\s|;|#)/) {
707 $deps_files{$1} = $file;
710 if($f =~ m/\.pm$/mx) {
715 $deps_files{$f} = $file;