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