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