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