Dev to 4.0.11
[usit-rt.git] / sbin / rt-test-dependencies
1 #!/usr/bin/perl
2 # BEGIN BPS TAGGED BLOCK {{{
3 #
4 # COPYRIGHT:
5 #
6 # This software is Copyright (c) 1996-2013 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' => 1,
97     'with-CORE' => 1,
98     'with-CLI' => 1,
99     'with-MAILGATE' => 1, 
100     'with-DEV' => 0, 
101     'with-GPG' => 0,
102     'with-ICAL' => 1,
103     'with-SMTP' => 1,
104     'with-GRAPHVIZ' => 0,
105     'with-GD' => 1,
106     'with-DASHBOARDS' => 1,
107     'with-USERLOGO' => 1,
108     'with-SSL-MAILGATE' => 0,
109     'with-HTML-DOC' => 0,
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 .
299
300 $deps{'FASTCGI'} = [ text_to_hash( << '.') ];
301 FCGI 0.74
302 FCGI::ProcManager
303 .
304
305 $deps{'MODPERL1'} = [ text_to_hash( << '.') ];
306 Apache::Request
307 Apache::DBI 0.92
308 .
309
310 $deps{'MODPERL2'} = [ text_to_hash( << '.') ];
311 Apache::DBI
312 HTML::Mason 1.36
313 .
314
315 $deps{'MYSQL'} = [ text_to_hash( << '.') ];
316 DBD::mysql 2.1018
317 .
318
319 $deps{'ORACLE'} = [ text_to_hash( << '.') ];
320 DBD::Oracle
321 .
322
323 $deps{'POSTGRESQL'} = [ text_to_hash( << '.') ];
324 DBD::Pg 1.43
325 .
326
327 $deps{'SQLITE'} = [ text_to_hash( << '.') ];
328 DBD::SQLite 1.00
329 .
330
331 $deps{'GPG'} = [ text_to_hash( << '.') ];
332 GnuPG::Interface
333 PerlIO::eol
334 .
335
336 $deps{'ICAL'} = [ text_to_hash( << '.') ];
337 Data::ICal
338 .
339
340 $deps{'SMTP'} = [ text_to_hash( << '.') ];
341 Net::SMTP
342 .
343
344 $deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
345 HTML::RewriteAttributes 0.05
346 MIME::Types
347 URI 1.59
348 .
349
350 $deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ];
351 GraphViz
352 IPC::Run 0.90
353 .
354
355 $deps{'GD'} = [ text_to_hash( << '.') ];
356 GD
357 GD::Graph
358 GD::Text
359 .
360
361 $deps{'USERLOGO'} = [ text_to_hash( << '.') ];
362 Convert::Color
363 .
364
365 $deps{'HTML-DOC'} = [ text_to_hash( <<'.') ];
366 Pod::Simple 3.24
367 HTML::Entities
368 .
369
370 my %AVOID = (
371     'DBD::Oracle' => [qw(1.23)],
372     'Email::Address' => [qw(1.893 1.894)],
373     'Devel::StackTrace' => [qw(1.28 1.29)],
374 );
375
376 if ($args{'download'}) {
377     download_mods();
378 }
379
380
381 check_perl_version();
382
383 check_users();
384
385 my %Missing_By_Type = ();
386 foreach my $type (sort grep $args{$_}, keys %args) {
387     next unless ($type =~ /^with-(.*?)$/) and $deps{$1};
388
389     $type = $1;
390     section("$type dependencies");
391
392     my @missing;
393     my @deps = @{ $deps{$type} };
394
395     my %missing = test_deps(@deps);
396
397     if ( $args{'install'} ) {
398         for my $module (keys %missing) {
399             resolve_dep($module, $missing{$module}{version});
400             my $m = $module . '.pm';
401             $m =~ s!::!/!g;
402             if ( delete $INC{$m} ) {
403                 my $symtab = $module . '::';
404                 no strict 'refs';
405                 for my $symbol ( keys %{$symtab} ) {
406                     next if substr( $symbol, -2, 2 ) eq '::';
407                     delete $symtab->{$symbol};
408                 }
409             }
410             delete $missing{$module}
411                 if test_dep($module, $missing{$module}{version}, $AVOID{$module});
412         }
413     }
414
415     $Missing_By_Type{$type} = \%missing if keys %missing;
416 }
417
418 if ( $args{'install'} && keys %Missing_By_Type ) {
419     exec($0, @orig_argv, '--no-install');
420 }
421 else {
422     conclude(%Missing_By_Type);
423 }
424
425 sub test_deps {
426     my @deps = @_;
427
428     my %missing;
429     while(@deps) {
430         my $module = shift @deps;
431         my $version = shift @deps;
432         my($test, $error) = test_dep($module, $version, $AVOID{$module});
433         my $msg = $module . ($version && !$error ? " >= $version" : '');
434         print_found($msg, $test, $error);
435
436         $missing{$module} = { version => $version, error => $error } unless $test;
437     }
438
439     return %missing;
440 }
441
442 sub test_dep {
443     my $module = shift;
444     my $version = shift;
445     my $avoid = shift;
446
447     if ( $args{'list-deps'} ) {
448         print $module, ': ', $version || 0, "\n"; 
449     }
450     else {
451         eval "use $module $version ()";
452         if ( my $error = $@ ) {
453             return 0 unless wantarray;
454
455             $error =~ s/\n(.*)$//s;
456             $error =~ s/at \(eval \d+\) line \d+\.$//;
457             undef $error if $error =~ /this is only/;
458
459             return ( 0, $error );
460         }
461         
462         if ( $avoid ) {
463             my $version = $module->VERSION;
464             if ( grep $version eq $_, @$avoid ) {
465                 return 0 unless wantarray;
466                 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.");
467             }
468         }
469
470         return 1;
471     }
472 }
473
474 sub resolve_dep {
475     my $module = shift;
476     my $version = shift;
477
478     print "\nInstall module $module\n";
479
480     my $ext = $ENV{'RT_FIX_DEPS_CMD'} || $ENV{'PERL_PREFER_CPAN_CLIENT'};
481     unless( $ext ) {
482         my $configured = 1;
483         {
484             local @INC = @INC;
485             if ( $ENV{'HOME'} ) {
486                 unshift @INC, "$ENV{'HOME'}/.cpan";
487             }
488             $configured = eval { require CPAN::MyConfig } || eval { require CPAN::Config };
489         }
490         unless ( $configured ) {
491             print <<END;
492 You haven't configured the CPAN shell yet.
493 Please run `/usr/bin/perl -MCPAN -e shell` to configure it.
494 END
495             exit(1);
496         }
497         my $rv = eval { require CPAN; CPAN::Shell->install($module) };
498         return $rv unless $@;
499
500         print <<END;
501 Failed to load module CPAN.
502
503 -------- Error ---------
504 $@
505 ------------------------
506
507 When we tried to start installing RT's perl dependencies, 
508 we were unable to load the CPAN client. This module is usually distributed
509 with Perl. This usually indicates that your vendor has shipped an unconfigured
510 or incorrectly configured CPAN client.
511 The error above may (or may not) give you a hint about what went wrong
512
513 You have several choices about how to install dependencies in 
514 this situatation:
515
516 1) use a different tool to install dependencies by running setting the following
517    shell environment variable and rerunning this tool:
518     RT_FIX_DEPS_CMD='/usr/bin/perl -MCPAN -e"install %s"'
519 2) Attempt to configure CPAN by running:
520    `/usr/bin/perl -MCPAN -e shell` program from shell.
521    If this fails, you may have to manually upgrade CPAN (see below)
522 3) Try to update the CPAN client. Download it from:
523    http://search.cpan.org/dist/CPAN and try again
524 4) Install each dependency manually by downloading them one by one from
525    http://search.cpan.org
526
527 END
528         exit(1);
529     }
530
531     if( $ext =~ /\%s/) {
532         $ext =~ s/\%s/$module/g; # sprintf( $ext, $module );
533     } else {
534         $ext .= " $module";
535     }
536     print "\t\tcommand: '$ext'\n";
537     return scalar `$ext 1>&2`;
538 }
539
540 sub download_mods {
541     my %modules;
542     use CPAN;
543     
544     foreach my $key (keys %deps) {
545         my @deps = (@{$deps{$key}});
546         while (@deps) {
547             my $mod = shift @deps;
548             my $ver = shift @deps;
549             next if ($mod =~ /^(DBD-|Apache-Request)/);
550             $modules{$mod} = $ver;
551         }
552     }
553     my @mods = keys %modules;
554     CPAN::get();
555     my $moddir = $args{'download'};
556     foreach my $mod (@mods) {
557         $CPAN::Config->{'build_dir'} = $moddir;
558         CPAN::get($mod);
559     }
560
561     opendir(DIR, $moddir);
562     while ( my $dir = readdir(DIR)) {
563         print "Dir is $dir\n";
564         next if ( $dir =~ /^\.\.?$/);
565
566         # Skip things we've previously tagged
567         my $out = `svn ls $args{'repository'}/tags/$dir`;
568         next if ($out);
569
570         if ($dir =~ /^(.*)-(.*?)$/) {
571             `svn_load_dirs -no_user_input -t tags/$dir -v $args{'repository'} dists/$1 $moddir/$dir`;
572             `rm -rf $moddir/$dir`;
573
574         }
575
576     }
577     closedir(DIR);
578     exit;
579 }
580
581 sub check_perl_version {
582   section("perl");
583   eval {require 5.008003};
584   if ($@) {
585     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.");
586     exit(1);
587   } else {
588     print_found( sprintf(">=5.8.3(%vd)", $^V), 1 );
589   }
590 }
591
592 sub check_users {
593   section("users");
594   print_found("rt group (uio-rt)",      defined getgrnam("uio-rt"));
595   print_found("bin owner (root)",   defined getpwnam("root"));
596   print_found("libs owner (root)", defined getpwnam("root"));
597   print_found("libs group (bin)", defined getgrnam("bin"));
598   print_found("web owner (httpd)",    defined getpwnam("httpd"));
599   print_found("web group (httpd)",   defined getgrnam("httpd"));
600 }
601
602 1;
603
604 __END__
605
606 =head1 NAME
607
608 rt-test-dependencies - test rt's dependencies
609
610 =head1 SYNOPSIS
611
612     rt-test-dependencies
613     rt-test-dependencies --install
614     rt-test-dependencies --with-mysql --with-fastcgi
615
616 =head1 DESCRIPTION
617
618 by default, C<rt-test-dependencies> determines whether you have installed all
619 the perl modules RT needs to run.
620
621 the "RT_FIX_DEPS_CMD" environment variable, if set, will be used instead of
622 the standard CPAN shell by --install to install any required modules.  it will
623 be called with the module name, or, if "RT_FIX_DEPS_CMD" contains a "%s", will
624 replace the "%s" with the module name before calling the program.
625
626 =head1 OPTIONS
627
628 =over
629
630 =item install
631
632     install missing modules
633
634 =item verbose
635
636 list the status of all dependencies, rather than just the missing ones.
637
638 -v is equal to --verbose
639
640 =item specify dependencies
641
642 =over
643
644 =item --with-mysql
645
646     database interface for mysql
647
648 =item --with-postgresql
649
650     database interface for postgresql 
651
652 =item with-oracle       
653     
654     database interface for oracle
655
656 =item with-sqlite 
657
658     database interface and driver for sqlite (unsupported)
659
660 =item with-fastcgi 
661
662     libraries needed to support the fastcgi handler
663
664 =item with-modperl1
665
666     libraries needed to support the modperl 1 handler
667
668 =item with-modperl2
669
670     libraries needed to support the modperl 2 handler
671
672 =item with-dev
673
674     tools needed for RT development
675
676 =back
677
678 =back
679