rt 4.2.15
[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-2018 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.908
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 Encode 2.64
243 Net::CIDR
244 Plack 1.0002
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::Request
317 .
318
319 $deps{'MYSQL'} = [ text_to_hash( << '.') ];
320 DBD::mysql 2.1018
321 .
322
323 $deps{'ORACLE'} = [ text_to_hash( << '.') ];
324 DBD::Oracle
325 .
326
327 $deps{'PG'} = [ text_to_hash( << '.') ];
328 DBIx::SearchBuilder 1.66
329 DBD::Pg 1.43
330 .
331
332 $deps{'SQLITE'} = [ text_to_hash( << '.') ];
333 DBD::SQLite 1.00
334 .
335
336 $deps{'GPG'} = [ text_to_hash( << '.') ];
337 File::Which
338 GnuPG::Interface
339 PerlIO::eol
340 .
341
342 $deps{'SMIME'} = [ text_to_hash( << '.') ];
343 Crypt::X509
344 File::Which
345 String::ShellQuote
346 .
347
348 $deps{'ICAL'} = [ text_to_hash( << '.') ];
349 Data::ICal
350 .
351
352 $deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
353 MIME::Types
354 URI 1.59
355 URI::QueryParam
356 .
357
358 $deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ];
359 GraphViz
360 IPC::Run 0.90
361 .
362
363 $deps{'GD'} = [ text_to_hash( << '.') ];
364 GD
365 GD::Graph 1.47
366 GD::Text
367 .
368
369 $deps{'USERLOGO'} = [ text_to_hash( << '.') ];
370 Convert::Color
371 .
372
373 $deps{'HTML-DOC'} = [ text_to_hash( <<'.') ];
374 HTML::Entities
375 Pod::Simple 3.24
376 .
377
378 my %AVOID = (
379     'DBD::Oracle' => [qw(1.23)],
380     'Devel::StackTrace' => [qw(1.28 1.29)],
381     'DateTime::Locale'  => [qw(1.00 1.01)],
382     'DBD::mysql'        => [qw(4.042)],
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     unless (defined $args{siteinstall}) {
493         require Config;
494         my %uniq;
495         my @order = grep {($_ eq $Config::Config{sitelibexp}
496                         or $_ eq $Config::Config{privlibexp})
497                         and not $uniq{$_}++} @INC;
498         if ($] < 5.011 and @order == 2
499                 and $order[0] eq $Config::Config{sitelibexp}
500                 and $order[1] eq $Config::Config{privlibexp}) {
501
502             print "\n";
503             print "Patched perl, with site_perl before core in \@INC, detected.\n";
504             print "Installing dual-life modules into site_perl so they are not\n";
505             print "later overridden by the distribution's package.\n";
506
507             $args{siteinstall} = 1;
508         } else {
509             $args{siteinstall} = 0;
510         }
511     }
512
513     print "\nInstall module $module\n";
514
515     my $ext = $ENV{'RT_FIX_DEPS_CMD'} || $ENV{'PERL_PREFER_CPAN_CLIENT'};
516     unless( $ext ) {
517         my $configured = 1;
518         {
519             local @INC = @INC;
520             if ( $ENV{'HOME'} ) {
521                 unshift @INC, "$ENV{'HOME'}/.cpan";
522             }
523             $configured = eval { require CPAN::MyConfig } || eval { require CPAN::Config };
524         }
525         unless ( $configured ) {
526             print <<END;
527 You haven't configured the CPAN shell yet.
528 Please run `@PERL@ -MCPAN -e shell` to configure it.
529 END
530             exit(1);
531         }
532
533         my $installdirs = $CPAN::Config->{makepl_arg} ||= "";
534         $installdirs =~ s/(\bINSTALLDIRS=\S+|$)/ INSTALLDIRS=site/
535             if $args{siteinstall};
536         local $CPAN::Config->{makepl_arg} = $installdirs;
537
538         my $rv = eval { require CPAN; CPAN::Shell->install($module) };
539         return $rv unless $@;
540
541         print <<END;
542 Failed to load module CPAN.
543
544 -------- Error ---------
545 $@
546 ------------------------
547
548 When we tried to start installing RT's perl dependencies, 
549 we were unable to load the CPAN client. This module is usually distributed
550 with Perl. This usually indicates that your vendor has shipped an unconfigured
551 or incorrectly configured CPAN client.
552 The error above may (or may not) give you a hint about what went wrong
553
554 You have several choices about how to install dependencies in 
555 this situatation:
556
557 1) use a different tool to install dependencies by running setting the following
558    shell environment variable and rerunning this tool:
559     RT_FIX_DEPS_CMD='@PERL@ -MCPAN -e"install %s"'
560 2) Attempt to configure CPAN by running:
561    `@PERL@ -MCPAN -e shell` program from shell.
562    If this fails, you may have to manually upgrade CPAN (see below)
563 3) Try to update the CPAN client. Download it from:
564    http://search.cpan.org/dist/CPAN and try again
565 4) Install each dependency manually by downloading them one by one from
566    http://search.cpan.org
567
568 END
569         exit(1);
570     }
571
572     if( $ext =~ /\%s/) {
573         $ext =~ s/\%s/$module/g; # sprintf( $ext, $module );
574     } else {
575         $ext .= " $module";
576     }
577     print "\t\tcommand: '$ext'\n";
578     return scalar `$ext 1>&2`;
579 }
580
581 sub check_perl_version {
582   section("perl");
583   eval {require 5.010_001};
584   if ($@) {
585     print_found("5.10.1", 0, sprintf("RT requires Perl v5.10.1 or newer. Your current Perl is v%vd", $^V));
586     exit(1);
587   } else {
588     print_found( sprintf(">=5.10.1(%vd)", $^V), 1 );
589   }
590 }
591
592 sub check_users {
593   section("users");
594   print_found("rt group (@RTGROUP@)",      defined getgrnam("@RTGROUP@"));
595   print_found("bin owner (@BIN_OWNER@)",   defined getpwnam("@BIN_OWNER@"));
596   print_found("libs owner (@LIBS_OWNER@)", defined getpwnam("@LIBS_OWNER@"));
597   print_found("libs group (@LIBS_GROUP@)", defined getgrnam("@LIBS_GROUP@"));
598   print_found("web owner (@WEB_USER@)",    defined getpwnam("@WEB_USER@"));
599   print_found("web group (@WEB_GROUP@)",   defined getgrnam("@WEB_GROUP@"));
600 }
601
602 1;
603
604 __END__
605
606 =head1 NAME
607
608 rt-test-dependencies - test rt's dependencies
609
610 =head1 SYNOPSIS
611
612     rt-test-dependencies
613     rt-test-dependencies --install
614     rt-test-dependencies --with-mysql --with-fastcgi
615
616 =head1 DESCRIPTION
617
618 by default, C<rt-test-dependencies> determines whether you have installed all
619 the perl modules RT needs to run.
620
621 the "RT_FIX_DEPS_CMD" environment variable, if set, will be used instead of
622 the standard CPAN shell by --install to install any required modules.  it will
623 be called with the module name, or, if "RT_FIX_DEPS_CMD" contains a "%s", will
624 replace the "%s" with the module name before calling the program.
625
626 =head1 OPTIONS
627
628 =over
629
630 =item install
631
632     install missing modules
633
634 =item verbose
635
636 list the status of all dependencies, rather than just the missing ones.
637
638 -v is equal to --verbose
639
640 =item specify dependencies
641
642 =over
643
644 =item --with-mysql
645
646 database interface for mysql
647
648 =item --with-pg
649
650 database interface for postgresql
651
652 =item --with-oracle
653
654 database interface for oracle
655
656 =item --with-sqlite
657
658 database interface and driver for sqlite (unsupported)
659
660 =item --with-fastcgi
661
662 libraries needed to support the fastcgi handler
663
664 =item --with-modperl1
665
666 libraries needed to support the modperl 1 handler
667
668 =item --with-modperl2
669
670 libraries needed to support the modperl 2 handler
671
672 =item --with-developer
673
674 tools needed for RT development
675
676 =back
677
678 =back
679