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