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