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