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