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