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