Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into FREESIDE...
[freeside.git] / rt / sbin / rt-test-dependencies.in
1 #!@PERL@
2 # BEGIN BPS TAGGED BLOCK {{{
3 #
4 # COPYRIGHT:
5 #
6 # This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
7 #                                          <sales@bestpractical.com>
8 #
9 # (Except where explicitly superseded by other copyright notices)
10 #
11 #
12 # LICENSE:
13 #
14 # This work is made available to you under the terms of Version 2 of
15 # the GNU General Public License. A copy of that license should have
16 # been provided with this software, but in any event can be snarfed
17 # from www.gnu.org.
18 #
19 # This work is distributed in the hope that it will be useful, but
20 # WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22 # General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301 or visit their web page on the internet at
28 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
29 #
30 #
31 # CONTRIBUTION SUBMISSION POLICY:
32 #
33 # (The following paragraph is not intended to limit the rights granted
34 # to you to modify and distribute this software under the terms of
35 # the GNU General Public License and is only of importance to you if
36 # you choose to contribute your changes and enhancements to the
37 # community by submitting them to Best Practical Solutions, LLC.)
38 #
39 # By intentionally submitting any modifications, corrections or
40 # derivatives to this work, or any other work intended for use with
41 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
42 # you are the copyright holder for those contributions and you grant
43 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
44 # royalty-free, perpetual, license to use, copy, create derivative
45 # works based on those contributions, and sublicense and distribute
46 # those contributions and any derivatives thereof.
47 #
48 # END BPS TAGGED BLOCK }}}
49 #
50 # This is just a basic script that checks to make sure that all
51 # the modules needed by RT before you can install it.
52 #
53
54 use strict;
55 use warnings;
56 no warnings qw(numeric redefine);
57 use Getopt::Long;
58 use Cwd qw(abs_path);
59 my %args;
60 my %deps;
61 my @orig_argv = @ARGV;
62 # Save our path because installers or tests can change cwd
63 my $script_path = abs_path($0);
64
65 GetOptions(
66     \%args,                               'v|verbose',
67     'install!',                           'with-MYSQL',
68     'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE',
69     'with-ORACLE',                        'with-FASTCGI',
70     'with-MODPERL1',                      'with-MODPERL2',
71     'with-STANDALONE',
72
73     'with-DEV',
74
75     'with-GPG',
76     'with-ICAL',
77     'with-SMTP',
78     'with-GRAPHVIZ',
79     'with-GD',
80     'with-DASHBOARDS',
81     'with-USERLOGO',
82     'with-SSL-MAILGATE',
83     'with-HTML-DOC',
84
85     'download=s',
86     'repository=s',
87     'list-deps',
88     'help|h',
89 );
90
91 if ( $args{help} ) {
92     require Pod::Usage;
93     Pod::Usage::pod2usage( { verbose => 2 } );
94     exit;
95 }
96
97 # Set up defaults
98 my %default = (
99     'with-MASON' => 1,
100     'with-PSGI' => 0,
101     'with-CORE' => 1,
102     'with-CLI' => 1,
103     'with-MAILGATE' => 1, 
104     'with-DEV' => @RT_DEVEL_MODE@, 
105     'with-GPG' => @RT_GPG@,
106     'with-ICAL' => 1,
107     'with-SMTP' => 1,
108     'with-GRAPHVIZ' => @RT_GRAPHVIZ@,
109     'with-GD' => @RT_GD@,
110     'with-DASHBOARDS' => 1,
111     'with-USERLOGO' => 1,
112     'with-SSL-MAILGATE' => @RT_SSL_MAILGATE@,
113     'with-HTML-DOC' => @RT_DEVEL_MODE@,
114 );
115 $args{$_} = $default{$_} foreach grep !exists $args{$_}, keys %default;
116
117 {
118   my $section;
119   my %always_show_sections = (
120     perl => 1,
121     users => 1,
122   );
123
124   sub section {
125     my $s = shift;
126     $section = $s;
127     print "$s:\n" unless $args{'list-deps'};
128   }
129
130   sub print_found {
131     my $msg = shift;
132     my $test = shift;
133     my $extra = shift;
134
135     unless ( $args{'list-deps'} ) {
136         if ( $args{'v'} or not $test or $always_show_sections{$section} ) {
137             print "\t$msg ...";
138             print $test ? "found" : "MISSING";
139             print "\n";
140         }
141
142         print "\t\t$extra\n" if defined $extra;
143     }
144   }
145 }
146
147 sub conclude {
148     my %missing_by_type = @_;
149
150     unless ( $args{'list-deps'} ) {
151         unless ( keys %missing_by_type ) {
152             print "\nAll dependencies have been found.\n";
153             return;
154         }
155
156         print "\nSOME DEPENDENCIES WERE MISSING.\n";
157
158         for my $type ( keys %missing_by_type ) {
159             my $missing = $missing_by_type{$type};
160
161             print "$type missing dependencies:\n";
162             for my $name ( keys %$missing ) {
163                 my $module  = $missing->{$name};
164                 my $version = $module->{version};
165                 my $error = $module->{error};
166                 print_found( $name . ( $version && !$error ? " >= $version" : "" ),
167                     0, $module->{error} );
168             }
169         }
170         exit 1;
171     }
172 }
173
174 sub text_to_hash {
175     my %hash;
176     for my $line ( split /\n/, $_[0] ) {
177         my($key, $value) = $line =~ /(\S+)\s*(\S*)/;
178         $value ||= '';
179         $hash{$key} = $value;
180     }
181
182     return %hash;
183 }
184 sub set_dep {
185     my ($name, $module, $version) = @_;
186     my %list = @{$deps{$name}};
187     $list{$module} = ($version || '');
188     $deps{$name} = [ %list ];
189 }
190
191 $deps{'CORE'} = [ text_to_hash( << '.') ];
192 Class::Accessor 0.34
193 DateTime 0.44
194 DateTime::Locale 0.40
195 Digest::base
196 Digest::MD5 2.27
197 Digest::SHA
198 DBI 1.37
199 Class::ReturnValue 0.40
200 DBIx::SearchBuilder 1.59
201 Text::Template 1.44
202 File::ShareDir
203 File::Spec 0.8
204 HTML::Quoted
205 HTML::Scrubber 0.08
206 HTML::TreeBuilder
207 HTML::FormatText
208 Log::Dispatch 2.23
209 Sys::Syslog 0.16
210 Locale::Maketext 1.06
211 Locale::Maketext::Lexicon 0.32
212 Locale::Maketext::Fuzzy
213 MIME::Entity 5.425
214 Mail::Mailer 1.57
215 Email::Address
216 Text::Wrapper 
217 Time::ParseDate
218 Time::HiRes 
219 File::Temp 0.19
220 Text::Quoted 2.02
221 Tree::Simple 1.04
222 UNIVERSAL::require
223 Regexp::Common
224 Scalar::Util
225 Module::Versions::Report 1.05
226 Cache::Simple::TimedExpiry
227 Encode 2.39
228 CSS::Squish 0.06
229 File::Glob
230 Devel::StackTrace 1.19
231 Text::Password::Pronounceable
232 Devel::GlobalDestruction
233 List::MoreUtils
234 Net::CIDR
235 Regexp::Common::net::CIDR
236 Regexp::IPv6
237 .
238
239 $deps{'MASON'} = [ text_to_hash( << '.') ];
240 HTML::Mason 1.43
241 Errno
242 Digest::MD5 2.27
243 CGI::Cookie 1.20
244 Storable 2.08
245 Apache::Session 1.53
246 XML::RSS 1.05
247 Text::WikiFormat 0.76
248 CSS::Squish 0.06
249 Devel::StackTrace 1.19
250 JSON
251 IPC::Run3
252 .
253
254 $deps{'PSGI'} = [ text_to_hash( << '.') ];
255 CGI 3.38
256 CGI::PSGI 0.12
257 HTML::Mason::PSGIHandler 0.52
258 Plack 0.9971
259 Plack::Handler::Starlet
260 CGI::Emulate::PSGI
261 .
262 set_dep( PSGI => CGI => 4.00 ) if $] > 5.019003;
263
264
265 $deps{'MAILGATE'} = [ text_to_hash( << '.') ];
266 Getopt::Long
267 LWP::UserAgent
268 Pod::Usage
269 .
270
271 $deps{'SSL-MAILGATE'} = [ text_to_hash( << '.') ];
272 Crypt::SSLeay
273 Net::SSL
274 LWP::UserAgent 6.0
275 LWP::Protocol::https
276 Mozilla::CA
277 .
278
279 $deps{'CLI'} = [ text_to_hash( << '.') ];
280 Getopt::Long 2.24
281 LWP
282 HTTP::Request::Common
283 Text::ParseWords
284 Term::ReadLine
285 Term::ReadKey
286 .
287
288 $deps{'DEV'} = [ text_to_hash( << '.') ];
289 Email::Abstract
290 Test::Email
291 HTML::Form
292 HTML::TokeParser
293 WWW::Mechanize 1.52
294 Test::WWW::Mechanize 1.30
295 Module::Refresh 0.03
296 Test::Expect 0.31
297 XML::Simple
298 File::Find
299 Test::Deep 0 # needed for shredder tests
300 String::ShellQuote 0 # needed for gnupg-incoming.t
301 Log::Dispatch::Perl
302 Test::Warn
303 Test::Builder 0.90 # needed for is_passing
304 Test::MockTime
305 Log::Dispatch::Perl
306 Test::WWW::Mechanize::PSGI
307 Plack::Middleware::Test::StashWarnings 0.08
308 Test::LongString
309 Test::NoWarnings
310 Locale::PO
311 .
312
313 $deps{'FASTCGI'} = [ text_to_hash( << '.') ];
314 FCGI 0.74
315 FCGI::ProcManager
316 .
317
318 $deps{'MODPERL1'} = [ text_to_hash( << '.') ];
319 Apache::Request
320 Apache::DBI 0.92
321 .
322
323 $deps{'MODPERL2'} = [ text_to_hash( << '.') ];
324 Apache::DBI
325 HTML::Mason 1.36
326 .
327
328 $deps{'MYSQL'} = [ text_to_hash( << '.') ];
329 DBD::mysql 2.1018
330 .
331
332 $deps{'ORACLE'} = [ text_to_hash( << '.') ];
333 DBD::Oracle
334 .
335
336 $deps{'POSTGRESQL'} = [ text_to_hash( << '.') ];
337 DBIx::SearchBuilder 1.66
338 DBD::Pg 1.43
339 .
340
341 $deps{'SQLITE'} = [ text_to_hash( << '.') ];
342 DBD::SQLite 1.00
343 .
344
345 $deps{'GPG'} = [ text_to_hash( << '.') ];
346 GnuPG::Interface
347 PerlIO::eol
348 .
349
350 $deps{'ICAL'} = [ text_to_hash( << '.') ];
351 Data::ICal
352 .
353
354 $deps{'SMTP'} = [ text_to_hash( << '.') ];
355 Net::SMTP
356 .
357
358 $deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
359 HTML::RewriteAttributes 0.05
360 MIME::Types
361 URI 1.59
362 .
363
364 $deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ];
365 GraphViz
366 IPC::Run 0.90
367 .
368
369 $deps{'GD'} = [ text_to_hash( << '.') ];
370 GD
371 GD::Graph
372 GD::Text
373 .
374
375 $deps{'USERLOGO'} = [ text_to_hash( << '.') ];
376 Convert::Color
377 .
378
379 $deps{'HTML-DOC'} = [ text_to_hash( <<'.') ];
380 Pod::Simple 3.24
381 HTML::Entities
382 .
383
384 my %AVOID = (
385     'DBD::Oracle' => [qw(1.23)],
386     'Email::Address' => [qw(1.893 1.894)],
387     'Devel::StackTrace' => [qw(1.28 1.29)],
388 );
389
390 if ($args{'download'}) {
391     download_mods();
392 }
393
394
395 check_perl_version();
396
397 check_users();
398
399 my %Missing_By_Type = ();
400 foreach my $type (sort grep $args{$_}, keys %args) {
401     next unless ($type =~ /^with-(.*?)$/) and $deps{$1};
402
403     $type = $1;
404     section("$type dependencies");
405
406     my @missing;
407     my @deps = @{ $deps{$type} };
408
409     my %missing = test_deps(@deps);
410
411     if ( $args{'install'} ) {
412         for my $module (keys %missing) {
413             resolve_dep($module, $missing{$module}{version});
414             my $m = $module . '.pm';
415             $m =~ s!::!/!g;
416             if ( delete $INC{$m} ) {
417                 my $symtab = $module . '::';
418                 no strict 'refs';
419                 for my $symbol ( keys %{$symtab} ) {
420                     next if substr( $symbol, -2, 2 ) eq '::';
421                     delete $symtab->{$symbol};
422                 }
423             }
424             delete $missing{$module}
425                 if test_dep($module, $missing{$module}{version}, $AVOID{$module});
426         }
427     }
428
429     $Missing_By_Type{$type} = \%missing if keys %missing;
430 }
431
432 if ( $args{'install'} && keys %Missing_By_Type ) {
433     exec($script_path, @orig_argv, '--no-install');
434 }
435 else {
436     conclude(%Missing_By_Type);
437 }
438
439 sub test_deps {
440     my @deps = @_;
441
442     my %missing;
443     while(@deps) {
444         my $module = shift @deps;
445         my $version = shift @deps;
446         my($test, $error) = test_dep($module, $version, $AVOID{$module});
447         my $msg = $module . ($version && !$error ? " >= $version" : '');
448         print_found($msg, $test, $error);
449
450         $missing{$module} = { version => $version, error => $error } unless $test;
451     }
452
453     return %missing;
454 }
455
456 sub test_dep {
457     my $module = shift;
458     my $version = shift;
459     my $avoid = shift;
460
461     if ( $args{'list-deps'} ) {
462         print $module, ': ', $version || 0, "\n"; 
463     }
464     else {
465         no warnings 'deprecated';
466         eval "use $module $version ()";
467         if ( my $error = $@ ) {
468             return 0 unless wantarray;
469
470             $error =~ s/\n(.*)$//s;
471             $error =~ s/at \(eval \d+\) line \d+\.$//;
472             undef $error if $error =~ /this is only/;
473
474             return ( 0, $error );
475         }
476         
477         if ( $avoid ) {
478             my $version = $module->VERSION;
479             if ( grep $version eq $_, @$avoid ) {
480                 return 0 unless wantarray;
481                 return (0, "It's known that there are problems with RT and version '$version' of '$module' module. If it's the latest available version of the module then you have to downgrade manually.");
482             }
483         }
484
485         return 1;
486     }
487 }
488
489 sub resolve_dep {
490     my $module = shift;
491     my $version = shift;
492
493     print "\nInstall module $module\n";
494
495     my $ext = $ENV{'RT_FIX_DEPS_CMD'} || $ENV{'PERL_PREFER_CPAN_CLIENT'};
496     unless( $ext ) {
497         my $configured = 1;
498         {
499             local @INC = @INC;
500             if ( $ENV{'HOME'} ) {
501                 unshift @INC, "$ENV{'HOME'}/.cpan";
502             }
503             $configured = eval { require CPAN::MyConfig } || eval { require CPAN::Config };
504         }
505         unless ( $configured ) {
506             print <<END;
507 You haven't configured the CPAN shell yet.
508 Please run `@PERL@ -MCPAN -e shell` to configure it.
509 END
510             exit(1);
511         }
512         my $rv = eval { require CPAN; CPAN::Shell->install($module) };
513         return $rv unless $@;
514
515         print <<END;
516 Failed to load module CPAN.
517
518 -------- Error ---------
519 $@
520 ------------------------
521
522 When we tried to start installing RT's perl dependencies, 
523 we were unable to load the CPAN client. This module is usually distributed
524 with Perl. This usually indicates that your vendor has shipped an unconfigured
525 or incorrectly configured CPAN client.
526 The error above may (or may not) give you a hint about what went wrong
527
528 You have several choices about how to install dependencies in 
529 this situatation:
530
531 1) use a different tool to install dependencies by running setting the following
532    shell environment variable and rerunning this tool:
533     RT_FIX_DEPS_CMD='@PERL@ -MCPAN -e"install %s"'
534 2) Attempt to configure CPAN by running:
535    `@PERL@ -MCPAN -e shell` program from shell.
536    If this fails, you may have to manually upgrade CPAN (see below)
537 3) Try to update the CPAN client. Download it from:
538    http://search.cpan.org/dist/CPAN and try again
539 4) Install each dependency manually by downloading them one by one from
540    http://search.cpan.org
541
542 END
543         exit(1);
544     }
545
546     if( $ext =~ /\%s/) {
547         $ext =~ s/\%s/$module/g; # sprintf( $ext, $module );
548     } else {
549         $ext .= " $module";
550     }
551     print "\t\tcommand: '$ext'\n";
552     return scalar `$ext 1>&2`;
553 }
554
555 sub download_mods {
556     my %modules;
557     use CPAN;
558     
559     foreach my $key (keys %deps) {
560         my @deps = (@{$deps{$key}});
561         while (@deps) {
562             my $mod = shift @deps;
563             my $ver = shift @deps;
564             next if ($mod =~ /^(DBD-|Apache-Request)/);
565             $modules{$mod} = $ver;
566         }
567     }
568     my @mods = keys %modules;
569     CPAN::get();
570     my $moddir = $args{'download'};
571     foreach my $mod (@mods) {
572         $CPAN::Config->{'build_dir'} = $moddir;
573         CPAN::get($mod);
574     }
575
576     opendir(DIR, $moddir);
577     while ( my $dir = readdir(DIR)) {
578         print "Dir is $dir\n";
579         next if ( $dir =~ /^\.\.?$/);
580
581         # Skip things we've previously tagged
582         my $out = `svn ls $args{'repository'}/tags/$dir`;
583         next if ($out);
584
585         if ($dir =~ /^(.*)-(.*?)$/) {
586             `svn_load_dirs -no_user_input -t tags/$dir -v $args{'repository'} dists/$1 $moddir/$dir`;
587             `rm -rf $moddir/$dir`;
588
589         }
590
591     }
592     closedir(DIR);
593     exit;
594 }
595
596 sub check_perl_version {
597   section("perl");
598   eval {require 5.008003};
599   if ($@) {
600     print_found("5.8.3", 0,"RT is known to be non-functional on versions of perl older than 5.8.3. Please upgrade to 5.8.3 or newer.");
601     exit(1);
602   } else {
603     print_found( sprintf(">=5.8.3(%vd)", $^V), 1 );
604   }
605 }
606
607 sub check_users {
608   section("users");
609   print_found("rt group (@RTGROUP@)",      defined getgrnam("@RTGROUP@"));
610   print_found("bin owner (@BIN_OWNER@)",   defined getpwnam("@BIN_OWNER@"));
611   print_found("libs owner (@LIBS_OWNER@)", defined getpwnam("@LIBS_OWNER@"));
612   print_found("libs group (@LIBS_GROUP@)", defined getgrnam("@LIBS_GROUP@"));
613   print_found("web owner (@WEB_USER@)",    defined getpwnam("@WEB_USER@"));
614   print_found("web group (@WEB_GROUP@)",   defined getgrnam("@WEB_GROUP@"));
615 }
616
617 1;
618
619 __END__
620
621 =head1 NAME
622
623 rt-test-dependencies - test rt's dependencies
624
625 =head1 SYNOPSIS
626
627     rt-test-dependencies
628     rt-test-dependencies --install
629     rt-test-dependencies --with-mysql --with-fastcgi
630
631 =head1 DESCRIPTION
632
633 by default, C<rt-test-dependencies> determines whether you have installed all
634 the perl modules RT needs to run.
635
636 the "RT_FIX_DEPS_CMD" environment variable, if set, will be used instead of
637 the standard CPAN shell by --install to install any required modules.  it will
638 be called with the module name, or, if "RT_FIX_DEPS_CMD" contains a "%s", will
639 replace the "%s" with the module name before calling the program.
640
641 =head1 OPTIONS
642
643 =over
644
645 =item install
646
647     install missing modules
648
649 =item verbose
650
651 list the status of all dependencies, rather than just the missing ones.
652
653 -v is equal to --verbose
654
655 =item specify dependencies
656
657 =over
658
659 =item --with-mysql
660
661     database interface for mysql
662
663 =item --with-postgresql
664
665     database interface for postgresql 
666
667 =item with-oracle       
668     
669     database interface for oracle
670
671 =item with-sqlite 
672
673     database interface and driver for sqlite (unsupported)
674
675 =item with-fastcgi 
676
677     libraries needed to support the fastcgi handler
678
679 =item with-modperl1
680
681     libraries needed to support the modperl 1 handler
682
683 =item with-modperl2
684
685     libraries needed to support the modperl 2 handler
686
687 =item with-dev
688
689     tools needed for RT development
690
691 =back
692
693 =back
694