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