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